Godot : Scènes et Scripts

De Centre de Ressources Numériques - Labomedia
Révision de 22 février 2018 à 20:13 par Serge (discussion | contributions) (Code final)

(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à : navigation, rechercher

Instancier une scène et apprendre les bases du code dans Godot.

Présentation

Godot tire sa puissance du système de nœuds qu'il met en place. Par rapport à Processing, il est plus facile de faire communiquer différents scripts ensembles, et notamment de les instancier.

La présente page à un but double : apprendre à faire communiquer différentes scènes entre elles, et apprendre les rudiments du code de l'application, à travers un simple programme qui fait apparaître et disparaître des flocons dans la fenêtre de jeu.

Préparation

Avant tout créez un nouveau projet que vous pourrez appeler PluieDeFlocons, par exemple. Je vais lui donner des dimensions de 600 pixels par 600 pixels, et un fond noir, mais vous pouvez choisir d'autres variables si vous prenez soin d'intégrer ces changements dans le code qui va venir. Pour ce projet, je vous propose de ne pas appeler la scène principale "main", mais "pluie". Vous comprendrez pourquoi.

Création du flocon

Nous n'allons pas tout de suite nous occuper de la scène pluie. Celle-ci servira plus tard à créer une pluie de flocons. Mais pour l'instant, nous avons d'abord besoin de créer un flocon.

Allez dans l'onglet "Scène" et cliquez "Nouvelle Scène".

Godot instance a.png

Dans l'onglet "Scène", ajoutez un nouveau nœud. Cette fois-ci, servez-vous de la barre de recherche pour sélectionner "Sprite". C'est le type de nœud qui permet de manipuler des images. Renommez-le "flocon" et sauvegardez-le dans le dossier "core", à côté de la scène "pluie".

Godot instance b.png

Vous pouvez maintenant alterner, dans l'éditeur, entre les deux scènes facilement.

Godot instance c.png

Mais nous restons pour l'instant sur la scène "flocon". Nous pourrions utiliser les différents onglets pour changer les paramètres du Sprite, par exemple l'image qu'il affiche, mais ce ne serait pas tout à fait une bonne idée. Ces onglets sont faits pour travailler plus rapidement, et permettent, dirons-nous, de manipuler du code en cliquant sur des boutons. Mais développer un jeu vidéo passe nécessairement par du code, et l'idée ici est justement de se faire la main sur les principes de base du code. Le code est contenu dans un script, et nous allons ici en attacher un au Sprite "flocon", en cliquant sur le petit parchemin surmonté d'un plus, en haut à droite de l'onglet scène, en ayant sélectionné "flocon".

Godot instance d.png

On nous propose de créer un nouveau fichier nommé "flocon.gd", dans le dossier "core". Ce fichier est le script en lui-même : il déterminera comment se comporte la scène "flocon". Appuyez sur "Créer".

Godot instance e.png

Vous voilà dans le code en lui-même !

Godot instance f.png

Les lignes en blanc sont des commentaires pour rappeler aux débutants comment le script s'organise. Comme vous êtes avec moi, commencez par les enlever, pour ne garder que les éléments utiles. Le code ressemble maintenant à ça :

Godot instance g.png

La première ligne, extends Sprite, indique que le script est attaché à un nœud de type Sprite et, en tant que tel, contient des fonctionnalités préconçues qui lui sont adaptées, par exemple la possibilité de lui attribuer une image.

La ligne func _ready(): est une fonction. Celle-ci est spéciale : le code qui la suit s'exécute quand la scène arrive en jeu. La ligne pass, dans une fonction indique qu'elle n'effectue aucune action. En clair : le script, actuellement, n'a aucun effet !

Avant de continuer, voici l'image que nous allons utiliser. Elle est libre de droit, je l'ai réduite et éclaircie :

Flocon.png

Enregistrez-la, et mettez là à l'intérieur du dossier "data".

Retournez maintenant dans l'éditeur. Elle apparaît dans l'onglet "Système de fichiers", qui représente le dossier du projet. Vous pouvez si vous le souhaitez copier le chemin qui permet d'y accéder.

Godot instance h.png

Un petit mot sur les chemins. Les chemins dans Godot sont toujours représentés sous forme de string, c'est-à-dire une suite de caractères. En tant que tels, ils commencent et terminent par des guillemets ( " ). Le chemin commence toujours par res://, qui fait référence au dossier du jeu. Ensuite viennent le nom du ou des dossiers, puis le nom du fichier, chacun séparés par un slash ( / ). Donc pour accéder au fichier flocon.png, situé dans le dossier data, situé dans le dossier du projet, nous utilisons le chemin suivant : "res://data/flocon.png".

Nous allons d'abord le stocker dans une variable, c'est-à-dire demander à l'ordinateur de créer, dans une petite partie de sa mémoire, un endroit où la stocker afin qu'il puisse aller la chercher :

Godot instance i.png

var signifie que nous créons une nouvelle variable, image est son nom, preload() permet de lui attribuer un fichier externe, et nous lui indiquons le chemin où la trouver.

Très bien, maintenant que nous avons mis en place le chargement de l'image, nous allons l'attribuer au Sprite lorsque celui-ci arrive dans l'application. Pour cela, une fonction a déjà été mise en place au sein de Godot : set_texture(). Elle est disponible puisque le script commence par la ligne extends Sprite. Si la scène avait été un Node2D, cela n'aurait pas été permis. Entre parenthèses, nous indiquons la variable que nous souhaitons lui attribuer, ici image :

Godot instance j.png

Puisque la fonction _ready(): doit effectuer une action, la ligne pass n'est plus utile, nous l'avons remplacée.

Petite précision : nous en parlerons un peu plus tard, mais vous pouvez apercevoir une petite flèche avant certaines lignes de code dans mes exemples. Il s'agit de l'"indentation", que l'on ajoute en appuyant sur tabulation. Si elle est absente, elle empêchera votre programme de fonctionner, veillez-donc à la respecter selon mon modèle.

Godot instance indent.png

Appuyer maintenant sur F6, cela lancera la scène active, dans notre cas "flocon.tscn". Petit rappel : F5 lance la scène principale, F6 lance la scène active, celle en cours d'édition. Voici ce qui apparaît :

Godot instance flocon00.png

Le flocon apparaît bien, mais est situé en haut à gauche de l'écran. Pourquoi donc ? La position de base d'un Sprite est la suivante : Vector2(0,0). Vector2 indique le type de données dont il s'agit. Un Vector2 contient deux données qui correspondent l'une à l'espace horizontal ( x ), l'autre à l'espace vertical ( y ). La position du Sprite est donc actuellement de 0 sur l'axe x, 0 sur l'axe y. Ces coordonnées sont relatives au coin haut/gauche de l'écran, il est donc normal qu'il se trouve là.

Mon projet étant configuré en 600 pixels par 600 pixels, le centre de l'écran se trouve à la position Vector2(300,300). Plaçons y notre Sprite, en rajoutons une nouvelle ligne de code : set_pos(Vector2(300,300)).

Godot instance set pos.png

Une vérification en appuyant sur F6 nous permet de voir que la position a bien été modifiée !

Godot instance mid.png

Bien. C'est là que les choses sérieuses commencent. Nous souhaiterions que le flocon soit affiché au hasard sur l'écran. Puisque l'écran fait 600 pixels de large, il faudrait que nous puissions obtenir un nombre au hasard entre 0 et 600. Il existe déjà une fonction qui permet de l'obtenir : randi() % n. Assigné à une variable, cette formule donnera a cette variable un nombre au hasard entre 0 et n-1. Nous allons donc créer une nouvelle variable, position_x, que nous mettrons égale à randi() % 601. Comme cela :

Godot instance randi1.png

Qu'il suffit de dupliquer avec Ctrl-C Ctrl-V pour créer une autre variable : position_y.

Godot instance randi2.png

Enfin il suffit maintenant de remplacer les valeurs de set_pos() par nos nouvelles variables.

Godot instance randi3.png

Appuyez sur F6, et contemplez le résultat ! Le flocon est maintenant situé au hasard !

Godot instance random.png

Mais si vous fermez la fenêtre, et refaites F6, le flocon reste à la même position. Fichtre. En fait, la formule randi() n'est pas complètement aléatoire. Il est nécessaire de la rafraîchir régulièrement pour qu'elle fonctionne. Avant vos variables, insérez la fonction randomize() qui permet à l’aléatoire d'être remis à jour.

Godot instance randomize.png

Faîtes plusieurs essais avec F6 : le flocon est maintenant distribué aléatoirement sur l'écran !

Très bien, il est temps de passer à la pluie.

Mise en place de la pluie

Retournez sur la scène "pluie.tscn", et attachez lui un script. Enlevez les commentaires. Bien. Premièrement, nous allons importer le flocon dans la pluie. Pour cela, il faut le charger. Rien de compliqué : une variable nommée flocon, qui charge le chemin de la scène ( donc "res://core/flocon.tscn" ). Cependant, comme il s'agit d'une scène, il faut l'instancier pour qu'elle fonctionne, en rajoutant la commande .instance() à la fin. Le code ressemble désormais à cela :

Godot instance soloflocon.png

Pour l'instant, nous n'avons fait que charger la scène. Dans la fonction _ready():, nous allons maintenant l'ajouter, grâce à la fonction add_child() :

Godot instance l.png

Appuyez maintenant sur F5, et non sur F6. Cela va lancer la scène "pluie", qui est la scène principale. Et pouf ! Le flocon apparaît !

C'est assez chouette, mais loin d'être une pluie en bonne et due forme. Il faudrait qu'il y ait d'autres flocons qui apparaissent. Nous allons d'abord essayer d'en ajouter plusieurs à la fois. Pour cela, supprimons le .instance(). Lorsque celui-ci est présent à la fin d'une scène (plus précisément d'une variable qui contient une scène), il permet de l'ajouter à la scène actuellement active. Lorsqu'il en est absent, il permet d'utiliser la scène comme un modèle pour créer plusieurs copies de cette scène.

