Dans le cours précédent, nous avions mis en place les différents éléments de notre scène (sol, murs, balle et collectibles). Dans ce cours, nous nous consacrerons principalement au scripting des différents éléments :
- Contrôle de la balle
- Contrôle de la caméra
- Disparition des collectibles et mise à jour du score.
Dans le [course=142]cours précédent[/course], nous avions mis en place les différents éléments de notre scène (sol, murs, balle et collectibles). Dans ce cours, nous nous consacrerons principalement au scripting des différents éléments :
- Contrôle de la balle
- Contrôle de la caméra
- Disparition des collectibles et mise à jour du score.
Contrôle de la balle
Nous allons voir dans cette partie comment se passe le contrôle d'un RigidBody avec Godot. D'une manière générale, la récupération des entrées utilisateurs (nos
inputs) se fait à chaque frame (60 fois par seconde pour un FPS de 60). Godot fournit un certain nombre de fonctions qui seront appelées sur chacun des noeuds à chaque nouvelle frame.
Ces fonctions sont :
_integrate_forces(state)
: à utiliser pour le contrôle des RigidBody. Godot peut-être amené à lancer plusieurs fil d'exécutions pour optimiser les calculs liés au moteur physique. Ceci explique que cette fonction soit la seule fonction sûre pour le contrôle des éléments dépendant directement du moteur physique.
_physics_process(delta)
: à utiliser pour le contrôle des KinematicBody. Le taux de rafraichissement dépend du FPS, lequel peut-être amené à varier. Néanmoins, les Kinematics ont besoin d'inscrire leur logique de déplacement sur une période indépendante du FPS.
_process(delta)
: Certaines opérations ont besoin d'être effectuées aussi souvent que possible (à chaque frame). Dans ce cas, c'est cette fonction qui doit-être utilisée.
Notre balle étant un RigidBody, La fonction à utiliser est
_integrate_forces(state)
. Ajoutons un script à notre balle (Ball.gd) et récupérons les inputs utilisateurs.
extends RigidBody
func _integrate_forces(state):
var input_h = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
var input_v = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
Ici, nous soustrayons deux
Input.get_action_strength
ce qui a à peu près le même effet que le
Input.GetAxis
de Unity (les avantages de cette méthode sont exposés
ici).
Maintenant que nous avons récupéré nos inputs utilisateurs, nous pouvons bouger notre balle. Attention toutefois, car il n'est pas recommandé de changer directement la position de notre balle puisque il s'agit d'un RigidBody. Pour contrôler notre balle, nous devrons lui appliquer des
forces, des
torques , des
impulsions ou encore modifier se
vélocité. Ceci vaut aussi pour la 2D.
Dans le cas présent, nous allons simplement appliquer une force au centre de la balle, laquelle sera proportionnelle aux inputs de notre utilisateur.
Le code résultant est très simple :
extends RigidBody
func _integrate_forces(state):
var input_h = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
var input_v = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
var force = Vector3(input_h, 0, input_v)
add_central_force(force.normalized())
Nous créons un
vecteur force à partir de nos inputs (avec uniquement une composante
x et une composante
z) et nous appliquons cette force au centre de la balle. Remarquez que nous
normalisons ce vecteur force avant de l'appliquer à notre balle (
.normalized()
) afin de garantir que notre vecteur sera toujours de longueur 1.
Dernier ajustement, nous allons faire en sorte de pouvoir régler la vitesse de notre balle directement depuis l'éditeur en exportant une variable speed dans notre script et en la multipliant à notre force.
extends RigidBody
export(float,1, 20) var speed = 5
func _integrate_forces(state):
var input_h = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
var input_v = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
var force = Vector3(input_h, 0, input_v)
add_central_force(force.normalized() * speed) # NE PAS OUBLIER DE MULTIPLIER PAR SPEED !!
Ce sera tout pour notre balle !
Ajout de la caméra
Si vous lancez votre scène level à ce stade, vous vous retrouverez devant une belle fenêtre grise sans nulle trace de votre Level. Ceci est dû au fait que votre scène 3D doit impérativement avoir une
caméra parmi ses enfants. Ajoutons-la à notre level et déplaçons-la un peu de façon à la dissocier de notre balle !
[keynotion]Une astuce pratique pour contrôler le point de vue de notre caméra consiste à sélectionner la caméra dans notre hiérarchie, puis à diviser notre vue en deux en cliquant sur
puis
Two viewports, et enfin à cocher la case
dans le viewport où vous souhaitez voir le point de vue de votre caméra ![/keynotion]
Nous obtenons quelque chose comme ça :
Maintenant, quand vous lancez la scène et que vous contrôlez votre balle avec les flèches directionnelles du clavier (ou encore la croix de votre manette !), vous voyez bien votre balle se déplacer !
Si vous attendez trop longtemps, votre balle se mettra en sleep mode et ignorera les forces que nous lui appliquons. Pour éviter cela, pous pouvez décocher la propritété Can Sleep de votre Ball.
Pour éviter que la balle sorte du champs de vision de la caméra, nous allons faire en sorte que la caméra suive le mouvement de la balle.
Souvent, nous pouvons faire cela en plaçant notre caméra comme enfant de notre balle. Cette approche fonctionne très bien dans le cas où nous contrôlons un élément qui "reste bien droit" tout au long du jeu (un FPS ou un TPS, par exemple). Malheureusement, cela ne fonctionne pas dans le cas de notre balle puisque celle-ci roule. Voyez plutôt :
Dans le tutoriel Unity, le développeur choisit de résoudre ce problème grâce à un script : il récupère la différence de position entre la caméra et la balle et fait en sorte que cet écart soit maintenu tout au long du jeu.
On pourrait très bien résoudre la problème de la manière en Godot mais je préfère vous montrer une autre méthode, probablement meilleure.
La méthode présentée ci-dessous a l'avantage de se passer de script et de n'exploiter que les noeuds Godot.
[keynotion]Lorsque cela est possible, essayez toujours de résoudre vos problèmes en utilisant les noeuds à votre disposition plutôt qu'en passant par des scripts[/keynotion]
Nous allons ajouter un Spatial à notre
Level (appelons le
CameraRef) et placer notre caméra comme enfant de notre
CameraRef.
Ensuite, nous allons utiliser un noeud bien utile, appelé
RemoteTransfom, lequel a la capacité de transmettre sa transform ou une partie de sa transform à un autre noeud.
Ajoutez un
RemoteTransform à votre
Ball puis assigner sa propriété
Remote Path au noeud
CameraRef. Dépliez ensuite
et décochez
Rotation et
Scale de façon à ce que notre
RemoteTransform transmette
uniquement sa position au noeud
CameraRef.
Et voilà le travail, notre caméra suit notre balle sans que nous ayons eu à scripter quoi que ce soit !
Faire disparaître les collectibles
Nous allons maintenant voir comment faire disparaître les collectibles au passage de la balle.
Pour ce faire, rendons-nous dans notre scène
Pick Up. Ajoutons un script à notre Pick Up (
Pick Up.gd) et connectons le signal body_entered de notre noeud
Pick Up avec notre script :
Ainsi, la fonction
_on_Pick_Up_body_entered(body)
sera exécutée lorsque un body pénètrera dans la zone occupée par le collectible en question. Pour l'instant, nous nous contenterons de supprimer notre collectible de la scène si cela se produit :
extends Area
func _on_Pick_Up_body_entered(body):
queue_free()
Si vous lancez votre scène maintenant, vous constaterez que la balle "ramasse" bien les collectibles! Rapide, non ?
Ajout du score
Ajout de l'UI
Comme en 2D, l'ajout d'une UI passe par l'ajout d'un CanvasLayer et de noeud héritant de
Control.
Ajoutez donc un
CanvasLayer à votre UI et renommez-le
UI. En enfant de ce CanvasLayer, nous allons mettre un
Label qui contiendra notre score. Vous pouvez placer ce Label en haut à gauche de votre écran et mettre comme texte par défaut
Score : 0
.
Mise à jour du score
Nous allons maintenant connecter notre UI (et donc le score qu'elle affiche) avec le ramassage de notre Pick Up. Pour cela, nous allons faire en sorte que les Pick Up émette un signal lorsqu'ils sont capturés, lequel sera transmis à notre UI.
Commençons par créer un signal pour la capture au sein du script
Pick Up.gd. Appelons le
captured et émettons-le lorsque notre élément est capturé par notre balle.
extends Area
signal captured
func _on_Pick_Up_body_entered(body):
queue_free()
emit_signal("captured")
Connectons maintenant ce signal avec notre UI au sein de notre
Level. Pour cela, nous devons connectez tous les Pick Up à notre UI au démarrage de notre scène
Level.
Nous allons devoir passer par un script pour faire cela. Attachons un script à Level (
Level.gd) et commençons y coder notre fonction
_ready()
(laquelle s'exécute au début de la vie de notre scène, au moment où tous noeuds sont en place).
extends Spatial
func _ready():
pass
Il existe plusieurs manière de récupérer tous les Pick Up de notre scène depuis ce Script. Une façon de faire assez propre est d'assigner un groupe (équivalent des
Tags en Unity) à nos Pick Up et de récupérer tous les éléments de ce groupe. Assignons donc le groupe "Pick Up" à notre scène
Pick Up. Vous devrez pour cela retourner dans la scène
Pick Up, cliquez sur l'onglet
puis sur
. Ajoutez ensuite le groupe
Pick Up.
Récupérons maintenant tous les Pick Up dans la méthode
_ready()
de
Level.gd puis connectons leur signal
captured avec ce script.
extends Spatial
func _ready():
var pickups = get_tree().get_nodes_in_group("Pick Up")
for pickup in pickups:
pickup.connect("captured", self, "_on_Pick_Up_captured")
func _on_Pick_Up_captured():
pass
Ainsi notre, fonction
_on_Pick_Up_captured()
s'exécutera pour chaque Pick Up capturé ! Nous pouvons maintenant ajouter un variable
score
à notre script, l'
incrémenter à chaque fois que cette fonction est appelée et mettre à jour notre UI !
extends Spatial
var score = 0
func _ready():
var pickups = get_tree().get_nodes_in_group("Pick Up")
for pickup in pickups:
pickup.connect("captured", self, "_on_Pick_Up_captured")
func _on_Pick_Up_captured():
score += 1
$UI/LabelScore.text = "Score : %s" % score
Finitions
Notre jeu est terminé dans les grandes lignes. Nous allons tout de même apporter deux améliorations. Afficher un panneau de fin lorsque tous les Pick Up ont été ramassés et ajouter une lumière afin de rendre notre scène moins terne.
Ajout du panneau de fin de jeu
Dans notre UI, nous allons ajouter un
PanelContainer contenant un label affichant simplement le texte "You win !!!". Modifiez également le
du PanelContainer pour l'afficher au centre.
Ce panel doit être masqué par défaut. Renommez-le
EndContainer.
Maintenant, nous allons faire en sorte que ce panel soit affiché lorsque nous ramassons le dernier collectible. Pour cela, nous calculons le score maximal au démarrage de notre scène principale et nous le comparons au score actuel à chaque fois qu'un Pick Up est capturé.
extends Spatial
var score = 0
var score_max
func _ready():
var pickups = get_tree().get_nodes_in_group("Pick Up")
for pickup in pickups:
pickup.connect("captured", self, "_on_Pick_Up_captured")
score_max = pickups.size()
func _on_Pick_Up_captured():
score += 1
$UI/LabelScore.text = "Score : %s" % score
if score == score_max:
$UI/EndContainer.show()
Toutes les mécaniques du jeu sont en place. Enjoy !
Ajouter une lumière
Pour l'instant, notre scène n'est éclairée que par la lumière ambiante. Afin de rendre le tout un peu plus joli, nous allons ajouter une lumière directionnelle.
Ajoutez un noeud de type
DirectionnalLight à votre scène et orientez-la de la manière dont vous voudrez pour obtenir le rendu qui vous satisfait ! Voici les valeurs de la transform de ma lumière :
Vous pouvez également activer les ombres :
Bonus : Régler l'anticrénelage
Vous l'avez peut-être remarqué : les bords des éléments sont peu crénelés (effet "marche d'escalier"
:
Vous pouvez allez dans Project/Project Settings/Rendering/Quality et activer l'anticrénelage MSAA. Plus la valeur est élevé, plus l'anti-crénelage est important. Essayez
x8
, par exemple :
Cette simple opération rend notre jeu beaucoup agréable à regarder, non ?
Conclusion
Ceci conclut ce chapitre et ce cours initiatique sur la 3D avc Godot. Si vous souhaitez voir d'autres contenus sur le thème de la 3D avec Godot, n'hésitez pas à nous contacter !