概述
当我们初学时,很多教程都教授我们使用类似is_jumping、is_air之类的布尔类型的变量表示玩家是否处于某个状态,然后在_process()或_physics_process()中每帧去侦测按键输入,然后再根据输入去改变上面的状态变量或通过判断执行相应的行为。
然而当使用这种简单粗暴的方式,为了实现玩家不同的状态和行为,我们或许会声明十几二十个这样的变量,并用各种if语句和嵌套的if语句把自己的代码搞得一团乱麻。
为此,我们引入新的方式来重构我们的代码。
原始代码
为了比较效果,这里我们还是贴上上一节基础平台移动和跳跃的代码。
原始代码
extends KinematicBody2D
var speed = 60.0 # 速度 像素/秒
var velocity = Vector2.ZERO # 速度向量
var gravity = 3000 # 重力加速度
var jump_force = 3000.0 # 单次跳跃高度
var is_jumping = false
func _process(delta):
velocity = Vector2.ZERO
# 左右移动
velocity.x = Input.get_axis("ui_left","ui_right") * speed
# 实现下落
velocity.y += gravity * delta
# 落地
if is_on_floor() and is_jumping == true:
is_jumping = false
return
# 跳跃
if Input.is_action_just_pressed("ui_accept"):
if is_on_floor(): # 在地面
velocity.y = -jump_force
is_jumping = true
else: # 在空中
if is_jumping: # 正在跳跃
# 二级跳
velocity.y = -jump_force
is_jumping = false
velocity = move_and_slide(velocity,Vector2.UP)
重构代码
然后我们对上面的代码进行重构。
原始代码
extends KinematicBody2D
var speed = 60.0 # 速度 像素/秒
var velocity = Vector2.ZERO # 速度向量
var gravity = 3000 # 重力加速度
var jump_force = 3000.0 # 单次跳跃高度
var is_jumping = false
enum states {
ON_FLOOR, # 在地面上
IN_AIR # 在空中
}
var _state = states.ON_FLOOR
func _physics_process(delta):
velocity = Vector2.ZERO
match _state:
# 在地面
states.ON_FLOOR:
# 边缘掉落
if not is_on_floor():
change_state_to(states.ON_FLOOR)
is_jumping = false
return
# 左右移动
velocity.x = Input.get_axis("ui_left","ui_right") * speed
# 实现下落 -- 用于边缘下落
velocity.y += gravity * delta
# 跳跃
if Input.is_action_just_pressed("ui_accept"):
velocity.y = -jump_force
change_state_to(states.IN_AIR)
is_jumping = true
velocity = move_and_slide(velocity,Vector2.UP)
# 在空中
states.IN_AIR:
# 掉到地板
if is_on_floor():
change_state_to(states.ON_FLOOR)
is_jumping = false
return
# 左右移动
velocity.x = Input.get_axis("ui_left","ui_right") * speed
# 实现下落 -- 用于边缘下落
velocity.y += gravity * delta
# 跳跃 -- 二级跳
if Input.is_action_just_pressed("ui_accept") and is_jumping:
velocity.y = -jump_force
change_state_to(states.IN_AIR)
is_jumping = false
velocity = move_and_slide(velocity,Vector2.UP)
pass
# 更改状态
func change_state_to(new_state:int):
_state = new_state
# 根据不同的状态,进行一些额外配置
match _state:
states.IN_AIR:
pass
states.ON_FLOOR:
pass
代码分析
我们声明一个枚举states,然后书写两个表示在地面和空中的常量ONFLOOR和IN_AIR。然后声明_state变量存储当前玩家所处的状态,然后在_physics_process()中用match语句去检测_state变量当前的值,也就是玩家当前的状态,然后再根据具体的状态去进行相应的每帧处理。
可以看到,貌似我们这样写,代码比原来还多了,而且有相当一部分的代码重复,这是因为之前的写法共用了两个状态都能用的代码,而现在为了进行不同状态代码之间的完全区分,或者叫状态隔离。我们必须牺牲一些复用性。
这样写的好处是,当我们继续实现诸如爬墙、爬梯子或其他什么功能和状态时,我们可以继续简单的扩充我们的状态枚举states,然后轻松的用match语句的一个分支来进行这些状态下的处理。而不是再搞一堆is打头的变量,并嵌套各种if语句。
这是一个很好的进步,你可以用这种方式重构你的玩家或NPC的代码,精确而又无痛的处理它们的状态和行为。而这种状态隔离处理的思维,也是有限状态机的基础。
另外,我们还将给state变量赋值,封装为一个函数change_state_to()。这样一方面可以呈现更明显的语义,另一方面在其中我们利用另一个match语句,可以在状态切换后的瞬间对各个状态进行一定的统一配置。
当然我们并没有完全消灭is打头的变量,我们保留了is_jumping变量 ,通过和IN_AIR状态搭配,可以用来区分玩家是第一次跳还是第二次跳,从而实现二级跳的效果。
运行效果
运行效果基本上与上一节的没有区别。此处就不再展示。