Godot instance m.png

Une fois le modèle prêt, créons une nouvelle variable et utilisons là pour copier la scène avant de l'ajouter. Le code sera plus simple à appréhender :

Godot instance flocon1.png

Pour l'instant nous n'en avons créé qu'un. Ajoutons en un deuxième.

Godot instance n.png

Lancez la scène principale :

Godot instance o.png

Et voilà ! Nous avons maintenant deux flocons qui s'affichent. Nous pourrions aisément créer de nombreuses variables pour ajouter autant de flocons que nous le souhaitons, mais ce serait fastidieux. Regardons plutôt le code suivant :

Godot instance q.png

La ligne de code for i in range ( 8 ): permet d'exécuter un bout de code 8 fois. C'est là que nous faisons une petite pause pour parler d'indentation. L'indentation est la petite flèche qui apparaît au début de certaines lignes de codes. Elle permet d'indiquer à quel fonction ce code appartient. Avant la fonction _ready():, il n'y a pas d'indentation. Après la fonction _ready():, dans nos codes précédents, il y a une indentation pour les lignes de code à l'intérieur de la fonction _ready():. Cela signifie que toute ligne de code qui a une indentation sera exécutée lorsque la fonction _ready(): sera exécutée (au moment où la scène est lancée). Nous remarquons qu'il y a une deuxième indentation après la ligne for i in range ( 8 ): dans le code ci-dessus. Ces lignes de codes seront donc toutes exécutées 8 fois, puisque l'indentation signifie qu'elles font partie de la fonction for i in range ( 8 ):. Pour ajouter une indentation, il faut appuyer sur la touche tabulation. Cependant, Godot les ajoute automatiquement.

