Mecánicas para implementar un juego de plataformas en el motor Godot. Parte 2

Hola, esta es una continuación del artículo anterior sobre la creación de un personaje jugable en GodotEngine. Finalmente descubrí cómo implementar algunas de las mecánicas como un segundo salto en el aire, trepar y saltar de la pared. La primera parte fue más simple en términos de saturación, ya que era necesario comenzar con algo para luego refinarlo o rehacerlo.



Enlaces a artículos anteriores



Para empezar, decidí recopilar todo el código anterior para que quienes usaron la información del artículo anterior entendieran cómo me imaginaba el programa en su totalidad:



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


Espero que quienes hayan leído el artículo anterior hayan entendido aproximadamente cómo funciona todo. Ahora volvamos al desarrollo.



Máquina estatal



La máquina de estado (a mi entender) es una parte del programa que determina el estado de algo: en el aire, en el piso, en el techo o en la pared, y también determina lo que debería sucederle al personaje en tal o cual lugar. GodotEngine tiene algo como enum, que crea una enumeración, donde cada elemento es una constante especificada en el código. Creo que será mejor que muestre esto con un ejemplo:



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


Este código se puede poner de forma segura al principio del guión del personaje del juego y tener en cuenta que existe. A continuación, inicializamos la variable en el lugar correcto var current_state: int = States.IN_AIR, que es igual a cero si usamos print. A continuación, debe determinar de alguna manera qué hará el jugador en el estado actual. Creo que muchos desarrolladores experimentados que vinieron de C ++ están familiarizados con la construcción switch () {case:}. GDScript tiene una construcción adaptada similar, aunque el cambio también está en la agenda. La construcción se llama partido.



Creo que sería más correcto mostrar esta construcción en la práctica, ya que será más difícil de decir que de mostrar:



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


Pero todavía no cambiamos el estado. Necesitamos crear una función separada, que llamaremos antes de la coincidencia, para cambiar la variable current_state, que debe agregarse al código al resto de las variables. Y llamaremos a la función 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


Ahora que la máquina de estado está lista, podemos agregar un montón de funciones. Incluyendo agregar animaciones al personaje ... Ni siquiera eso ... Podemos agregar un montón de animaciones al personaje. El sistema se ha vuelto modular. Pero no hemos terminado con el código aquí. Dije al principio que te mostraría cómo hacer un salto extra en el aire, trepar y saltar de la pared. Empecemos por orden.



Salto extra en el aire



Primero, agregue la llamada de salto en el estado States.IN_AIR a nuestra coincidencia, que modificaremos un poco.



Aquí está nuestro código de salto que arreglé:



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


Los comentarios al código básicamente dicen cómo cambié el programa, espero que entiendas mis palabras allí. Pero, de hecho, estas correcciones son suficientes para cambiar la mecánica de salto y mejorar al doble. Me tomó un par de meses inventar los siguientes métodos. De hecho, las escribí anteayer, 1 de octubre de 2020.



Escalando las paredes



Desafortunadamente para nosotros, el GodotEngine Wall Normal no nos permite saberlo, lo que significa que tendremos que crear una pequeña muleta. Para empezar, haré una nota al pie de las variables disponibles actualmente para que pueda saber fácilmente qué ha cambiado.



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


Ahora debes determinar qué pared está escalando el jugador.



Aquí está el árbol de la escena que debe preparar para implementar el calificador del lado de la pared:



imagen



Coloque 2 Area2D a los lados del personaje y CollisionShape2D no deben superponerse con el personaje. Firme los objetos WallLeft / WallRight de forma adecuada y adjunte las señales _on_body_endered y _on_body_exited a un único script de carácter. Aquí está el código que se necesita para definir las paredes (agregar al final del 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 #        -   


Empecemos por el método de escalada. El código dirá todo por mi

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


Y necesitamos reescribir el control move_character () para que no solo podamos aguantar, sino subir y bajar, ya que tenemos dirección:



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


Y arreglamos nuestro _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))


El personaje ahora debería poder escalar paredes.



Saltar de la pared



Ahora implementemos el salto desde la pared.



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


Agregamos una llamada a este método a nuestra coincidencia -> States.ON_WALL y adjuntamos nuestro método al resto de _physics_process ().



Conclusión



En este artículo, he mostrado la implementación de mecánicas relativamente complejas (para principiantes) en GodotEngine. Pero esta no es la última parte de una serie de artículos, por lo que les pediría a aquellos que saben cómo implementar los métodos mostrados por mí en este artículo que escriban sobre ellos en los comentarios. Yo, y muchos lectores, agradeceremos las soluciones rápidas y de alta calidad.



All Articles