limbo hsm

This commit is contained in:
2026-10-15 16:56:45 +08:00
parent 18e7b2daa8
commit a76c4aebef
30 changed files with 665 additions and 121 deletions
+17
View File
@@ -0,0 +1,17 @@
extends LimboHSM
## 空中子机(挂在 HSM/Airborne 上)。
##
## Jump(上升段)→ Fall(越过顶点开始下落)。进入时按当前竖直速度挑初始子状态:
## 起跳进来 vy > 0 → Jump;踏空掉进来 vy ≈ 0 → Fall(vy ≤ 0 一律算 Fall)。
@onready var jump: LimboState = $Jump
@onready var fall: LimboState = $Fall
func _setup() -> void:
add_transition(jump, fall, &"falling")
func _enter() -> void:
var player := get_agent() as Player
set_initial_state(fall if player == null or player.velocity.y <= 0.0 else jump)
+1
View File
@@ -0,0 +1 @@
uid://43psuo6pb4wq
+18
View File
@@ -0,0 +1,18 @@
extends PlayerGroundState
## 原地蹲:目标速度 0,播 crouch(蹲着不动)。
## 蹲着走是 Sneak —— 两者只差「有没有方向输入」,转换事件同为 move_start/stop。
func _enter() -> void:
var player := get_agent() as Player
player.motor.target_speed = 0.0
player.motor.sprinting = false
player.motor.crouching = true
player.animation_player.play_state(PlayerAnimator.AnimState.CROUCH)
func _think(_delta: float) -> void:
var player := get_agent() as Player
if not Input.is_action_pressed("crouch"):
dispatch(&"crouch_end")
elif player.motor.has_move_input():
dispatch(&"move_start")
+1
View File
@@ -0,0 +1 @@
uid://bb8dvx7nwuiwo
+14
View File
@@ -0,0 +1,14 @@
extends LimboState
## 下落段:播 fall。落地(is_on_floor 且不再上升)转回地面,由 Grounded._enter
## 按当下输入挑子状态(走/跑/蹲着落地不会闪站立)。
func _enter() -> void:
(get_agent() as Player).animation_player.play_state(PlayerAnimator.AnimState.FALL)
func _update(_delta: float) -> void:
var player := get_agent() as Player
if player == null:
return
if player.is_on_floor() and player.velocity.y <= 0.0:
dispatch(&"landed")
+1
View File
@@ -0,0 +1 @@
uid://yadts2ydgfs6
+32
View File
@@ -0,0 +1,32 @@
class_name PlayerGroundState
extends LimboState
## 地面叶子状态的公共基类(Idle / Walk / Sprint / Crouch / Sneak 共用)。
##
## 每帧先统一判定「环境事实」,之后才轮到 _think() 里各状态自己的输入转换:
## 1. 跳跃缓冲成立(try_jump 当场起跳)→ 转空中(vy > 0 → Jump);
## 2. 脚下没地板(踏空)→ 转空中(Fall)。
## 这两层判定放在**叶子**而不是父机:父机 _update 先于子状态执行,
## 若在父机里 dispatch,转换后父机本轮还会继续调(已被退出的)旧子状态,
## 存在「旧状态在已退出的机器里再次 dispatch」的顺序陷阱;叶子自己判、
## dispatch 后立即 return,则没有这个窗口。
##
## is_on_floor() 反映的是上一帧 move_and_slide() 的结果 ——
## 与推进的更新节奏一致,不引入额外的检测延迟。
func _update(delta: float) -> void:
var player := get_agent() as Player
if player == null:
return
if player.motor.try_jump():
dispatch(&"airborne")
return
if not player.is_on_floor():
dispatch(&"airborne")
return
_think(delta)
## 各状态自己的输入转换。要不要 dispatch 事件、往哪转,由具体状态决定
## (转换表注册在 grounded.gd 的 _setup 里)。
func _think(_delta: float) -> void:
pass
+1
View File
@@ -0,0 +1 @@
uid://dss42q7c4kqui
+45
View File
@@ -0,0 +1,45 @@
extends LimboHSM
## 地面子机(挂在 HSM/Grounded 上)。
##
## 五个叶子状态:
## Idle 站立不动 Walk 步行 Sprint 疾跑 Crouch 原地蹲 Sneak 蹲走
## 叶子各自的输入转换见 ground_state.gd 及各状态脚本;本机只注册转换表,
## 并在 _enter(首次激活 / 每次落地)时按「当下输入」挑初始子状态 ——
## 不然按着方向键落地会先闪一帧站立。
@onready var idle: LimboState = $Idle
@onready var walk: LimboState = $Walk
@onready var sprint: LimboState = $Sprint
@onready var crouch: LimboState = $Crouch
@onready var sneak: LimboState = $Sneak
func _setup() -> void:
# 事件口径:move_start/stop 有无方向输入,sprint_start/stop 疾跑键,
# crouch_start/end 蹲键。蹲伏优先于疾跑(蹲下时按 Shift 不算疾跑)。
add_transition(idle, walk, &"move_start")
add_transition(idle, crouch, &"crouch_start")
add_transition(walk, idle, &"move_stop")
add_transition(walk, sprint, &"sprint_start")
add_transition(walk, sneak, &"crouch_start")
add_transition(sprint, walk, &"sprint_stop")
add_transition(sprint, idle, &"move_stop")
add_transition(sprint, sneak, &"crouch_start")
add_transition(crouch, sneak, &"move_start")
add_transition(crouch, idle, &"crouch_end")
add_transition(sneak, crouch, &"move_stop")
add_transition(sneak, walk, &"crouch_end")
func _enter() -> void:
var player := get_agent() as Player
if player == null:
set_initial_state(idle)
return
var moving := player.motor.has_move_input()
if Input.is_action_pressed("crouch"):
set_initial_state(sneak if moving else crouch)
elif moving:
set_initial_state(sprint if Input.is_action_pressed("sprint") else walk)
else:
set_initial_state(idle)
+1
View File
@@ -0,0 +1 @@
uid://2bnkxlycmqqu
+17
View File
@@ -0,0 +1,17 @@
extends PlayerGroundState
## 站立:目标速度 0,播 idle。
func _enter() -> void:
var player := get_agent() as Player
player.motor.target_speed = 0.0
player.motor.sprinting = false
player.motor.crouching = false
player.animation_player.play_state(PlayerAnimator.AnimState.IDLE)
func _think(_delta: float) -> void:
# 蹲键优先 —— 同帧又蹲又走时先蹲下,下一帧 Crouch 再转 Sneak,不会漏蹲。
if Input.is_action_pressed("crouch"):
dispatch(&"crouch_start")
elif (get_agent() as Player).motor.has_move_input():
dispatch(&"move_start")
+1
View File
@@ -0,0 +1 @@
uid://2isemrs6ov3
+20
View File
@@ -0,0 +1,20 @@
extends LimboState
## 上升段:播 jump。越过顶点(vy ≤ 0)转 Fall。
## 水平目标速度保持离地前的值 —— 空中加速度本来就小(air_acceleration),
## 不由本状态改写。
func _enter() -> void:
(get_agent() as Player).animation_player.play_state(PlayerAnimator.AnimState.JUMP)
func _update(_delta: float) -> void:
var player := get_agent() as Player
if player == null:
return
# 落地判定带 vy ≤ 0 门槛:起跳那一帧 is_on_floor() 还是 true
# (要等 move_and_slide() 之后才翻假),没有门槛会当帧弹回地面状态。
if player.is_on_floor() and player.velocity.y <= 0.0:
dispatch(&"landed")
return
if player.velocity.y <= 0.0:
dispatch(&"falling")
+1
View File
@@ -0,0 +1 @@
uid://cvfcvo7k1i5jr
+16
View File
@@ -0,0 +1,16 @@
extends LimboHSM
## 玩家状态机根(挂在 Player/HSM 节点上,update_mode = MANUAL)。
##
## 只管最粗的一层:「身体在地上还是空中」。细分状态在两个子机里:
## Grounded 地面 grounded.gd —— Idle / Walk / Sprint / Crouch / Sneak
## Airborne 空中 airborne.gd —— Jump / Fall
## 推进由 Player._physics_process 显式调 update(delta)(管线第一位)。
@onready var grounded: LimboHSM = $Grounded
@onready var airborne: LimboHSM = $Airborne
func _setup() -> void:
# 起跳或踏空 → 空中;落地 → 回地面。事件的发起在各叶子状态里。
add_transition(grounded, airborne, &"airborne")
add_transition(airborne, grounded, &"landed")
+1
View File
@@ -0,0 +1 @@
uid://cgjaw5xbg7gdj
+19
View File
@@ -0,0 +1,19 @@
extends PlayerGroundState
## 蹲走(潜行):目标速度 = crouch_speed,播 sneak。
## 蹲伏优先于疾跑:蹲着按 Shift 也只是潜行。
func _enter() -> void:
var player := get_agent() as Player
player.motor.target_speed = player.motor.crouch_speed
player.motor.sprinting = false
player.motor.crouching = true
player.animation_player.play_state(PlayerAnimator.AnimState.SNEAK, player.motor.crouch_speed)
func _think(_delta: float) -> void:
var player := get_agent() as Player
if not Input.is_action_pressed("crouch"):
dispatch(&"crouch_end")
elif not player.motor.has_move_input():
dispatch(&"move_stop")
+1
View File
@@ -0,0 +1 @@
uid://b4okw70vrah42
+19
View File
@@ -0,0 +1,19 @@
extends PlayerGroundState
## 疾跑:目标速度 = sprint_speed,播 run。只在按着方向键时成立(Sprint 由 Walk 进入)。
func _enter() -> void:
var player := get_agent() as Player
player.motor.target_speed = player.motor.sprint_speed
player.motor.sprinting = true
player.motor.crouching = false
player.animation_player.play_state(PlayerAnimator.AnimState.RUN, player.motor.sprint_speed)
func _think(_delta: float) -> void:
var player := get_agent() as Player
if Input.is_action_pressed("crouch"):
dispatch(&"crouch_start")
elif not player.motor.has_move_input():
dispatch(&"move_stop")
elif not Input.is_action_pressed("sprint"):
dispatch(&"sprint_stop")
+1
View File
@@ -0,0 +1 @@
uid://cklw8uq6xahe
+19
View File
@@ -0,0 +1,19 @@
extends PlayerGroundState
## 步行:目标速度 = walk_speed,播 walk。
func _enter() -> void:
var player := get_agent() as Player
player.motor.target_speed = player.motor.walk_speed
player.motor.sprinting = false
player.motor.crouching = false
player.animation_player.play_state(PlayerAnimator.AnimState.WALK, player.motor.walk_speed)
func _think(_delta: float) -> void:
var player := get_agent() as Player
if Input.is_action_pressed("crouch"):
dispatch(&"crouch_start")
elif not player.motor.has_move_input():
dispatch(&"move_stop")
elif Input.is_action_pressed("sprint"):
dispatch(&"sprint_start")
+1
View File
@@ -0,0 +1 @@
uid://6pa3038100o4