Si le concept n'est pas encore clair, pas de panique. Pour l'instant, regardons ce qui se passe lorsque nous lançons la scène principale.

Godot instance p.png

Parfait, il y a maintenant bien huit flocons qui apparaissent à l'écran. Malgré tout, cela reste beaucoup trop statique. Nous allons maintenant voir comment Godot permet d'agir dans le temps, en utilisant le pendant de la fonction _ready():, la fonction _fixed_process(delta): .

Avant tout, il faut l'activer et la créer, comme cela :

Godot instance r.png

Plusieurs choses sont importantes ici. Tout d'abord, pour revenir au concept d'indentation, regardez la ligne 11. Il n'y a pas d'indentation. Cela permet d'indiquer à Godot que la fonction _ready(): est terminée. C'est pour la même raison que la ligne de code set_fixed_process(true) n'a qu'une seule indentation : elle ne fait pas partie de for i in range ( 8 ): car elle ne doit être exécutée qu'une seule fois.

set_fixed_process(true) s'inclut dans la fonction _ready():. Elle permet d'indiquer à Godot qu'il devra utiliser la fonction _fixed_process(delta):.

La fonction _fixed_process(delta): est particulièrement utile : c'est une fonction qui, si elle est activée, s'exécutera à chaque frame du jeu, c'est-à-dire, par défault, 25 fois par seconde. Faisons un essai, et utilisons là pour ajouter un nouveau flocon. Lancez ensuite l'application.

Godot instance process.png

