Outils pour utilisateurs

Outils du site


dindomoteur_creation_du_joueur

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
dindomoteur_creation_du_joueur [2022/03/20 12:06] Simon Deplatdindomoteur_creation_du_joueur [2022/03/27 19:24] (Version actuelle) Simon Deplat
Ligne 28: Ligne 28:
  
 De manière intuitive, chacun d'entre eux aura **un nœud //Sprite// et une //Collision Shape 2D// de la bonne taille**. De manière intuitive, chacun d'entre eux aura **un nœud //Sprite// et une //Collision Shape 2D// de la bonne taille**.
 +
 +**Les calques et masques de collisions seront également réglés correctement** (le Joueur est sur le calque 1 et détecte le masque 4 (entre autres), et la plate-forme est sur le calque 4 et ne détecte rien).
  
 Ensuite, **je les ajoute au nœud Main en les instanciant**. Ils ne bougent pas, mais ils apparaissent à l'écran ! À noter : dans le futur, ceux-ci seront plutôt instanciés à l'intérieur d'un nœud "niveau". Ensuite, **je les ajoute au nœud Main en les instanciant**. Ils ne bougent pas, mais ils apparaissent à l'écran ! À noter : dans le futur, ceux-ci seront plutôt instanciés à l'intérieur d'un nœud "niveau".
