Godot : Listes et Boutons
Créer un diaporama pour se familiariser avec les listes et le système de contrôle.
Sommaire
Présentation
Un des attraits de Godot réside dans le système de contrôle qu'il intègre. De nombreux nœuds sont mis en place afin que l'utilisateur puisse interagir avec le programme. Boutons, sliders, entrée texte, et autres ScrollBars permettent de développer rapidement de petits utilitaires adaptés aux besoins de chacun.
Le présent tutoriel propose de créer un simple diaporama (ici plus précisément un trombinoscope en l'honneur des acteurs de votre film préféré) connecté à deux boutons afin de comprendre les interactions possibles entre le l'utilisateur et le programme.
Note sur les fichiers
Le but de cette page n'étant pas d'expliquer la manipulation des fichiers, les images qui seront utilisées seront déjà redimensionnées à la même échelle, et seront chargés manuellement. J'entends par là qu'un programme plus complexe utiliserait un système de redimensionnement automatique, et également un système d'import des fichiers, mais que pour des raisons pratiques, ce tutoriel ne couvrira pas ces deux points.
Préparation
Commencez par créer un nouveau projet, aux dimensions 600*600 et intitulé Diaporama.
Téléchargez le dossier d'image et extrayez le dans le dossier data.
Enfin, attachez un script à la scène "main".
Système d'affichage
Au poil. La première de nos opérations va être de charger chacune des images au sein du programme. La ligne de code est la suivante :
var image1 = preload("chemin/image1")
Le chemin, pour rappel, se décompose comme suit : "res://dossier1/dossier2/fichier"
.
Dans notre cas, la première image devrait ressembler à ça :
var image1 = preload("res://data/trombines/wayne.png")
Créez une variable pour chacune de vos idoles et le code devrait ressembler à ça :
Il s'agit maintenant de créer un Sprite qui va pouvoir afficher ces images. Plutôt que de créer une nouvelle scène, nous allons cette fois créer le Sprite directement via la code. Premièrement, nous allons créer une variable qui permettra de le stocker. Nous pouvons la laisser vide pour l'instant. Elle s'appellera s (pour Sprite).
Nous allons maintenant nous occuper de la régler. Dans la fonction _ready():, nous allons d'abord indiquer à Godot qu'il s'agit d'un Sprite, en utilisant la commande Sprite.new()
. .new()
permet de rajouter des nœuds à l'intérieur même du code. Nous la réutiliserons tout-à-l'heure.
Puis nous allons la positionner. Au milieu de l'écran sur l'axe des X (à 300 pixels). Sur l'axe des Y, nous souhaiterions qu'il y ait un écart de 10 pixels entre le haut de l'image et le haut de la fenêtre. Un peu de maths : les images font 400 pixels de haut. Comme l'image est centrée sur la position du Sprite, l'image commencera donc 200 pixels au-dessus de sa position, et finira 200 pixels au-dessous. En mettant le Sprite à 210 sur l'axe des Y, l'image commencera donc à 10 pixels du haut de la fenêtre. Nous pouvons donc utiliser la fonction set_pos()
comme suit :
s.set_pos( Vector2( 300, 210 ) )
Ainsi, nous utilisons la fonction set_pos()
de s pour le situer à la position Vector2(300,210)
.
Dans la même logique, nous allons pour l'instant lui attribuer l'image1 grâce à sa fonction set_texture()
:
s.set_texture( image1 )
Enfin, nous l'ajoutons à la scène :
add_child( s )
Le code ressemble désormais à cela (n'oubliez pas d'indenter !) :
Et le programme affiche bien la trombine de l'homme le plus classe du monde :
Si vous changez la variable à l'intérieur de set_texture()
, par exemple en mettant s.set_texture( image4 )
, le programme change également :
C'est grâce à ce principe que nous allons créer notre diaporama. Pour cela, nous n'allons pas directement nous servir des variables, mais les stocker dans une liste, comme ceci :
Une liste permet de stocker une suite de variable, puis d'y accéder en utilisant un index.
Nous créons d'abord la liste :
var liste_image = [ image1, image2, image3, image4, image5 ]
Et si nous utilisons ensuite le code :
liste_image[0]
Nous obtiendrons la première variable de la liste.
liste_image[1]
Pour la seconde. Et cætera. Attention ! L'index commence à 0, non pas à 1, de plus, une erreur se produira si vous essayez d'accéder à un index qui ne contient pas de données (par exemple, ici, liste_image[42]
).
Revenons à nos moutons et essayons de modifier notre fonction set_texture()
:
Haha, sacré Robert ! Bon, plus qu'une étape avant que vous ne compreniez l'intérêt de la méthode. Créez une nouvelle variable, image_n
, égale à 0, et remplacez set_texture( image_list[2] )
par set_texture( image_list[image_n] )
.
Lancez la scène principale pour retrouver l'homme le plus classe du monde, bien vivant.
Je pense que vous avez saisi : nous n'avons maintenant plus qu'à modifier image_n
pour changer l'image du Sprite. En modifiant image_n, cela modifiera également l'index que nous allons chercher en tapant liste_image[image_n]
, ce qui modifiera donc l'image que nous attachons au Sprite en faisant set_texture( liste_image[image_n] )
. Les deux boutons ne servirons donc qu'à augmenter ou diminuer image_n
de 1.
Contrôle qu'on trolle
Le système de contrôles de Godot, permettant de créer l'interface utilisateur, est extrêmement polyvalent. Pensé pour permettre d'exporter ses programmes sur différentes tailles d'écrans, il est au début assez complexe à appréhender. Le but ici est de survoler les grands principes sans rentrer dans les détails, ce qui nous noierait sous des montagnes d'explication.
Le plus simple est généralement de regrouper tous les contrôles dans une fenêtre dédiée à ceux-ci. Il existe un nœud pour ce faire : Panel. Commençons par en créer un dans la fonction _ready():
:
Si nous lancions la scène maintenant, il n’apparaîtrait pas, car nous ne lui avons pas assigné de dimensions. Les contrôles sont tous des rectangles, qui se positionnent à l'aide de la fonction set_margin()
. set_margin()
prend deux arguments, c'est-à-dire qu'il y aura deux nombres entre parenthèses : le premier définit le bord du rectangle que nous souhaitons positionner, le deuxième sa position en pixel. Le premier nombre peut être 0 pour le bord gauche, 1 pour le bord haut, 2 pour le bord droit et 3 pour le bord gauche.
Dans notre cas, nous souhaiterions aligner les bord de ce panel sur les bords de l'image. Le bord gauche de l'image est situé à 100 pixels, son bord droit à 500 pixel. Nous allons donc redimensionner le panel comme ceci :
panel.set_margin( 0, 100 )
Pour mettre le bord gauche ( 0 ) à 100 pixels.
panel.set_margin( 2, 500 )
Pour mettre le bord droit ( 2 ) à 500 pixels.
Ensuite, nous souhaiterions faire commencer le haut du panel 10 pixels en-dessous de l'image, soit à 420 pixels, et le faire finir à 10 pixels du bas de la fenêtre du programme, soit à 590 pixels.
panel.set_margin( 1, 420 )
panel.set_margin( 3, 590 )
Lorsque nous lançons le programme, celle-ci apparaît maintenant aux bonnes positions !
Nous allons maintenant rajouter un premier bouton, bouton_gauche
. Nous n'allons cependant pas l'ajouter à la scène. Nous allons l'ajouter à l'intérieur de panel :
Lançons le programme sans avoir précisé la position du bouton.
Celui-ci n'apparaît pas en haut à gauche de la fenêtre, mais en haut à gauche de panel ! En effet, rajouter un contrôle à l'intérieur d'un autre contrôle rend sa position relative au contrôle auquel il a été ajouté. Plus précisément, elle la rend relative au coin haut/gauche du contrôle parent.
Premièrement, pour des raisons esthétiques, nous allons situer le bouton à 10 pixels du bord de panel, en le modifiant avec set_margin()
:
bouton_gauche.set_margin( 0, 10 )
bouton_gauche.set_margin( 1, 10 )
Le bord gauche du bouton est maintenant situé à 10 pixels du bord gauche du panel, et son bord haut à 10 pixel du bord haut du panel. Bien. Maintenant, élargissons le :
bouton_gauche.set_margin( 2, 195 )
Le bord droit du bouton est maintenant situé à 195 pixel du bord gauche du panel.
Nous pourrions faire la même chose pour son bord bas mais il existe une meilleure méthode. Par défaut, les bords bas et haut d'un contrôle sont situés relativement au bord haut de leur contrôle parent. Mais la fonction set_anchor()
permet de changer ce réglage :
bouton_gauche.set_anchor( 3, 1 )
Le bord bas du bouton ( 3 ) est maintenant situé relativement au bord bas du panel, selon la règle 1 de set_anchor()
.
bouton_gauche.set_margin( 3, 10 )
Nous mettons maintenant le bord bas du bouton ( 3 ) à 10 pixels de décalage.
En lançant la scène principale, on remarque que le bouton est maintenant aux bonnes dimensions ! Quel est l'intérêt de cette méthode ? Eh bien si nous changions maintenant la taille du panel, cela redimensionnerai également le bouton, puisque la taille du bouton est définie en fonction de la taille du panel !
Maintenant que nous avons notre bouton, il est l'heure de le connecter. Comment cela fonctionne-t-il ? Les contrôles, lorsqu'ils reçoivent une commande, émettent un signal. Dans le cas d'un bouton, il émettra le signal "button_down"
lorsque l'on cliquera dessus. La ligne de commande suivante va permettre de connecter le signal "button_down"
à une fonction que nous appellerons "photo_precedente"
:
Cela signifie que lorsque bouton_gauche
émettra le signal "button_down"
, le nœud self
(qui fait référence ici à main.tscn) exécutera la fonction photo_precedente()
. Que nous allons maintenant créer :
Comme nous l'avions dit, le but de ce bouton est de baisser image_n
de 1. Il y a cependant un risque. Si image_n
est à 0, elle passera à -1. Or, il n'existe pas d'index -1 dans liste_image
, ce qui risque de générer une erreur. Dans le cas où image_n
serait égale à 0, nous aimerions qu'elle repasse à l'index maximum de liste_image
. Il existe une fonction qui permet de récupérer l'index maximum d'une list : .size()
. liste_image.size()
donnera un nombre égal au nombre de variable dans image_list
, en partant de 1. Comme l'index part de 0, il faudra donc, si image_n
est égale à 0 et que nous cliquons sur le bouton pour aller à l'image précédente, que image_n
devienne égale à liste_image.size() - 1
. Dans le cas contraire, il faut baisser image_n
de 1 :
Enfin, comme l'image a changée, il faut mettre à jour le Sprite :
Lancez le programme et appuyez sur le bouton : vous pouvez maintenant, dans un sens, naviguer entre les images !
Maintenant, nous allons nous occuper du deuxième bouton. Copiez-collez le premier bouton et changez le nom de la variable par bouton_droite
:
Les bords haut et bas sont aux bonnes positions, mais les bords gauche et droit sont aux mêmes endroits que ceux du côté gauche. Pourtant, il n'est quasiment pas nécessaire de toucher aux dimensions du bouton. Plutôt que de positionner ses côtés par rapport au côté gauche du panel, nous allons les positionner par rapport au côté droit du panel :
bouton_droit.set_anchor( 0, 1 )
bouton_droit.set_anchor( 2, 1 )
Puis intervertir les valeurs de set_margin( 0, a )
et set_margin( 2, b )
:
Quand nous lançons le programme, les deux boutons sont correctement placés.
Maintenant, il s'agit de changer la connexion du bouton droit pour la fonction "photo_suivante"
.
Ensuite, copier-coller photo_precedente()
, changer son nom par photo_suivante()
, et inverser sa logique : si image_n
est égale à liste_image.size() - 1
, alors image_n = 0
. Sinon, on augmente image_n
de 1. Puis on met à jour l'image !
Voilà, vous pouvez maintenant lancer le programme et admirer le trombinoscope de vos rêves !
Conclusion
Vous savez maintenant utiliser une liste et accéder aux éléments qui la composent. Même si ça n'a l'air de rien de prime abord, c'est un outil très puissant pour ranger les variables par catégories. Mieux encore, une liste peut elle-même contenir d'autres listes afin d'obtenir un système de classement complexe.
Nous avons aussi effleuré le concept de contrôle bien qu'il nécessite un peu de pratique pour être totalement appréhendé. Je vous conseille de faire un tour dans la documentation pour regarder les différents types de contrôles et les signaux qu'ils émettent, afin d'avoir une meilleure vision des programmes que vous pourriez mettre en place.
Sur ce, j'me taperai bien une 'ouiche.
Code final
main.gd
extends Node2D
var image1 = preload("res://data/trombines/wayne.png")
var image2 = preload("res://data/trombines/newman.png")
var image3 = preload("res://data/trombines/redford.png")
var image4 = preload("res://data/trombines/gable.png")
var image5 = preload("res://data/trombines/fonda.png")
var liste_image = [ image1, image2, image3, image4, image5 ]
var image_n = 0
var s
func _ready():
s = Sprite.new()
s.set_pos( Vector2( 300, 210 ) )
s.set_texture( liste_image[image_n] )
add_child( s )
var panel = Panel.new()
panel.set_margin( 0, 100 )
panel.set_margin( 2, 500 )
panel.set_margin( 1, 420 )
panel.set_margin( 3, 590 )
add_child(panel)
var bouton_gauche = Button.new()
bouton_gauche.set_margin( 0, 10 )
bouton_gauche.set_margin( 1, 10 )
bouton_gauche.set_margin( 2, 195 )
bouton_gauche.set_anchor( 3, 1 )
bouton_gauche.set_margin( 3, 10 )
bouton_gauche.connect("button_down", self, "photo_precedente")
panel.add_child( bouton_gauche )
var bouton_droit = Button.new()
bouton_droit.set_anchor( 0, 1 )
bouton_droit.set_anchor( 2, 1 )
bouton_droit.set_margin( 0, 195 )
bouton_droit.set_margin( 1, 10 )
bouton_droit.set_margin( 2, 10 )
bouton_droit.set_anchor( 3, 1 )
bouton_droit.set_margin( 3, 10 )
bouton_droit.connect("button_down", self, "photo_suivante")
panel.add_child( bouton_droit )
func photo_precedente():
if image_n == 0:
image_n = liste_image.size()-1
else:
image_n -= 1
s.set_texture( liste_image[image_n] )
func photo_suivante():
if image_n == liste_image.size()-1:
image_n = 0
else:
image_n += 1
s.set_texture( liste_image[image_n] )