On ne saurait être plus clair ! 25 fois par seconde, une copie de la scène flocon est ajoutée à la scène pluie. Cela commence à ressembler à quelque chose. Mais l'écran est rapidement rempli. Le mieux serait de faire disparaître le flocon peu à peu, comme s'il fondait. Pour cela, ce n'est pas la pluie qu'il faut modifier, mais le flocon lui-même. Cliquez sur "flocon.gd", dans le menu à gauche du script.

Godot instance gd.png

Rajoutez lui une variable opacite (sans accent) égale à 1, et activez sa fonction _fixed_process(delta):.

Godot instance t.png

L'opacité est la valeur de transparence d'une image. À 1, elle est complètement opaque, à 0, elle devient complètement transparente. Nous allons effectuer une opération intéressante :

Godot instance u.png

if est un outil très utilisé en code. Il permet d'effectuer une opération si et seulement si sa condition est respectée. La condition qui lui est ici assignée est le fait qu'opacite soit supérieure à 0. Tant qu'opacite sera supérieure à 0, elle effectuera les deux opération qui suivent (et qui sont indentées puisqu'elles appartiennent à if). D'abord opacite est baissée de 0.05, puis l'opacité du Sprite est changée pour la valeur actuelle d'opacite, grâce à la fonction set_opacity(). Ainsi, à chaque frame, l'opacité du Sprite sera baissée de 0.05 jusqu'à ce qu'il devienne transparent. Lancez la pluie avec F5.

Godot instance pluie.png

Bravo ! Le programme semble terminée, mais une dernière chose reste à régler. Si les Sprites disparaissent bien à l’œil, ils sont en fait gardés en mémoire. Comme ils ne servent plus à rien une fois qu'ils sont devenus transparents, nous allons les supprimer grâce à l'outil else :

Godot instance v.png

else ne s'utilise qu'après if. Il permet d'effectuer une opération si les conditions d'if ne sont pas remplies. Ici, si la valeur d'opacite est supérieure à 0, la ligne de code queue_free() ne sera pas exécutée. Mais si opacite devient égale ou inférieure à 0, alors ce sont les deux lignes de codes qui suivent if qui ne seront pas exécuté, et queue_free() sera exécuté à la place. queue_free() est une fonction qui permet de supprimer une scène lorsqu'elle n'est plus utile. Ainsi, nous pourrions laisser la pluie tourner des heures durant, elle ne surchargerait pas la mémoire de l'ordinateur.

Pour terminer, je vous laisse essayer le code suivant et comprendre par vous-même ce qu'il change au programme :

Godot instance final.png

Conclusion

Vous savez désormais utiliser les deux fonctions principales de Godot : _ready():, et _fixed_process(delta):, qui permettent respectivement de préparer un nœud quand il arrive dans le programme, puis de lui assigner une tâche à exécuter tant qu'il y reste.

Vous savez de surcroît utiliser une scène pour en instancier une autre. C'est ce qui fait la puissance du système de nœuds. On imagine rapidement une scène armée qui instancie des gnomes, ou un bouquet qui fait apparaître des fleurs. Pour aller plus loin, nous n'avons là qu'un code à deux niveaux. Imaginez donc un pays, qui instancie des villes, qui instancient des maisons, qui instancient des personnages...

Code final

flocon.gd

extends Sprite

var image = preload ("res://data/flocon.png")

var opacite = 1

func _ready():
	set_texture(image)
	
	randomize()
	var position_x = randi() % 601
	var position_y = randi() % 601
	
	set_pos(Vector2(position_x,position_y))

	set_fixed_process(true)

func _fixed_process(delta):
	
	if opacite > 0:
		opacite -= 0.05
		set_opacity(opacite)
	else:
		queue_free()

pluie.gd (version flocon seul)

extends Node2D

var flocon = preload("res://core/flocon.tscn")

func _ready():
	var nouveau_flocon = flocon.instance()
	
	add_child(nouveau_flocon)
	
	set_fixed_process(true)

func _fixed_process(delta):
	var nouveau_flocon = flocon.instance()
	
	add_child(nouveau_flocon)

pluie.tscn (version multi flocon)

extends Node2D

var flocon = preload("res://core/flocon.tscn")
var nombre_de_flocons = 8

func _ready():
	for i in range ( nombre_de_flocons ):
		var nouveau_flocon = flocon.instance()
		add_child(nouveau_flocon)

	set_fixed_process(true)

func _fixed_process(delta):
	for i in range ( nombre_de_flocons ):
		var nouveau_flocon = flocon.instance()
		add_child(nouveau_flocon)