Ligne 39: Ligne 41:
  
 **C'est un nœud //KinematicBody2D// qui sera le plus pertinent pour celui-ci.** Il existe de nombreux articles sur la différence entre les //KinematicBody// et les //RigidBody// qui rentrent en détail sur les différences de ceux-ci, et insistent sur le fait qu'**un personnage jouable est plus facilement géré via le //KinematicBody// en raison du contrôle fin qu'il est possible de lui donner** sans avoir à contrebalancer l'intégration automatique des forces physiques sur le nœud. **C'est un nœud //KinematicBody2D// qui sera le plus pertinent pour celui-ci.** Il existe de nombreux articles sur la différence entre les //KinematicBody// et les //RigidBody// qui rentrent en détail sur les différences de ceux-ci, et insistent sur le fait qu'**un personnage jouable est plus facilement géré via le //KinematicBody// en raison du contrôle fin qu'il est possible de lui donner** sans avoir à contrebalancer l'intégration automatique des forces physiques sur le nœud.
 +
 +Si vous n'êtes pas familier avec les corps physiques de Godot, [[https://docs.godotengine.org/fr/stable/tutorials/physics/physics_introduction.html#doc-physics-introduction|voici l'article dédié]].
  
 Voici [[https://docs.godotengine.org/fr/stable/tutorials/physics/using_kinematic_body_2d.html|la documentation du système de collision que nous utilisons]]. Voici [[https://docs.godotengine.org/fr/stable/tutorials/physics/using_kinematic_body_2d.html|la documentation du système de collision que nous utilisons]].
  
-Nous avions dans un premier temps commencé à utiliser **la méthode //move_and_slide_with_snap()//** afin de le déplacer, ce qui est très pratique et facile à faire. Comparée à la méthode //move_and_slide()//, **celle-ci permet d'accrocher automatiquement la //hitbox// du joueur à la //hitbox// de l'environement** avec laquelle il est en contact, même si son déplacement l'amène à traverser celle-ci. Il n'y a donc pas à corriger sa position 'à la main'.+Nous allons utiliser **la méthode //move_and_slide_with_snap()//** afin de déplacer le joueur, ce qui est très pratique et facile à faire. Comparée à la méthode //move_and_slide()//, **celle-ci permet d'accrocher automatiquement la //hitbox// du joueur à la //hitbox// de l’environnement** avec laquelle il est en contact, même si son déplacement l'amène à traverser celle-ci. Il n'y a donc pas à corriger sa position 'à la main'.
  
 **La méthode //move_and_slide()// est une méthode très pratique dans le cas d'un clone de Super Mario Bros 3, ou d'un jeu en vue de dessus comme //The Binding Of Isaac//.** Elle permet d'indiquer le déplacement du //Joueur//, à l'aide d'un //Vecteur 2D//, et **s'occupe automatiquement de faire glisser le //Joueur// contre la //hitbox// de l'environnement**. **Si un vecteur définissant l'orientation du sol lui est attribué, elle permet de différencier les murs, contre lesquels le joueur va glisser, et le sol, contre lequel la vélocité de déplacement est remise systématiquement à zéro**, et empêche donc de glisser. **La méthode //move_and_slide()// est une méthode très pratique dans le cas d'un clone de Super Mario Bros 3, ou d'un jeu en vue de dessus comme //The Binding Of Isaac//.** Elle permet d'indiquer le déplacement du //Joueur//, à l'aide d'un //Vecteur 2D//, et **s'occupe automatiquement de faire glisser le //Joueur// contre la //hitbox// de l'environnement**. **Si un vecteur définissant l'orientation du sol lui est attribué, elle permet de différencier les murs, contre lesquels le joueur va glisser, et le sol, contre lequel la vélocité de déplacement est remise systématiquement à zéro**, et empêche donc de glisser.
  
-Cependant, **cela ne permet pas d'implémenter certaines fonctionnalités traditionnelles des //metroidvania// récents**, comme le saut mural. En effet, **//move_and_slide()// est pensée pour ne proposer que la détection du sol**, et ne fera donc par exemple pas la différence entre le plafond et le mur, ou en même le sol si le vecteur qui indique son orientation n'est pas défini.+Je pensais en premier lieu que **cela ne permettait pas d'implémenter certaines fonctionnalités traditionnelles des //metroidvania// récents**, comme le saut mural. En effet, **//move_and_slide()// est pensée pour ne proposer que la détection du sol**, et n'est donc pas sensée faire la différence entre le plafond et le mur. Heureusement**les développeurs ont ajouté deux fonctions spécifiques pour ces cas particuliers : //is_on_wall()// et //is_on_ceiling()//**. 
 + 
 +Le cas échéant, **c'est la méthode //move_and_collide()// que nous aurions utilisée**. **Lorsque cette méthode est appelée avec la vélocité du joueur en argument, celle-ci renvoie un objet //KinematicCollision2D//, qui contient différentes informations à propos de la collision, notamment le vecteur de déplacement absorbé par l'objet en collision.** En analysant cet objet, il devient possible de déduire l'orientation de la //hitbox// que le personnage vient de toucher, et donc de différencier le sol, les murs, le plafond ou encore certaines pentes. Il est ainsi possible d'implémenter soi-même la réponse à la collision de manière fine. 
 + 
 +====Note à propos de la classe Joueur==== 
 +Dans les versions précédentes de //Godot//, l'exemple //Platformer// n'utilisait pas de système d'héritage pour mutualiser l'attraction terrestre entre le joueur et les ennemis. C'est maintenant le cas, et le script //Player// hérite d'//Actor//, qui lui-même de //Kinematic Body 2D//. 
 + 
 +Les scripts en deviennent cependant moins intuitifs et cela peut conduire à des erreurs. 
 + 
 +Notamment, dans le script //Actor.gd// de l'exemple, la vélocité verticale est modifiée par le delta de la fonction. Or, le paramètre delta n'est pas sensé être utilisé lors de l'appel à //move_and_slide()//. Ce paramètre est donc redéfini dans //Player.gd//, ce qui est contre-intuitif. 
 + 
 +**De fait nous n'utiliserons pas d'héritage pour la classe //Joueur.gd//.** 
 + 
 +====Implémentation de la gravité==== 
 + 
 +**[ NOTE ] : Comme discuté [[https://regakakobigman.netlify.app/godot/2019/10/09/how-to-use-delta.html|ici]], la gestion actuelle des accélérations est mal implémentée car elle n'est pas multipliée par delta.** 
 + 
 +Le joueur aura donc une masse et sera affecté par la gravité : 
 + 
 +<code> 
 +extends KinematicBody2D 
 + 
 +var _gravite = 50 
 + 
 +var _masse = 1 
 + 
 +var _velocite = Vector2.ZERO 
 + 
 +func _ready(): 
 + if get_tree().root.has_node("Global"): 
 + _gravite = Global.gravite 
 + 
 +func _physics_process( _delta ): 
 +  
 + # Intégration de la gravité 
 + _velocite.y += _gravite * _masse 
 +  
 + _velocite = move_and_slide_with_snap( 
 + _velocite, 
 + Vector2.ZERO, # Accroche au sol 
 + Vector2.UP, # Direction du sol ( Vector2.UP ) 
 + true, # Ne glisse pas sur le sol en cas d'inactivité ? 
 + 4, # Nombre de collisions traitées par cycle 
 + 0.9, # Angle maximum du sol ( en radians ) 
 + false # Inertie infinie ? 
 +
 +</code> 
 + 
 +====Implémentation du saut==== 
 + 
 +On ajoute au Joueur une capacité de saut. Plus les valeurs de //masse// et de //forceSaut// sont élevées, plus le saut est rapide et dynamique. 
 + 
 +//Personnage.gd// 
 +<code> 
 +extends KinematicBody2D 
 + 
 +var _gravite = 50 
 + 
 +var _masse = 1 
 +var _forceSaut = 1000 
 + 
 +var _velocite = Vector2.ZERO 
 + 
 +func _ready(): 
 + if get_tree().root.has_node("Global"): 
 + _gravite = Global.gravite 
 + 
 +func _physics_process( _delta ): 
 +  
 + entreeSaut() 
 +  
 + # Intégration de la gravité 
 + _velocite.y += _gravite * _masse 
 +  
 + _velocite = move_and_slide_with_snap( 
 + _velocite, 
 + Vector2.ZERO, # Accroche au sol 
 + Vector2.UP, # Direction du sol ( Vector2.UP ) 
 + true, # Ne glisse pas sur le sol en cas d'inactivité ? 
 + 4, # Nombre de collisions traitées par cycle 
 + 0.9, # Angle maximum du sol ( en radians ) 
 + false # Inertie infinie ? 
 +
 + 
 +# Appui sur le bouton de saut 
 +func entreeSaut(): 
 + # Mécanisme de saut 
 + if is_on_floor(): 
 + if Input.is_action_just_pressed("espace"): 
 + _velocite.y -= _forceSaut 
 +</code> 
 + 
 +====Ajout du déplacement latéral==== 
 +Un simple algorithme qui permet de **déplacer le Joueur sur l'axe horizontal**. Ici, le plus intéressant est la méthode //bouge()//, qui ne peut fonctionner qu'au clavier : le //nesting// permet de mettre en place plusieurs comportement différents en fonction de l'ordre dans lequel les touches sont appuyées/relâchées. 
 + 
 +Pas de phénomène d'accélération et d'inertie horizontale pour l'instant. Le déplacement pendant le saut est autorisé, et le restera probablement vu l'agréabilité de ce type de //gameplay//
 + 
 +<code> 
 +extends KinematicBody2D 
 + 
 +var _gravite = 50 
 + 
 +var _masse = 1 
 +var _forceSaut = 1000 
 +var _vitesse = 300 
 + 
 +var _direction = 0 
 + 
 +var _velocite = Vector2.ZERO 
 + 
 +func _ready(): 
 + if get_tree().root.has_node("Global"): 
 + _gravite = Global.gravite 
 + 
 +func _physics_process( _delta ): 
 +  
 + bouge() 
 + entreeSaut() 
 +  
 + # Intégration du déplacement latéral 
 + _velocite.x = _direction * _vitesse 
 + # Intégration de la gravité 
 + _velocite.y += _gravite * _masse 
 +  
 + _velocite = move_and_slide_with_snap( 
 + _velocite, 
 + Vector2.ZERO, # Accroche au sol 
 + Vector2.UP, # Direction du sol ( Vector2.UP ) 
 + true, # Ne glisse pas sur le sol en cas d'inactivité ? 
 + 4, # Nombre de collisions traitées par cycle 
 + 0.9, # Angle maximum du sol ( en radians ) 
 + false # Inertie infinie ? 
 +
 + 
 +# Appui sur le bouton de saut 
 +func entreeSaut(): 
 + # Mécanisme de saut 
 + if is_on_floor(): 
 + if Input.is_action_just_pressed("espace"): 
 + _velocite.y -= _forceSaut 
 + 
 +# Appui sur les boutons clavier de déplacement 
 +func bouge(): 
 + if Input.is_action_just_pressed("gauche"): 
 + _direction = -1 
 + elif Input.is_action_just_pressed("droite"): 
 + _direction = 1 
 +  
 + if Input.is_action_just_released("gauche"): 
 + if not Input.is_action_pressed("droite"): 
 + _direction = 0 
 + else: 
 + _direction = 1 
 +  
 + if Input.is_action_just_released("droite"): 
 + if not Input.is_action_pressed("gauche"): 
 + _direction = 0 
 + else: 
 + _direction = -1 
 +</code> 
 + 
 + 
 +====Ajout d'une inertie latérale==== 
 +Il y a maintenant nécessité pour le //Joueur// de **se mettre en mouvement et d'augmenter sa vitesse avant d'atteindre le maximum. Celui-ci met également un peu de temps à s'arrêter sur le sol. Cette décélération est absente lorsqu'il est en l'air.** Pour l'instant, être collé à un mur permet tout-de-même d'augmenter sa vitesse : cela sera réglé lors de l'appel à //is_on_wall()//
 + 
 +<code> 
 +extends KinematicBody2D 
 + 
 +var _gravite = 50 
 + 
 +var _masse = 1 
 +var _forceSaut = 350 
 +var _vitesse = 300 
 +var _acceleration = 20 
 +var _deceleration = 10 
 + 
 +var _direction = 0 
 +var _vitesseActuelle = 0 
 + 
 +var _velocite = Vector2.ZERO 
 + 
 +func _ready(): 
 + if get_tree().root.has_node("Global"): 
 + _gravite = Global.gravite 
 + 
 +func _physics_process( _delta ): 
 +  
 + bouge() 
 + entreeSaut() 
 + traitementDuMouvement() 
 +  
 + # Intégration du déplacement latéral 
 + _velocite.x = _vitesseActuelle 
 + # Intégration de la gravité 
 + _velocite.y += _gravite * _masse 
 +  
 + _velocite = move_and_slide_with_snap( 
 + _velocite, 
 + Vector2.ZERO, # Accroche au sol 
 + Vector2.UP, # Direction du sol ( Vector2.UP ) 
 + true, # Ne glisse pas sur le sol en cas d'inactivité ? 
 + 4, # Nombre de collisions traitées par cycle 
 + 0.9, # Angle maximum du sol ( en radians ) 
 + false # Inertie infinie ? 
 +
 + 
 +# Appui sur le bouton de saut 
 +func entreeSaut(): 
 + # Mécanisme de saut 
 + if is_on_floor(): 
 + if Input.is_action_just_pressed("espace"): 
 + _velocite.y = _forceSaut 
 + 
 +# Appui sur les boutons clavier de déplacement 
 +func bouge(): 
 + if Input.is_action_just_pressed("gauche"): 
 + _direction = -1 
 + elif Input.is_action_just_pressed("droite"): 
 + _direction = 1 
 +  
 + if Input.is_action_just_released("gauche"): 
 + if not Input.is_action_pressed("droite"): 
 + _direction = 0 
 + else: 
 + _direction = 1 
 +  
 + if Input.is_action_just_released("droite"): 
 + if not Input.is_action_pressed("gauche"): 
 + _direction = 0 
 + else: 
 + _direction = -1 
 + 
 +# Gestion des accélérations/décélérations 
 +func traitementDuMouvement(): 
 +# Vélocité horizontale 
 + if _direction != 0: 
 + _vitesseActuelle += _acceleration * _direction 
 +  
 + # Restriction aux vitesses max 
 + if _vitesseActuelle < 0: 
 + if _vitesseActuelle < -_vitesse: 
 + _vitesseActuelle = -_vitesse 
 + else: 
 + if _vitesseActuelle > _vitesse: 
 + _vitesseActuelle = _vitesse 
 + else: 
 + # Décélération au sol 
 + if is_on_floor(): 
 + if _vitesseActuelle < 0: 
 + if _vitesseActuelle < -_deceleration: 
 + _vitesseActuelle += _deceleration 
 + else: 
 + _vitesseActuelle = 0 
 + elif _vitesseActuelle > 0: 
 + if _vitesseActuelle > _deceleration: 
 + _vitesseActuelle -= _deceleration 
 + else: 
 + _vitesseActuelle = 0 
 +</code> 
 + 
 +====Affinage de l'algorithme de saut==== 
 +On ajoute deux mécanismes pour rendre les sauts plus agréables. 
 + 
 +Premièrement, **l'interruption d'un saut en relâchant la barre espace multiplie la vélocité par un ratio inférieur à 1 (ici 0.6) si celle-ci est négative, une fois par saut**. Le joueur peut donc sauter plus ou moins en haut en fonction du moment où il relâche le bouton. C'est notamment utilisé lors des passages de plate-formes où il y a des piques au-dessus du joueur. 
 + 
 +Deuxièmement, **la gravité contraire appliquée à la phase ascendante du saut est modifiée par un ratio qui peut nier tout ou partie de la gravité**. Au lieu d'un saut en cloche, la phase ascendant peut donc être plus rapide que la phase descendante. 
 + 
 +<code> 
 +extends KinematicBody2D 
 + 
 +var _gravite = 50 
 + 
 +var _masse = 2 
 +var _forceSaut = 2000 
 +var _vitesse = 300 
 +var _acceleration = 20 
 +var _deceleration = 10 
 +var _ratioDInterruptionDuSaut = 0.5 
 +var _ratioDGraviteSaut = 0.75 
 + 
 +var _direction = 0 
 +var _vitesseActuelle = 0 
 +var _forceSautActuelle = 0 
 + 
 +var _velocite = Vector2.ZERO 
 +var _saute = false 
 + 
 +func _ready(): 
 + if get_tree().root.has_node("Global"): 
 + _gravite = Global.gravite 
 + 
 +func _physics_process( _delta ): 
 +  
 + bouge() 
 + entreeSaut() 
 + if _saute: 
 + if _velocite.y < 0: 
 + interruptionDuSaut() 
 + traitementDuMouvement() 
 +  
 + # Intégration du déplacement latéral 
 + _velocite.x = _vitesseActuelle 
 + # Intégration de la gravité 
 + if _velocite.y < 0: 
 + _velocite.y += _gravite * _masse * _ratioDGraviteSaut 
 + else: 
 + _velocite.y += _gravite * _masse 
 +  
 + _velocite = move_and_slide_with_snap( 
 + _velocite, 
 + Vector2.ZERO, # Accroche au sol 
 + Vector2.UP, # Direction du sol ( Vector2.UP ) 
 + true, # Ne glisse pas sur le sol en cas d'inactivité ? 
 + 4, # Nombre de collisions traitées par cycle 
 + 0.9, # Angle maximum du sol ( en radians ) 
 + false # Inertie infinie ? 
 +
 + 
 +# Appui sur le bouton de saut 
 +func entreeSaut(): 
 + # Mécanisme de saut 
 + if is_on_floor(): 
 + if Input.is_action_just_pressed("espace"): 
 + _velocite.y -= _forceSaut 
 + _saute = true 
 + 
 +# Gestion de l'interruption du saut 
 +func interruptionDuSaut(): 
 + if Input.is_action_just_released("espace"): 
 + _velocite.y *= _ratioDInterruptionDuSaut 
 + _saute = false 
 + 
 +# Appui sur les boutons clavier de déplacement 
 +func bouge(): 
 + if Input.is_action_just_pressed("gauche"): 
 + _direction = -1 
 + elif Input.is_action_just_pressed("droite"): 
 + _direction = 1 
 +  
 + if Input.is_action_just_released("gauche"): 
 + if not Input.is_action_pressed("droite"): 
 + _direction = 0 
 + else: 
 + _direction = 1 
 +  
 + if Input.is_action_just_released("droite"): 
 + if not Input.is_action_pressed("gauche"): 
 + _direction = 0 
 + else: 
 + _direction = -1 
 + 
 +# Gestion des accélérations/décélérations 
 +func traitementDuMouvement(): 
 +# Vélocité horizontale 
 + if _direction != 0: 
 + _vitesseActuelle += _acceleration * _direction 
 +  
 + # Restriction aux vitesses max 
 + if _vitesseActuelle < 0: 
 + if _vitesseActuelle < -_vitesse: 
 + _vitesseActuelle = -_vitesse 
 + else: 
 + if _vitesseActuelle > _vitesse: 
 + _vitesseActuelle = _vitesse 
 + else: 
 + # Décélération au sol 
 + if is_on_floor(): 
 + if _vitesseActuelle < 0: 
 + if _vitesseActuelle < -_deceleration: 
 + _vitesseActuelle += _deceleration 
 + else: 
 + _vitesseActuelle = 0 
 + elif _vitesseActuelle > 0: 
 + if _vitesseActuelle > _deceleration: 
 + _vitesseActuelle -= _deceleration 
 + else: 
 + _vitesseActuelle = 0 
 +</code> 
 + 
 +====Ajout d'une Camera2D==== 
 + 
 +**L'ajout d'un nœud //Camera2D// permet de suivre facilement les déplacement du personnage.** 
 + 
 +À l'aide de l'inspecteur, j'ai simplement réglé son //offset// à [ 0, - 150 ], et coché la propriété //Current//
 + 
 +C'est suffisant pour le prototypage. 
 + 
 +====Changement de direction du personnage==== 
 + 
 +L'algorithme est tout bête, il suffit d'**inverser l'échelle du //Sprite// sur l'axe X lors de l'appel à la fonction //bouge()//** . Attention cependant, comme j'ai opté pour un déplacement spécifique lors de l'utilisation du clavier, à savoir de pouvoir reculer sans changer de sens en appuyant sur //Gauche// et //Droite// en même temps, le retournement ne se fait pas dans ce cas-ci. 
 + 
 +<code> 
 +# Appui sur les boutons clavier de déplacement 
 +func bouge(): 
 + if Input.is_action_just_pressed("gauche"): 
 + _direction = -1 
 + if not Input.is_action_pressed("droite"): 
 + $Sprite.set_scale( Vector2( -1, 1 ) ) 
 + elif Input.is_action_just_pressed("droite"): 
 + _direction = 1 
 + if not Input.is_action_pressed("gauche"): 
 + $Sprite.set_scale( Vector2( 1, 1 ) ) 
 + if Input.is_action_just_released("gauche"): 
 + if not Input.is_action_pressed("droite"): 
 + _direction = 0 
 + else: 
 + _direction = 1 
 + $Sprite.set_scale( Vector2( 1, 1 ) ) 
 +  
 + if Input.is_action_just_released("droite"): 
 + if not Input.is_action_pressed("gauche"): 
 + _direction = 0 
 + else: 
 + _direction = -1 
 + $Sprite.set_scale( Vector2( -1, 1 ) ) 
 +</code> 
 + 
 + 
 +====Animation du personnage==== 
 + 
 +Il ne manque à ce stade plus que d'**animer le personnage**. J'ai pour ce faire créé quatre //sprites// très simple. Le résultat n'est pas fabuleux mais permet de comprendre comment faire. Voici les texture utilisées : 
 + 
 +{{ ::dm_quatrelutins.png?357 |}} 
 + 
 +J'ai commencé par **ajouter un nœud //AnimationPlayer//** à la scène, auquel j'ai rajouté **3 pistes, nommées //repos//, // saut//, et //marche//**. Pour chacune d'elles, j'ai simplement **modifié la texture** du //Sprite//. //repos// et //saut// ne consistent qu'en une seule clef, //marche// boucle sur 4 textures. 
 + 
 +J'ai activé **la //lecture automatique au chargement// de l'animation //repos//**, et activé **la //lecture en boucle// de l'animation //marche//**. 
 + 
 +**J'ai dû rajouter un nœud //RayCast2D// pour que l'animation du saut se fasse correctement.** 
 + 
 +Ma première idée était d'utiliser //is_on_floor()// pour détecter si le personnage était en train de sauter. Cependant, **lorsque le //KinematicBody2D// glisse le long d'une pente, il lui arrive de se décoller légèrement lorsque que la pente ascendante passe sur un plan horizontal**. Dans ce cas, //is_on_floor()// passe à //false// et déclenche l'animation, ce qui n'est pas joli à voir. 
 + 
 +À la place, j'ai mis un //RayCast2D// sous les pieds du //Joueur//, dont la propriété //Cast To// a été réglée à Vector2( 0, 20 ). J'ai coché le paramètre //enabled// pour l'activer, et changer le //masque de collision//
 + 
 +Cependant, cela pose le problème inverse lorsque que le personnage passe une plate-forme à sens unique en sautant. Alors qu'il est sensé être en saut, le //RayCast2D// détecte la plate-forme et lance l'animation. Il faut donc, pendant le saut, désactiver le //RayCast2D//
 + 
 +Le tout est ensuite appelé au sein d'une fonction dédiée : 
 + 
 +<code> 
 +func _physics_process( _delta ): 
 + [...] 
 + animer() 
 + 
 +[...] 
 + 
 +# Appui sur le bouton de saut 
 +func entreeSaut(): 
 + # Mécanisme de saut 
 + if is_on_floor(): 
 + if not $RayCast2D.enabled: 
 + $RayCast2D.set_enabled( true ) 
 + if Input.is_action_just_pressed("espace"): 
 + _velocite.y -= _forceSaut 
 + _saute = true 
 + $RayCast2D.set_enabled( false ) 
 + 
 + 
 +[...] 
 +# Animation du personnage : 
 +func animer(): 
 + # Vérifier si le personnage est au sol : 
 + if $RayCast2D.enabled: 
 + if $RayCast2D.is_colliding(): 
 + # S'il bouge 
 + if _velocite.x != 0: 
 + $AnimationPlayer.play("marche"
 + # Sinon 
 + else: 
 + $AnimationPlayer.play("repos"
 + else: 
 + $AnimationPlayer.play("saut"
 +</code> 
 + 
 + 
 +====Contrôle du personnage à la manette==== 
 + 
 +Avant de terminer, je souhaite simplement que l'on puisse également **contrôler le personnage à l'aide d'une manette**. 
 + 
 +Dans cette optique, je décide de changer la fonction //bouge()// affin qu'elle se comporte différemment en fonction d'un contrôle au clavier ou à la manette. Trois types de contrôles sont possibles, à la manette, au clavier, et au clavier et à la souris. Cependant, le déplacement du personnage étant restreint à la main gauche au clavier, il sera le même pour les deux derniers modes. 
 + 
 +Je rajoute donc **un paramètre d'entrée et de zone morte pour le **joystick**, et change l'algorithme de déplacement** : 
 + 
 +<code> 
 +extends KinematicBody2D 
 + 
 +var _typeEntree = "clavier" 
 +var _zoneMorte = 0.3 
 + 
 +var _gravite = 50 
 + 
 +var _masse = 2 
 +var _forceSaut = 2000 
 +var _vitesse = 400 
 +var _acceleration = 20 
 +var _deceleration = 10 
 +var _ratioDInterruptionDuSaut = 0.5 
 +var _ratioDGraviteSaut = 0.75 
 + 
 +var _direction = 0 
 +var _vitesseActuelle = 0 
 +var _forceSautActuelle = 0 
 + 
 +var _velocite = Vector2.ZERO 
 +var _saute = false 
 + 
 +func _ready(): 
 + if get_tree().root.has_node("Global"): 
 + _typeEntree = Global.typeEntree 
 + _zoneMorte = Global.zoneMorte 
 + _gravite = Global.gravite 
 + 
 +func _physics_process( _delta ): 
 +  
 + bouge() 
 + entreeSaut() 
 + if _saute: 
 + if _velocite.y < 0: 
 + interruptionDuSaut() 
 + traitementDuMouvement() 
 +  
 + # Intégration du déplacement latéral 
 + _velocite.x = _vitesseActuelle 
 + # Intégration de la gravité 
 + if _velocite.y < 0: 
 + _velocite.y += _gravite * _masse * _ratioDGraviteSaut 
 + else: 
 + _velocite.y += _gravite * _masse 
 +  
 + _velocite = move_and_slide_with_snap( 
 + _velocite, 
 + Vector2.ZERO, # Accroche au sol 
 + Vector2.UP, # Direction du sol ( Vector2.UP ) 
 + true, # Ne glisse pas sur le sol en cas d'inactivité ? 
 + 4, # Nombre de collisions traitées par cycle 
 + 0.9, # Angle maximum du sol ( en radians ) 
 + false # Inertie infinie ? 
 +
 +  
 + animer() 
 + 
 +# Appui sur le bouton de saut 
 +func entreeSaut(): 
 + # Mécanisme de saut 
 + if is_on_floor(): 
 + if not $RayCast2D.enabled: 
 + $RayCast2D.set_enabled( true ) 
 + if Input.is_action_just_pressed("espace"): 
 + _velocite.y -= _forceSaut 
 + _saute = true 
 + $RayCast2D.set_enabled( false ) 
 + 
 +# Gestion de l'interruption du saut 
 +func interruptionDuSaut(): 
 + if Input.is_action_just_released("espace"): 
 + _velocite.y *= _ratioDInterruptionDuSaut 
 + _saute = false 
 + 
 +# Appui sur les boutons clavier de déplacement 
 +func bouge(): 
 + if _typeEntree == "manette": 
 + var entree = Input.get_axis("gauche", "droite"
 + if entree < -_zoneMorte: 
 + _direction = -1 
 + $Sprite.set_scale( Vector2( -1, 1 ) ) 
 + elif entree > _zoneMorte: 
 + _direction = 1 
 + $Sprite.set_scale( Vector2( 1, 1 ) ) 
 + else: 
 + _direction = 0 
 + else: 
 + if Input.is_action_just_pressed("gauche"): 
 + _direction = -1 
 + if not Input.is_action_pressed("droite"): 
 + $Sprite.set_scale( Vector2( -1, 1 ) ) 
 + elif Input.is_action_just_pressed("droite"): 
 + _direction = 1 
 + if not Input.is_action_pressed("gauche"): 
 + $Sprite.set_scale( Vector2( 1, 1 ) ) 
 + if Input.is_action_just_released("gauche"): 
 + if not Input.is_action_pressed("droite"): 
 + _direction = 0 
 + else: 
 + _direction = 1 
 + $Sprite.set_scale( Vector2( 1, 1 ) ) 
 +  
 + if Input.is_action_just_released("droite"): 
 + if not Input.is_action_pressed("gauche"): 
 + _direction = 0 
 + else: 
 + _direction = -1 
 + $Sprite.set_scale( Vector2( -1, 1 ) ) 
 + 
 +# Gestion des accélérations/décélérations 
 +func traitementDuMouvement(): 
 +# Vélocité horizontale 
 + if _direction != 0: 
 + _vitesseActuelle += _acceleration * _direction 
 +  
 + # Restriction aux vitesses max 
 + if _vitesseActuelle < 0: 
 + if _vitesseActuelle < -_vitesse: 
 + _vitesseActuelle = -_vitesse 
 + else: 
 + if _vitesseActuelle > _vitesse: 
 + _vitesseActuelle = _vitesse 
 + else: 
 + # Décélération au sol 
 + if is_on_floor(): 
 + if _vitesseActuelle < 0: 
 + if _vitesseActuelle < -_deceleration: 
 + _vitesseActuelle += _deceleration 
 + else: 
 + _vitesseActuelle = 0 
 + elif _vitesseActuelle > 0: 
 + if _vitesseActuelle > _deceleration: 
 + _vitesseActuelle -= _deceleration 
 + else: 
 + _vitesseActuelle = 0 
 + 
 +# Animation du personnage : 
 +func animer(): 
 + # Vérifier si le personnage est au sol : 
 + if $RayCast2D.enabled: 
 + if $RayCast2D.is_colliding(): 
 + # S'il bouge 
 + if _velocite.x != 0: 
 + $AnimationPlayer.play("marche"
 + # Sinon 
 + else: 
 + $AnimationPlayer.play("repos"
 + else: 
 + $AnimationPlayer.play("saut"
 +</code> 
 + 
 + 
 + 
 +====Notes de fin==== 
 + 
 +Et voilà pour cette première étape de personnage. Je décide de m'arrêter ici car **nous avons un personnage jouable pour un jeu simple**. En fait nous avons quasiment le personnage de //Super Mario Bros 3// : celui-ci se déplace latéralement, saute, est affecté par l'inertie, et rentre en collision avec l'environnement. 
 + 
 +Il pourra servir de base pour des jeux n'ayant pas besoin de contrôles supplémentaires.
  
-À  la place**c'est la méthode //move_and_collide()// qui doit être utilisée**. **Lorsque cette méthode est appelée avec la vélocité du joueur en argument, celle-ci ne déplace pas le personnage automatiquement. À la place, elle renvoie le vecteur qui correspond au déplacement théorique du personnage.** En analysant celui-ci, il devient alors possible de déduire l'orientation de la //hitbox// que le personnage vient de toucher, et donc de différencier le sol, les murs, le plafond ou encore une pente. Plus avant, comme nous devons ensuite appeler la méthode //slide()// pour déplacer le personnage en fonction de ce vecteur, il devient possible d'implémenter soi-même la réponse à la collision. Par exemple, si un mur a été détecté, nous pouvons arrêter le personnage si celui-ci a un objet 'crochet', ou le faire glisser en fonction de la gravité le cas échéant, comme dans le jeu //Hollow Knight//.+Au cas oùvoici à quoi ressemble ma scène dans l'éditeur :
  
-====Mise-en-place du système de collision====+{{ ::dm_joueursimpleediteur.jpg?600 |}}
  
-**[EN CONSTRUCTION]**+Notez que je n'ai pas renommé les noeuds (sauf le parent) pour que vous les identifiiez plus facilement ici, mais qu'il est tout-de-même conseillé de les renommer lors de ses projets propres.
  
 +Je créé par ailleurs **une copie autonome de ce //Joueur// dans le dossier //src/JoueurSimple/// **, en stockant l'ensemble des ressources à l'intérieur même du dossier afin de faciliter l'importation de la scène dans d'autres projets. La dépendance à //Global// n'étant pas forte, le seul paramétrage à faire est d'associer les bonnes entrées utilisateurs dans les //Paramètres du projet//.
 {{tag>godot dindomoteur jeu vidéo}} {{tag>godot dindomoteur jeu vidéo}}
dindomoteur_creation_du_joueur.1647778007.txt.gz · Dernière modification : 2022/03/20 12:06 de Simon Deplat