Dans ce tutoriel, on se propose de mettre en place une mécanique simple et fonctionnelle de jeu en 2.5D avec Godot Engine.
15 min
Intermédiaire
Dans la famille des jeux vidéos, il existe une catégorie de jeux un peu bâtarde qui celle des jeux en 2.5D. C'est le cas de jeux tels que New Super Mario Bros 2 (sorti sur 3DS), ou encore du jeu Tarzan (sorti sur playstation 1). C'est aussi le cas du jeu Inside, un jeu dont on s'inspirera dans la suite de ce tutoriel. Il existe bien sûr un grand nombre d'autres jeux ayant adopté cette approche.
Ces jeux ne sont ni complètement en 3D -car le mouvement est contraint selon un plan- ni plus vraiment en 2D (l’environnement dans lequel nous évoluons est un environnement 3D). Cela dit, ils possèdent deux grands avantages :
Notre scène a maintenant une ambiance un peu plus personnelle. Ajoutons notre personnage.
Nous utilisons la fonction move_and_slide de notre Kinematic afin de gérer d'office les collisions. Cette fonction prend en paramètre une vélocité dont elle retourne un valeur corrigée. Pour l'instant, nous nous contentons de faire varier cette vélocité selon l'axe des x ce qui a pour effet de déplacer notre cube horizontalement.
Nous pouvons également prendre en compte le saut en faisant varier la vélocité selon l'axe vertical (axe des Y).
Le code suivant, qui prend en compte le saut, est tout autant utilisable en 2D :
- Les mouvements du joueurs sont à peu près aussi simples à gérer qu'en 2D.
- Les décors 3D apportent un profondeur de champs très intéressante qui peut même parfois rendre ces jeux plus simples à concevoir que des jeux en 2D !
Mise en place de la scène
La scène de base
Pour ce tutoriel, on ne démarrera pas à partir de rien. La scène de base ressemblera à ceci : Cette scène ne contient aucun modèle 3D complexe, uniquement des cubes de différentes tailles. Ces cubes sont des MeshInstances redimensionnés pour les circonstances. Notez qu'il est inutile de les assigner un quelconque comportement physique (donc inutile de les attacher à un PhysicBody) à l'exception notable du sol qui est un StaticBody. Le sol est en effet le seul élément avec lequel notre personnage interagira. Vous êtes bien sûr libre de concevoir la scène que vous voulez pour ce cours. Ceux qui ne souhaitent pas y passer de temps peuvent retrouver le fichier intégral ici : Main.tscnAmélioration du rendu
Pour commencer, nous allons améliorer un peu le rendu de notre scène en y ajoutant un nœud de type WorldEnvironment. Ensuite, nous allons redéfinir la propriété Environment de notre WorldEnvironment en cliquant sur [empty] puis New Environment. Quand nous faisons cela, tout devient noir/gris dans notre scène. Beurk. Pas de panique ! Nous définissons notre environnement à partir de rien, il est normal que ça ne ressemble à rien au début. Nous allons créer une ambiance sombre, proche de ce qu'on peut trouver dans le jeu Inside. Changeons les propriété suivantes dans notre Environment :- Background / mode : Color+sky
- Ambient Light / color : #a9a9a9
- Fog :
- Enabled : On
- Color : #1d1d1d
- Depth Begin : 7
Vous pouvez également, si l'effet vous plaît, activer les ombres en cliquant sur Shadow/enabled dans les propriétés de votre lumière.
Ajout du personnage
Notre personnage sera un simple cube rouge que nous ajouterons à notre scène au sein d'un KinematicBody. Ajoutons ce KinematicBody et ajoutons-lui comme enfant un MeshInstance ayant pour mesh un CubeMesh. Grossissez ce mesh en jouant sur sa scale Une fois cela fait, nous pouvons créer une CollisionShape à partir de ce Mesh en sélectionnant notre Mesh puis en cliquant sur puis create single convex collision sibling dans la barre du haut. Déplacez ensuite votre KinematicBody à un endroit quelconque de votre scène (vous pouvez vous aider du bouton puis Snap To Floor afin de poser votre Cube sur le sol). Nous obtenons : Nous pouvons aussi changer la couleur de notre cube ajoutant un SpatialMaterial au MeshInstance et en modifiant la color de sa propriété albedo. Un fois cela fait, il est temps de commencer à coder le comportement de notre cube/personnage.Déplacer le personnage le long d'un chemin
Configurer notre chemin
Pour que notre cube se déplace en mode 2.5D, il faut lui définir un chemin à suivre. Godot possède dans sa boîte à outil intégrée un nœud des plus utile appelé Path. Ce nœud, comme son nom l'indique, permettra de définir le chemin que doit emprunter notre cube. Ajoutez ce nœud Path à votre scène. Vous avez maintenant la possibilité d'ajouter/supprimer/déplacer des points à votre chemin à l'aide des boutons dédiées situés dans la barre du haut : L'enjeu maintenant est de déplacer notre personnage le long de ce chemin. Il existe plusieurs manières de faire. La nôtre va consister à privilégier l'emploi des nœuds au maximum afin de bien visualiser ce qu'on fait. Nous allons donc ajouter un nœud PathFollow à notre Path ainsi qu'un nœud Position3D dans ce PathFollow. Cette Position3D est contrainte par le PathFollow à suivre le chemin Path. Vous pouvez essayer de faire varier la propriété offset de votre PathFollow et visualiser que la position3D parcoure le chemin. L'idée est que notre Cube suive cette position tout en restant libre de ses mouvements verticaux (selon l'axe des y) pour les sauts. Voyons comment scripter ceci.Scripter le déplacement du cube
Ajoutons un script à notre cube et plaçons-y le code inital suivant :extends KinematicBody
func _physics_process(delta):
var input_h = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
Ce code récupère les entrées gauche et droite de l'utilisateur (les touches gauche et droite du clavier par exemple) au sein d'une fonction _physics_process. Cette fonction _physics_process est généralement le meilleur endroit pour gérer le déplacement d'un KinematicBody.
Déplacement en 2D pure
Mettons en place le code minimal pour le contrôle de notre Kinematic dans un jeu purement 2D.extends KinematicBody
export(float) var SPEED = 15.0
var velocity = Vector3.ZERO
func _physics_process(delta):
var input_h = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
velocity.x = input_h * SPEED
velocity = move_and_slide(velocity)
Ce code permet purement et simplement de déplacer notre Cube selon l'axe horizontal (l'axe des X). Il est quasiment transposable en l'état à un contexte de jeu en 2D (en remplaçant le Vector3 par un Vector2).
extends KinematicBody
export(float) var SPEED = 15.0
export(float) var JUMP = 10.0
export(float) var GRAVITY = 9.81
var velocity = Vector3.ZERO
func _physics_process(delta):
if Input.is_action_just_pressed("ui_accept"):
velocity.y += JUMP
velocity.y -= GRAVITY * delta
var input_h = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
velocity.x = input_h * SPEED
velocity = move_and_slide(velocity)
Avec ce code, vous pouvez déjà gérer certains jeux en 2.5D sans notion de profondeur de déplacement. Voyons comment gérer un cas plus complexe ou notre personnage doit suivre notre chemin.
Suivre le chemin
Pour vérifier que nous pouvons nous déplacer, nous devons ajouter une Camera à notre scène. Nous pouvons pour l'instant l'ajouter à notre Kinematic afin qu'elle suive ce dernier. Repositionnez-la dans votre scène à l'endroit qui vous paraît le plus judicieux. Si vous lancez votre scène maintenant, vous constatez que le cube se déplace et saute, ce qui est bien, mais pas selon le chemin que nous avons défini ! Faisons en sorte que ce soit le cas. Pour cela, commençons par récupérer les noeuds dont nous aurons besoin, à savoir le PathFollow et la Position3D, au sein de notre script !# ...
var velocity = Vector3.ZERO
onready var path_follow = get_parent().get_node("Path/PathFollow")
onready var position_ref = path_follow.get_node("Position3D")
func _physics_process(delta):
# ...
Remplaçons cette ligne :
velocity.x = input_h * SPEED
par cette ligne :
path_follow.offset += input_h * SPEED * delta
Nous faisons varier l'offset de notre pathfollow ce qui va avoir pour effet de déplacer notre Position3D de référence. Ceci ne déplace par pour autant notre cube. Pour le déplacer nous devons être en mesure définir notre fameuse velocity puisque c'est elle que nous passerons à move_and_slide. Pour trouver cette vélocité, nous allons simplement calculer la différence de position entre la position actuelle du cube et la position visée (en excluant la composante y qui est indépendante) :
path_follow.offset += input_h * SPEED * delta
var target = position_ref.global_transform.origin
target.y = global_transform.origin.y
var diff = target - global_transform.origin
Nous ne pouvons pas définir notre différence de position comme étant notre vélocity (bien que ce soit tentant). En effet, move_and_slide multipliera cette velocité par delta ce qui n'est pas pris en compte par notre diff. D'autre part, diff n'est pas censé impacter la composante y de la vélocité.
le code doit donc ressembler à ceci :
path_follow.offset += input_h * SPEED * delta
var target = position_ref.global_transform.origin
target.y = global_transform.origin.y
var diff = target - global_transform.origin
diff /= delta
velocity.x = diff.x
velocity.z = diff.z
velocity = move_and_slide(velocity)
Maintenant, quand nous exécutons notre programme, nous obtenons :
Magnifique, notre cube suit notre chemin ! Les bases sont posées.
Regarder dans la direction du déplacement
Notre cube se déplace bien sur le chemin mais ne regarde pas dans la direction dans laquelle il va ! Réglons ce problème en utilisant une fonction très utile de nos transforms : looking_at !# ...
var diff = target - global_transform.origin
if diff.length() > 0.01:
global_transform = global_transform.looking_at(target, Vector3.UP)
diff /= delta
# ...
Nous devons au préalable vérifier que notre élément se déplace bien un petit peu, autrement looking_at ne fonctionne pas.
Quand nous testons, nous voyons que cela fonctionne mais que nous avons introduit un nouveau "bug".
Notre caméra suit la rotation de notre cube. Ce n'est pas inintéressant mais ce n'est pas ce que nous avions en tête. Corrigeons cela en utilisant la petite astuce du RemoteTransform.
Voici la procédure :
- Ajouter un noeud RemoteTransform à notre cube.
- Ajouter un Spatial à la racine de notre scène, et nommez-le CameraRef.
- Faire en sorte que le RemoteTransform transmette sa position et uniquement sa position au CameraRef.
- Déplacer (reparenter) la caméra dans le noeud CameraRef.
- Visualisation du plan dans lequel s'inscrit le personnage (en plus du chemin)
- Gestion plus fine de la caméra (zoom/dézoom contextuel)
- Possibilité de changer de chemin en fonction de la zone !