在Godot引擎上实现平台游戏的机制。第2部分

您好,这是上一篇有关在GodotEngine中创建可玩角色的文章的续篇。我终于弄清楚了如何实现某些机制,例如空中第二跳,爬上山墙和从墙上跳下来。第一部分在饱和度方面更简单,因为有必要从某些内容开始,以便稍后对其进行完善或重做。



链接到以前的文章



首先,我决定收集所有以前的代码,以便那些使用上一篇文章中的信息的人将理解我如何完整地想象该程序:



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


我希望那些阅读上一篇文章的人大致了解所有工作原理。现在让我们回到开发。



状态机



状态机(据我理解)是程序的一部分,它确定事物的状态:在空中,在地板上,在天花板上或在墙壁上,并且还确定角色在一个地方或另一个地方应该发生什么。GodotEngine具有诸如enum之类的东西,它创建一个枚举,其中每个元素都是代码中指定的常量。我想我最好用一个例子来说明这一点:



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


该代码可以安全地放在游戏角色脚本的开头,并记住它的存在。接下来,在正确的位置var current_state初始化变量:int = States.IN_AIR,如果使用print,则该变量等于零。接下来,您需要以某种方式确定播放器在当前状态下的操作。我认为许多来自C ++的有经验的开发人员都熟悉switch(){case:}的构造。 GDScript具有类似的适应结构,尽管切换也已列入议程。该构造称为匹配。



我认为在实践中展示这种构造会更正确,因为要讲起来比展示起来更加困难:



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


但是我们仍然不会改变状态。我们需要创建一个单独的函数,在匹配之前将调用该函数,以更改变量current_state,该变量应添​​加到代码中的其余变量中。我们将调用函数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


现在状态机已经准备就绪,我们可以添加大量功能。包括向角色添加动画...甚至还不...我们可以向角色添加大量动画。该系统已经模块化。但是我们还没有完成这里的代码。我在一开始就说过,我将向您展示如何在空中进行额外的跳跃,爬升并从墙上跳下来。让我们按顺序开始。



空中跳跃



首先,将States.IN_AIR状态中的跳转调用添加到我们的匹配项中,我们将对其进行一些调整。



这是我修复的跳转代码:



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


代码注释基本上说明了我如何更改程序,希望您在那里理解我的话。但实际上,这些修补程序足以改变跳跃机制并提高为两倍。我花了几个月的时间发明了以下方法。我实际上是在2020年10月1日的前一天写的。



爬墙



对于我们来说不幸的是,GodotEngine Wall Normal不允许我们知道,这意味着我们将不得不创建一个小型拐杖。首先,我将对当前可用的变量做一个脚注,以便您可以轻松知道发生了什么变化。



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


现在您需要确定玩家正在爬哪堵墙。



这是为实施墙侧限定符而准备的场景树,



图片



在角色的侧面放置2个Area2D,并且CollisionShape2D都不应与角色相交。适当签署WallLeft / WallRight对象,并将_on_body_endered和_on_body_exited信号附加到单个字符脚本。这是定义墙所需的代码(添加到脚本的最后):



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


让我们从攀登方法开始。代码会为我说一切

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


并且我们需要重写move_character()控件,以便我们不仅可以坚持,而且可以上下爬,因为我们有方向:



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


然后我们修复了_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))


角色现在应该可以爬墙了。



跳下墙



现在,让我们实现从墙跳下。



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


我们在match-> States.ON_WALL中添加对此方法的调用,并将方法附加到_physics_process()的其余部分。



结论



在本文中,我展示了GodotEngine中相对复杂的机制(对于初学者)的实现。但这不是系列文章的最后一部分,所以我问那些知道如何实现本文中显示的方法的人最好在评论中写下它们。我和许多读者将感谢您提供高质量,快速的解决方案。



All Articles