Mecânica para a implementação de um jogo de plataforma no motor Godot. Parte 2

Olá, esta é uma continuação do artigo anterior sobre a criação de um personagem jogável em GodotEngine. Finalmente descobri como implementar algumas das mecânicas, como um segundo salto no ar, escalar e pular da parede. A primeira parte era mais simples em termos de saturação, pois era preciso começar com algo para refinar ou refazer depois.



Links para artigos anteriores



Para começar, decidi coletar todo o código anterior para que aqueles que usaram as informações do artigo anterior entendessem como imaginei o programa em sua totalidade:



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 aqueles que leram o artigo anterior tenham entendido aproximadamente como tudo funciona. Agora vamos voltar ao desenvolvimento.



Máquina de estado



A máquina de estados (no meu entendimento) é uma parte do programa que determina o estado de algo: no ar, no chão, no teto ou na parede, e também determina o que deve acontecer com o personagem em um lugar ou outro. GodotEngine tem algo como enum, que cria uma enumeração, onde cada elemento é uma constante especificada no código. Acho melhor mostrar isso com um exemplo:



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


Este código pode ser colocado com segurança bem no início do script do personagem do jogo e tenha em mente que ele existe. Em seguida, inicializamos a variável no lugar certo var current_state: int = States.IN_AIR, que é igual a zero se usarmos print. Em seguida, você precisa determinar de alguma forma o que o player fará no estado atual. Acho que muitos desenvolvedores experientes que vieram do C ++ estão familiarizados com a construção switch () {case:}. GDScript tem uma construção adaptada semelhante, embora switch também esteja na agenda. A construção é chamada de jogo.



Acho que seria mais correto mostrar essa construção na prática, pois será mais difícil dizer do que 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()
	#    


Mas ainda não mudamos o estado. Precisamos criar uma função separada, que chamaremos antes da partida, para alterar a variável current_state, que deve ser adicionada ao código para o resto das variáveis. E vamos chamar a função 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


Agora que a máquina de estado está pronta, podemos adicionar uma tonelada de funções. Incluindo adicionar animações ao personagem ... Nem isso ... Podemos adicionar uma tonelada de animações ao personagem. O sistema tornou-se modular. Mas ainda não terminamos com o código aqui. Eu disse no início que iria mostrar como dar um salto extra no ar, subir e pular da parede. Vamos começar em ordem.



Salto adicional no ar



Primeiro, adicione a chamada de salto no estado States.IN_AIR à nossa correspondência, que iremos ajustar um pouco.



Aqui está o código do nosso salto que consertei:



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


Os comentários ao código basicamente dizem como mudei o programa, espero que você entenda minhas palavras aqui. Mas, na verdade, essas correções são suficientes para mudar a mecânica do salto e melhorar para dobrar. Levei alguns meses para inventar os métodos a seguir. Na verdade, eu os escrevi anteontem, 1º de outubro de 2020.



Escalando as paredes



Infelizmente para nós, o GodotEngine Wall Normal não nos permite saber, o que significa que teremos que criar uma pequena muleta. Para começar, farei uma nota de rodapé das variáveis ​​atualmente disponíveis para que você possa saber facilmente o que mudou.



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


Agora você precisa determinar em qual parede o jogador está escalando.



Aqui está a árvore de cena que você deve preparar para implementar o qualificador do lado da parede:



imagem



Coloque 2 Area2Ds nas laterais do personagem e CollisionShape2D ambos não devem se sobrepor ao personagem. Assine os objetos WallLeft / WallRight apropriadamente e anexe os sinais _on_body_endered e _on_body_exited a um script de caractere único. Aqui está o código necessário para definir as paredes (adicionar no final do 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 #        -   


Vamos começar com o método de escalada. O código vai dizer tudo para mim

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


E precisamos reescrever o controle move_character () para que possamos não apenas segurar, mas subir e descer, já que temos direção:



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


E corrigimos nosso _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))


O personagem agora deve ser capaz de escalar paredes.



Pule da parede



Agora vamos implementar o salto da parede.



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


Adicionamos uma chamada a este método para nossa correspondência -> States.ON_WALL e anexamos nosso método ao resto do _physics_process ().



Conclusão



Neste artigo, mostrei a implementação de mecânicas relativamente complexas (para iniciantes) no GodotEngine. Mas esta não é a última parte de uma série de artigos, então peço àqueles que sabem como implementar os métodos que mostrei neste artigo que escrevam sobre eles nos comentários. Eu, e muitos leitores, seremos gratos por soluções rápidas e de alta qualidade.



All Articles