[Godot] – character – 有限状态机

Introduction:

来自timothyqiu的勇者传说教程

状态机,用于便于管理人物的各种状态,包括人物之间状态的切换。

例如,你平常吃了睡,睡了吃就是两个状态,睡 <-> 吃。并且这两个状态之间恰好是可以相互转换的,吃饱了进入到睡的状态,睡醒了进入吃的状态。

StateMachine

使用状态机的模型,首先需要定义一个通用的状态机管理脚本。这个脚本是一个通用的脚本,便于组装到所有的角色上。

项目结构
### 脚本: StateMachine.gd ###

class_name  StateMachine
extends Node

var current_state: int = -1:						# 开始先设置为-1专门表示起始值
	set(v):											# 每次为current_state 赋值的时候都会执行set(v)函数
		owner.transition_state(current_state, v)	# 拥有状态机脚本的角色将会执行状态的切换函数
		# transition_state() 需要在角色中进行定义
		current_state = v							# 更新状态的值
		
func _ready() -> void:
	await owner.ready								# 开始先等待角色初始化函数执行完毕
	current_state = 0								# 默认直接进入第一个状态
	
func _physics_process(delta: float) -> void:
	while  true:
		var next := owner.get_next_state(current_state) as int	# 每次都会调用定义的函数获取下一个状态
		if current_state == next:								# 如果状态不变,直接跳出
			break
		current_state = next									# 否则就进入到下一个状态
		
	owner.tick_physics(current_state, delta)					# 执行角色的物理函数

可以发现,该函数需要角色(owner)拥有三个定义函数进行支持,下面来定义这三个函数:

### 脚本: Player.gd ### 

extends CharacterBody2D
# 玩家脚本
# 首先顶一个一个状态枚举: [默认, 跑步, 跳跃, 坠落]
enum State {	
	IDLE,
	RUNNING,
	JUMP,
	FALL,
}
const GROUND_STATE := [State.IDLE, State.RUNNING]	# 地面状态的集合 
const RUN_SPEED := 160.0
const JUMP_VELOCITY := -400.0
const FLOOR_ACCELERATION := RUN_SPEED / 0.2
const AIR_ACCELERATION := RUN_SPEED / 0.02
var default_gravity := ProjectSettings.get("physics/2d/default_gravity") as float	# 默认下的重力
var is_first_tick:bool = false														# 是否处于第一帧
@onready var animation_player = $AnimationPlayer
@onready var sprite_2d = $Sprite2D
@onready var coyote_timer: Timer = $"../CoyoteTimer"
@onready var jump_request_timer: Timer = $"../JumpRequestTimer"


func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("jump"):
		jump_request_timer.start()
	if event.is_action_released("jump") and velocity.y < JUMP_VELOCITY / 2:
		velocity.y = JUMP_VELOCITY / 2


func tick_physics(state: State, delta: float):	# 每一帧都要执行的玩家物理逻辑函数
	match state:
		State.IDLE:
			move(default_gravity, delta)
		State.RUNNING:
			move(default_gravity, delta)
		State.JUMP:
			move(0.0 if is_first_tick else default_gravity, delta)	 # 如果第一帧取消重力否则下落
		State.FALL:
			move(default_gravity, delta)

	is_first_tick = false						# 第一帧结束后取消第一帧的布尔

func move(gravity: float, delta: float) -> void:
	var direction := Input.get_axis("move_left", "move_right")	# 获取左右输入
	var acceleration := FLOOR_ACCELERATION if is_on_floor() else AIR_ACCELERATION	# 选择重力
	velocity.x = move_toward(velocity.x, direction * RUN_SPEED, acceleration * delta)	# 根据不同重力进行移动
	velocity.y += gravity * delta	# 获得重力
	
	if not is_zero_approx(direction):	# 如果有左右方向的输入
		sprite_2d.flip_h = direction < 0	# 根据方向进行反转		
	move_and_slide()	# 进行物理碰撞检测的移动

func get_next_state(state: State) ->State:	# 根据输入获得下一个状态
	var can_jump := is_on_floor() or coyote_timer.time_left > 0	# 在地面上或者处在coyoteTimer的时间内就允许跳跃
	var should_jump := can_jump and jump_request_timer.time_left > 0 # 允许跳跃并且已经按下了跳跃键就应该跳跃
	if should_jump:	# 应该跳跃进入跳跃状态
		return State.JUMP
	############### 以下为不应该跳跃的情况 #########################
	var direction := Input.get_axis("move_left", "move_right")	# 获取水平输入
	var is_still := is_zero_approx(direction) and is_zero_approx(velocity.x)	# 如果水平静止

	match  state:
		State.IDLE:	# 在静止状态中
			if not is_on_floor():
				return State.FALL	# 不在地面进入坠落状态
			if not is_still:
				return State.RUNNING	# 不静止进入跑步状态

		State.RUNNING:	# 在跑步状态中
			if is_still:	# 回到静止就进入待机状态
				return State.IDLE
			if not is_on_floor():	# 不在地面就进入坠落状态
				return State.FALL
				
		State.JUMP:			# 在跳跃状态中判断下一次状态应该的值
			if velocity.y >= 0:	# 如果速度变为向下,进入坠落状态
				return State.FALL

		State.FALL:	# 在坠落状态中
			if is_on_floor():	# 坠落回地面进入跑步或静止状态
				return State.IDLE if is_still else State.RUNNING	# 根据是否静止进入跑步或者待机状态
	return state # 最后返回得到的状态

func transition_state(from: State, to: State) -> void:
	if from not in GROUND_STATE and to in GROUND_STATE:
		### 如果是从陆地以外的状态进入到陆地状态就停止胶囊时间CoyoteTimer ###
		coyote_timer.stop()	
	match  to:	# 根据目标状态更新当前的属性
		State.IDLE:	# 如果要去到 IDLE 状态就切换回 IDLE 应有的动画
			animation_player.play("idle")
		State.RUNNING:	# 如果需要进入 RUNNING 就切换到 running 的动画
			animation_player.play("running")
		State.JUMP:	# 进入到 JUMP 切换到 jump 动画并且给一个向上的初速度
			animation_player.play("jump")
			velocity.y = JUMP_VELOCITY
			coyote_timer.stop()	# 停止胶囊时间
			jump_request_timer.stop()	# 停止跳跃延迟时间
		State.FALL:	# 需要进入坠落状态播放坠落动画
			animation_player.play("fall")
			if from in GROUND_STATE:	# 如果是从地面状态进入到坠落状态就启动胶囊时间
				coyote_timer.start()
	is_first_tick = true	# 设置当前是第一帧
	##########################################################################
	# 当进入一个状态时运行完一次后设置为是第一帧,并在物理函数中运动过一次后设置为是第一帧 #
	##########################################################################

根据以上代码可以发现,如果想要运用状态机,只需要准备一个StateMachine脚本,继承自Node。

将StateMachine组装到Player类场景的节点树下,并在玩家中定义三个行为函数即可。

  • transition_state(current_state, v)
  • get_next_state(current_state)
  • tick_physics(current_state, delta)
作者:Sy_
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