Mekanika untuk mengimplementasikan platformer pada mesin Godot. Bagian 2

Halo, ini adalah kelanjutan dari artikel sebelumnya tentang membuat karakter yang dapat dimainkan di GodotEngine. Saya akhirnya menemukan cara untuk menerapkan beberapa mekanisme seperti lompatan kedua di udara, memanjat, dan melompat dari dinding. Bagian pertama lebih sederhana dalam hal saturasi, karena itu perlu dimulai dengan sesuatu untuk menyempurnakan atau mengulangnya nanti.



Tautan ke artikel sebelumnya



Untuk memulainya, saya memutuskan untuk mengumpulkan semua kode sebelumnya sehingga mereka yang menggunakan informasi dari artikel sebelumnya akan mengerti bagaimana saya membayangkan program ini secara keseluruhan:



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


Saya harap mereka yang telah membaca artikel sebelumnya kurang lebih memahami bagaimana semuanya bekerja. Sekarang mari kembali ke pengembangan.



Mesin negara



Mesin keadaan (dalam pengertian saya) adalah bagian dari program yang menentukan keadaan sesuatu: di udara, di lantai, di langit-langit, atau di dinding, dan juga menentukan apa yang harus terjadi pada karakter di tempat ini atau itu. GodotEngine memiliki hal seperti enum, yang membuat enumerasi, di mana setiap elemen adalah konstanta yang ditentukan dalam kode. Saya pikir lebih baik saya menunjukkan ini dengan contoh:



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


Kode ini dapat ditempatkan dengan aman di awal skrip karakter permainan dan perlu diingat bahwa kode itu ada. Selanjutnya, kita menginisialisasi variabel di tempat yang benar var current_state: int = States.IN_AIR, yang sama dengan nol jika kita menggunakan print. Selanjutnya, Anda perlu menentukan apa yang akan dilakukan pemain dalam keadaan saat ini. Saya rasa banyak pengembang berpengalaman yang berasal dari C ++ yang akrab dengan konstruksi switch () {case:}. GDScript memiliki konstruksi adaptasi yang serupa, meskipun switch juga ada dalam agenda. Konstruksinya disebut korek api.



Saya pikir akan lebih tepat untuk menunjukkan konstruksi ini dalam praktiknya, karena akan lebih sulit untuk mengatakannya daripada menunjukkan:



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


Tapi kami tetap tidak mengubah keadaan. Kita perlu membuat fungsi terpisah, yang akan kita panggil sebelum pertandingan, untuk mengubah variabel current_state, yang harus ditambahkan ke kode ke variabel lainnya. Dan kami akan memanggil fungsi 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


Sekarang mesin negara sudah siap, kita bisa menambahkan banyak fungsi. Termasuk menambahkan animasi ke karakter ... Bahkan tidak ... Kita bisa menambahkan banyak animasi ke karakter. Sistem telah menjadi modular. Tapi kami belum selesai dengan kode di sini. Saya berkata di awal bahwa saya akan menunjukkan kepada Anda bagaimana melakukan lompatan ekstra di udara, memanjat, dan melompat dari tembok. Mari kita mulai secara berurutan.



Lompatan tambahan di udara



Pertama, tambahkan jump call di status States.IN_AIR ke kecocokan kita, yang akan kita ubah sedikit.



Berikut adalah kode lompatan kami yang saya perbaiki:



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


Komentar pada kode pada dasarnya mengatakan bagaimana saya mengubah program, saya harap Anda memahami kata-kata saya di sana. Namun nyatanya, perbaikan ini cukup untuk mengubah mekanisme lompatan dan meningkatkannya menjadi dua kali lipat. Saya membutuhkan beberapa bulan untuk menemukan metode berikut. Sebenarnya saya menulisnya sehari sebelum kemarin, 1 Oktober 2020.



Memanjat tembok



Sayangnya bagi kami, GodotEngine Wall Normal tidak mengizinkan kami untuk mengetahuinya, yang berarti kami harus membuat kruk kecil. Untuk memulainya, saya akan membuat catatan kaki dari variabel yang tersedia saat ini sehingga Anda dapat dengan mudah mengetahui apa yang telah berubah.



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


Sekarang Anda perlu menentukan dinding mana yang sedang dipanjat pemain.



Berikut adalah pohon adegan yang harus Anda persiapkan untuk mengimplementasikan kualifikasi sisi dinding.



gambar



Tempatkan 2 Area2D di sisi karakter dan CollisionShape2D keduanya tidak boleh tumpang tindih dengan karakter. Tanda tangani objek WallLeft / WallRight dengan benar dan lampirkan sinyal _on_body_endered dan _on_body_exited ke satu skrip karakter. Berikut adalah kode yang diperlukan untuk menentukan dinding (Tambahkan ke bagian paling akhir skrip):



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


Mari kita mulai dengan metode pendakian. Kode akan mengatakan segalanya untuk saya

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


Dan kita perlu menulis ulang kontrol move_character () sehingga kita tidak bisa hanya bertahan, tetapi naik dan turun, karena kita memiliki arah:



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


Dan kami memperbaiki _physics_process () kami:



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


Karakter sekarang harus bisa memanjat dinding.



Lompat dari tembok



Sekarang mari kita terapkan lompatan dari dinding.



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


Kami menambahkan panggilan ke metode ini ke pertandingan kami -> States.ON_WALL dan kami melampirkan metode kami ke sisa _physics_process ().



Kesimpulan



Pada artikel ini, saya telah menunjukkan implementasi mekanika yang relatif kompleks (untuk pemula) di GodotEngine. Tapi ini bukan bagian terakhir dari rangkaian artikel, jadi saya meminta mereka yang tahu bagaimana menerapkan metode yang saya tunjukkan di artikel ini lebih baik menulis tentang mereka di komentar. Saya, dan banyak pembaca, akan berterima kasih atas solusi cepat dan berkualitas tinggi.



All Articles