Mechanik zur Implementierung eines Plattformers auf der Godot-Engine. Teil 2

Hallo, dies ist eine Fortsetzung des vorherigen Artikels zum Erstellen eines spielbaren Charakters in GodotEngine. Endlich fand ich heraus, wie man einige der Mechaniken wie einen zweiten Sprung in die Luft, Klettern und Springen von der Wand umsetzt. Der erste Teil war in Bezug auf die Sättigung einfacher, da es notwendig war, mit etwas zu beginnen, um es später zu verfeinern oder zu wiederholen.



Links zu früheren Artikeln



Zunächst habe ich beschlossen, den gesamten vorherigen Code zu sammeln, damit diejenigen, die die Informationen aus dem vorherigen Artikel verwendet haben, verstehen, wie ich mir das Programm in seiner Gesamtheit vorgestellt habe:



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


Ich hoffe, diejenigen, die den vorherigen Artikel gelesen haben, haben ungefähr verstanden, wie alles funktioniert. Kommen wir nun zur Entwicklung zurück.



Zustandsmaschine



Die Zustandsmaschine (nach meinem Verständnis) ist Teil des Programms, das den Zustand von etwas bestimmt: in der Luft, auf dem Boden, an der Decke oder an der Wand und auch, was mit dem Charakter an dem einen oder anderen Ort geschehen soll. GodotEngine hat so etwas wie eine Aufzählung, die eine Aufzählung erstellt, wobei jedes Element eine im Code angegebene Konstante ist. Ich denke, ich sollte dies besser anhand eines Beispiels zeigen:



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


Dieser Code kann sicher ganz am Anfang des Skripts des Spielcharakters eingefügt werden und berücksichtigt, dass er vorhanden ist. Als nächstes initialisieren wir die Variable an der richtigen Stelle var current_state: int = States.IN_AIR, was bei Verwendung von print gleich Null ist. Als nächstes müssen Sie irgendwie bestimmen, was der Spieler im aktuellen Zustand tun wird. Ich denke, viele erfahrene Entwickler, die aus C ++ kamen, sind mit der Konstruktion von switch () {case:} vertraut. GDScript hat ein ähnlich angepasstes Konstrukt, obwohl der Wechsel ebenfalls auf der Tagesordnung steht. Die Konstruktion heißt Match.



Ich denke, es wäre richtiger, diese Konstruktion in der Praxis zu zeigen, da es schwieriger zu sagen sein wird als zu zeigen:



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


Aber wir ändern den Zustand immer noch nicht. Wir müssen eine separate Funktion erstellen, die wir vor dem Match aufrufen, um die Variable current_state zu ändern, die dem Code zu den restlichen Variablen hinzugefügt werden soll. Und wir rufen die Funktion update_state () auf.



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


Jetzt, da die Zustandsmaschine bereit ist, können wir eine Menge Funktionen hinzufügen. Einschließlich des Hinzufügens von Animationen zum Charakter ... Nicht einmal das ... Wir können dem Charakter eine Menge Animationen hinzufügen. Das System ist modular aufgebaut. Aber wir sind hier nicht mit dem Code fertig. Ich sagte am Anfang, dass ich dir zeigen würde, wie man einen zusätzlichen Sprung in die Luft macht, hochklettert und von der Wand springt. Beginnen wir in der richtigen Reihenfolge.



Zusätzlicher Sprung in die Luft



Fügen Sie zuerst den Sprungaufruf im Status States.IN_AIR zu unserem Match hinzu, den wir ein wenig optimieren werden.



Hier ist unser Sprungcode, den ich behoben habe:



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


Die Kommentare zum Code sagen im Grunde, wie ich das Programm geändert habe. Ich hoffe, Sie verstehen meine Worte dort. Tatsächlich reichen diese Korrekturen jedoch aus, um die Sprungmechanik zu ändern und zu verdoppeln. Ich habe ein paar Monate gebraucht, um die folgenden Methoden zu erfinden. Ich habe sie tatsächlich vorgestern, am 1. Oktober 2020, geschrieben.



Die Wände hochklettern



Leider lässt uns die GodotEngine Wall Normal nicht wissen, was bedeutet, dass wir eine kleine Krücke bauen müssen. Zunächst mache ich eine Fußnote zu den derzeit verfügbaren Variablen, damit Sie leicht erkennen können, was sich geändert hat.



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


Jetzt müssen Sie bestimmen, auf welche Wand der Spieler klettert.



Hier ist der



Bild



Szenenbaum , den Sie vorbereiten sollten, um das Qualifikationsmerkmal für die Wandseite zu implementieren: Platzieren Sie 2 Area2Ds an den Seiten des Charakters und CollisionShape2D dürfen sich nicht mit dem Charakter überschneiden. Signieren Sie die WallLeft / WallRight-Objekte entsprechend und hängen Sie die Signale _on_body_endered und _on_body_exited an ein einzelnes Zeichenskript an. Hier ist der Code, der zum Definieren der Wände benötigt wird (am Ende des Skripts hinzufügen):



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


Beginnen wir mit der Klettermethode. Der Code sagt alles für mich

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


Und wir müssen das move_character () - Steuerelement neu schreiben, damit wir nicht nur festhalten, sondern auf und ab klettern können, da wir die Richtung haben:



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


Und wir reparieren unseren _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))


Der Charakter sollte nun in der Lage sein, Wände zu besteigen.



Von der Wand springen



Lassen Sie uns nun den Sprung von der Wand ausführen.



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


Wir fügen dieser Methode einen Aufruf zu unserer Übereinstimmung hinzu -> States.ON_WALL und hängen unsere Methode an den Rest des _physics_process () an.



Fazit



In diesem Artikel habe ich die Implementierung relativ komplexer Mechaniken (für Anfänger) in GodotEngine gezeigt. Dies ist jedoch nicht der letzte Teil einer Reihe von Artikeln. Deshalb bitte ich diejenigen, die wissen, wie man die in diesem Artikel gezeigten Methoden implementiert, besser, in den Kommentaren darüber zu schreiben. Ich und viele Leser werden für hochwertige und schnelle Lösungen dankbar sein.



All Articles