Outils pour utilisateurs

Outils du site


dindomoteur_creation_du_joueur

Ceci est une ancienne révision du document !


DindoMoteur : Création du Joueur

Contexte

Cette page présente les différentes étapes de la création d'un nœud Joueur pour le DindoMoteur.

Création visuelle d'un joueur et d'une plate-forme

La première étape est d'avoir un simple joueur contrôlable ainsi qu'une plate-forme sur lequel celui-ci puisse se mouvoir.

Normalement, nous pourrions supposer que cette plate-forme est l'objet d'un autre article du wiki, mais il ne fait pas sens d'avoir un joueur sans environnement pour l'intégrer, nous mettons donc en place un environnement rudimentaire pour tester celui-ci.

Dans un premier temps, je créé deux répertoire dans src/ : Joueur/ et Niveau/ .

Je remarque que dans l'exemple Platformer, il y a un répertoire Platform/ dédié aux plate-formes. Nous verrons en temps voulu si c'est pertinent, commençons simplement pour l'instant.

Avant toute logique de déplacement et de collision, je souhaite simplement que ces deux objets soient affichés à l'écran. J'utilise donc Inkscape pour créer deux sprites qui leurs correspondent.

En raison de l'unité de référence que nous avons choisi, la plate-forme de test fera 512 x 64 pixels.

Pour l'instant, je créerai un dossier assets/Dev/ qui contiendra les ressources temporaires liées au développement.

Voici les deux sprites utilisés pour l'instant :

Une fois les sprites réalisés, je crée donc les scènes associées :

  • pour le Joueur, un Kinematic Body 2D (ce choix est discuté à la section suivante).
  • pour la plate-forme, un Static Body 2D .

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”.

Notes à propos de la méthode de collision

Après quelques recherches, nous sommes arrivés à plusieurs conclusions concernant le système de collisions liées au Joueur.

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, voici l'article dédié.

Voici la documentation du système de collision que nous utilisons.

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.

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é

Le joueur aura donc une masse et sera affecté par la gravité :

extends KinematicBody2D

var _gravite = Global.gravite

var _masse = 1

var _velocite = Vector2.ZERO

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 ?
	)

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

extends KinematicBody2D

var _gravite = Global.gravite

var _masse = 1
var _forceSaut = 350

var _forceSautActuelle = 0

var _velocite = Vector2.ZERO

func _physics_process( _delta ):
	
	traitementDuSaut()
	entreeSaut()
	
	# Intégration de la gravité
	_velocite.y += _gravite * _masse
	# Intégration de la vélocité de saut
	_velocite.y -= _forceSautActuelle
	
	_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"):
			_forceSautActuelle = _forceSaut

# Gestion de la mécanique de saut
func traitementDuSaut():
	#Si le Joueur est au sol, mais a une vélocité de saut,
	if is_on_floor() and _forceSautActuelle > 0:
		# Remise à zéro de sa vélocité de saut
		_forceSautActuelle = 0
	# Si le joueur n'est pas sur le sol mais saute
	if not is_on_floor() and _forceSautActuelle > 0:
		# Décroît de la vélocité de saut
		_forceSautActuelle -= _gravite * _masse
		# Remise à zéro si la vélocité de saut devient négative
		if _forceSautActuelle < 0:
			_forceSautActuelle = 0

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.

extends KinematicBody2D

var _gravite = Global.gravite

var _masse = 1
var _forceSaut = 350
var _vitesse = 300

var _direction = 0
var _forceSautActuelle = 0

var _velocite = Vector2.ZERO

func _physics_process( _delta ):
	
	traitementDuSaut()
	bouge()
	entreeSaut()
	
	# Intégration du déplacement latéral
	_velocite.x = _direction * _vitesse
	# Intégration de la gravité
	_velocite.y += _gravite * _masse
	# Intégration de la vélocité de saut
	_velocite.y -= _forceSautActuelle
	
	_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"):
			_forceSautActuelle = _forceSaut

# Gestion de la mécanique de saut
func traitementDuSaut():
	#Si le Joueur est au sol, mais a une vélocité de saut,
	if is_on_floor() and _forceSautActuelle > 0:
		# Remise à zéro de sa vélocité de saut
		_forceSautActuelle = 0
	# Si le joueur n'est pas sur le sol mais saute
	if not is_on_floor() and _forceSautActuelle > 0:
		# Décroît de la vélocité de saut
		_forceSautActuelle -= _gravite * _masse
		# Remise à zéro si la vélocité de saut devient négative
		if _forceSautActuelle < 0:
			_forceSautActuelle = 0

# 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

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().

Personnage.gd

class_name Personnage
extends KinematicBody2D

var masse = 1
var gravite = Global.gravite
var forceSaut = 1
var vitesse = 1

var acceleration = 20
var deceleration = 20

var forceSautActuelle = 0
var vitesseActuelle = 0

var _velocite = Vector2.ZERO

# la fonction _physics_process héritée est appelée
# après la fonction _physics_process parente
func _physics_process( delta ):
	_velocite.y += gravite * masse * delta
	
	#Si le Joueur est au sol, et s'il est en train de sauter,
	if is_on_floor() and forceSautActuelle > 0:
		# Remise à zéro de sa vélocité de saut
		forceSautActuelle = 0
	# Si le joueur n'est pas sur le sol mais saute
	if not is_on_floor() and forceSautActuelle > 0:
		# Décroît de la vélocité de saut
		forceSautActuelle = forceSautActuelle - ( gravite * masse * delta )
		# Remise à zéro si la vélocité de saut devient négative
		if forceSautActuelle < 0:
			forceSautActuelle = 0

Joueur.gd

class_name Joueur
extends Personnage

var _direction = 0

func _ready():
	masse = 100
	forceSaut = 14000
	vitesse = 400

func _physics_process( _delta ):
	
	# Vérification des entrées utilisateurs pour l'axe horizontal
	bouge()
	
	# 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
	
	_velocite.x = vitesseActuelle
	
	# Mécanisme de saut
	if is_on_floor():
		if Input.is_action_just_pressed("espace"):
			forceSautActuelle = forceSaut
	
	# Si le personnage a encore une vélocité de saut
	if forceSautActuelle > 0:
		# Compensation de la gravité
		_velocite.y -= forceSautActuelle * _delta
	
	_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 ?
	)

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

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é du saut par un ration inférieur à 1 (ici 0.6). 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.

[EN CONSTRUCTION]

dindomoteur_creation_du_joueur.1647800570.txt.gz · Dernière modification : 2022/03/20 18:22 de Simon Deplat