Mécanique d'implémentation d'un platformer sur le moteur Godot. Partie 2

Bonjour, ceci est la suite de l'article précédent sur la création d'un personnage jouable dans GodotEngine. J'ai finalement compris comment mettre en œuvre certains des mécanismes comme un deuxième saut en l'air, grimper et sauter du mur. La première partie était plus simple en termes de saturation, car il fallait commencer par quelque chose afin de l'affiner ou de le refaire plus tard.



Liens vers les articles précédents



Pour commencer, j'ai décidé de rassembler tout le code précédent pour que ceux qui utilisaient les informations de l'article précédent comprennent comment j'imaginais le programme dans son intégralité:



extends KinematicBody2D

# 
const GRAVITY: int = 40
const MOVE_SPEED: int = 120 #     
const JUMP_POWER: int = 80 #  

# 
var velocity: Vector2 = Vector2.ZERO

func _physics_process(_delta: float) -> void:
	#     
	move_character() #  
	jump()
	#     
	self.velocity.y += GRAVITY
	self.velocity = self.move_and_slide(self.velocity, Vector2(0, -1))

func move_character() -> void:
	var direction: float = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") 
	self.velocity.x = direction * MOVE_SPEED

func jump() -> void:
	if self.is_on_floor():
		if Input.is_action_pressed("ui_accept"): #     ui_accept
			#      
			self.velocity.y -= JUMP_POWER


J'espère que ceux qui ont lu l'article précédent ont compris approximativement comment tout fonctionne. Revenons maintenant au développement.



Machine d'état



La machine à états (à ma connaissance) fait partie du programme qui détermine l'état de quelque chose: dans l'air, sur le sol, au plafond ou sur le mur, et détermine également ce qui devrait arriver au personnage dans tel ou tel endroit. GodotEngine a une chose telle que enum, qui crée une énumération, où chaque élément est une constante spécifiée dans le code. Je pense que je ferais mieux de montrer cela avec un exemple:



enum States { #   States,       States.IN_AIR, States.ON_FLOOR...
	IN_AIR, #  
	ON_FLOOR, #   
	ON_WALL #  
}


Ce code peut être placé en toute sécurité au tout début du script du personnage du jeu et garder à l'esprit qu'il existe. Ensuite, nous initialisons la variable au bon endroit var current_state: int = States.IN_AIR, qui est égal à zéro si nous utilisons print. Ensuite, vous devez déterminer d'une manière ou d'une autre ce que le joueur fera dans l'état actuel. Je pense que de nombreux développeurs expérimentés issus du C ++ sont familiers avec la construction switch () {case:}. GDScript a une construction adaptée similaire, bien que le changement soit également à l'ordre du jour. La construction s'appelle match.



Je pense qu'il serait plus correct de montrer cette construction en pratique, car il sera plus difficile de dire que de montrer:



func _physics_process(_delta: float) -> void:
	#   
	match (self.current_state):
		States.IN_AIR:
			#      .
			self.move_character()
		States.ON_FLOOR:
			#  ,    .
			self.move_character()
			self.jump()
		States.ON_WALL:
			#  ,  ,      .     .
			self.move_character()
	#    


Mais nous ne changeons toujours pas l'état. Nous devons créer une fonction séparée, que nous appellerons avant la correspondance, afin de changer la variable current_state, qui devrait être ajoutée au code au reste des variables. Et nous appellerons la fonction update_state ().



func update_state() -> void:
	#        .
	if self.is_on_floor():
		self.current_state = self.States.ON_FLOOR
	elif self.is_on_wall() and !self.is_on_floor():
		#     .
		self.current_state = self.States.ON_WALL
	elif self.is_on_wall() and self.is_on_floor():
		#  .      .
		self.current_state = self.States.ON_WALL
	else: #       
		self.current_state = self.states.IN_AIR


Maintenant que la machine d'état est prête, nous pouvons ajouter une tonne de fonctions. Y compris l'ajout d'animations au personnage ... Pas même ça ... Nous pouvons ajouter une tonne d'animations au personnage. Le système est devenu modulaire. Mais nous n'en avons pas fini avec le code ici. J'ai dit au début que je vous montrerais comment faire un saut supplémentaire en l'air, grimper et sauter du mur. Commençons dans l'ordre.



Saut supplémentaire en l'air



Tout d'abord, ajoutez l'appel de saut dans l'état States.IN_AIR à notre correspondance, que nous peaufinerons un peu.



Voici notre code de saut que j'ai corrigé:



func jump() -> void:
	#    .    .
	if Input.is_action_pressed("ui_accept"): #    
		if self.current_state == self.States.ON_FLOOR:
			#  ,     _
			self.velocity.y -= JUMP_POWER
		elif (self.current_state == self.States.IN_AIR or self.current_state == self.States.ON_WALL)
				and self.second_jump == true:
				#             
			self.velocity.y = -JUMP_POWER
			#       
			self.second_jump = false
			#    var second_jump: bool = true   .   update_state()
			#   if self.is_on_floor(): self.second_jump = true #        .


Dans les commentaires sur le code, en principe, il est dit comment j'ai changé le programme, j'espère que vous comprenez mes mots là-bas. Mais en fait, ces correctifs suffisent à changer la mécanique du saut et à s'améliorer pour doubler. Il m'a fallu quelques mois pour inventer les méthodes suivantes. En fait, je les ai écrits avant-hier, le 1er octobre 2020.



Grimper les murs



Malheureusement pour nous, le GodotEngine Wall Normal ne nous permet pas de le savoir, ce qui signifie qu'il va falloir créer une petite béquille. Pour commencer, je ferai une note de bas de page des variables actuellement disponibles afin que vous puissiez facilement savoir ce qui a changé.



extends KinematicBody2D

# 
signal timer_ended #     yield  wall_jump,     .
# 
const GRAVITY: int = 40
const MOVE_SPEED: int = 120 #     
const JUMP_POWER: int = 80 #  
const WALL_JUMP_POWER: int = 60 #    .    
const CLIMB_SPEED: int = 30 #  

# 
var velocity: Vector2 = Vector2.ZERO
var second_jump: bool = true
var climbing: bool = false #   ,     ,  .
var timer_working: bool = false
var is_wall_jump: bool = false # ,  ,      
var left_pressed: bool = false #     
var right_pressed: bool = false #     
var current_state: int = States.IN_AIR
var timer: float = 0 #  ,     _process(delta: float)
var walls = [false, false, false] #    .    - .  - .
#      
# 
enum States {
	IN_AIR, #  
	ON_FLOOR, #   
	ON_WALL #  
}
#       ,   _process()   
func _process(delta: float):
	if timer_working:
		timer -= delta
	if timer <= 0:
		emit_signal("timer_ended")
		timer = 0


Vous devez maintenant déterminer sur quel mur le joueur grimpe.



Voici l'arborescence de scènes que vous devez préparer pour implémenter le qualificatif côté mur.



image



Placez 2 Area2D sur les côtés du personnage et CollisionShape2D ne doit pas se croiser avec le personnage. Signez les objets WallLeft / WallRight de manière appropriée et associez les signaux _on_body_endered et _on_body_exited à un script à un seul caractère. Voici le code nécessaire pour définir les murs (ajouter à la toute fin du script):



#     
#  ,     
func _on_WallRight_body_entered(_body):
	if (_body.name != self.name):
		self.walls[0] = true #     ,   - 

func _on_WallRight_body_exited(_body):
	self.walls[0] = false #        -   

func _on_WallLeft_body_entered(_body):
	if (_body.name != self.name):
		self.walls[2] = true #     ,   - 

func _on_WallLeft_body_exited(_body):
	self.walls[2] = false #        -   


Commençons par la méthode d'escalade. Le code dira tout pour moi

func climbing() -> void:
	if (self.walls[0] or self.walls[2]): #       
		#   action     ui_climb.        .
		self.climbing = Input.is_action_pressed("ui_climb")
	else:
		self.climbing = false


Et nous devons réécrire le contrôle move_character () afin que nous ne puissions pas simplement tenir le coup, mais monter et descendre, car nous avons la direction:



func move_character() -> void:
	var direction: float = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") 
	if !self.climbing:
		self.velocity.x = direction * MOVE_SPEED
	else:
		self.velocity.y = direction * CLIMB_SPEED


Et nous corrigeons notre _physics_process ():



func _physics_process(_delta: float) -> void:
	#   
	match (self.current_state):
		States.IN_AIR:
			self.move_character()
		States.ON_FLOOR:
			self.move_character()
			self.jump()
		States.ON_WALL:
			self.move_character()
	#     
	if !self.climbing:
		self.velocity.y += GRAVITY
	self.velocity = self.move_and_slide(self.velocity, Vector2(0, -1))


Le personnage devrait désormais pouvoir escalader les murs.



Saute du mur



Maintenant, implémentons le saut du mur.



func wall_jump() -> void:
	if Input.is_action_just_pressed("ui_accept") and Input.is_action_pressed("ui_climb"): 
		#   1       
		self.is_wall_jump = true #     = 
		self.velocity.y = -JUMP_POWER #    -JUMP_POWER
		if walls[0]: #   
			self.timer = 0.5 #  self.timer  0.5 
			self.timer_enabled = true #  
			self.left_pressed = true #   left_pressed  
			yield(self, "timer_ended") #    timer_ended
			self.left_pressed = false #  left_pressed
		if walls[2]: #   
			self.timer = 0.5 #  self.timer  0.5 
			self.timer_enabled = true #  
			self.right_pressed = true #   right_pressed  
			yield(self, "timer_ended") #    timer_ended
			self.right_pressed = false #  right_pressed
		self.is_wall_jump = false # .    


Nous ajoutons un appel à cette méthode à notre match -> States.ON_WALL et nous avons attaché notre méthode au reste du _physics_process ().



Conclusion



Dans cet article, j'ai montré l'implémentation de mécanismes relativement complexes (pour les débutants) dans GodotEngine. Mais ce n'est pas la dernière partie d'une série d'articles, alors je demande à ceux qui savent comment mettre en œuvre les méthodes que j'ai montrées dans cet article qu'il vaut mieux écrire à leur sujet dans les commentaires. De nombreux lecteurs et moi-même serons reconnaissants pour des solutions rapides et de haute qualité.



All Articles