敌ai重写及状态机的应用

Heart Lv462

今天主要的任务是为敌人 LavaGolem 添加基于状态机的 AI 行为逻辑,并解决敌人动画朝向与实际移动方向不一致的问题。
之前的问题包括:

  • 敌人的精灵图在运行动画时会朝反方向移动。
  • 翻转精灵图后,RayCast2D 的检测方向和碰撞逻辑与精灵方向不一致。
  • 需要保证动画、碰撞检测器和逻辑方向保持一致。

2. 状态机结构设计

定义了 State 枚举来描述 LavaGolem 的三种主要行为:

1
2
3
4
5
enum State {
IDLE, # 原地站立
WALK, # 巡逻行走
RUN # 追击玩家
}

状态目标:

  • IDLE:站立不动,等待巡逻或发现玩家。
  • WALK:低速巡逻,遇到墙或悬崖掉头。
  • RUN:高速追击玩家,遇到墙或悬崖掉头。

3. 脚本实现细节

利用 playerchecker(RayCast2D)检测玩家是否在视线范围内:

1
2
3
4
func can_see_player() -> bool:
if not playerchecker.is_colliding():
return false
return playerchecker.get_collider() is Player

状态物理逻辑

tick_physics() 根据当前状态执行不同速度和移动逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
func tick_physics(state: State, delta: float) -> void:
match state:
State.IDLE:
move(0.0, delta)
State.WALK:
move(max_speed / 3, delta)
State.RUN:
if wallchecker.is_colliding() or not floorchecker.is_colliding():
direction *= -1
move(max_speed, delta)
if can_see_player():
calm_down_timer.start()

状态切换规则

get_next_state() 用来判断何时切换状态:

  • IDLE → WALK:等待超过 2 秒。
  • WALK → IDLE:遇到墙或悬崖。
  • RUN → WALK:冷静计时结束。
  • 玩家在视线范围内时,优先进入 RUN 状态。

动画与翻转同步

transition_state() 中播放对应动画,并在需要时调整方向:

1
2
3
4
5
6
7
8
9
10
11
12
13
func transition_state(from: State, to: State) -> void:
match to:
State.IDLE:
animation_player.play("idle")
if wallchecker.is_colliding():
direction *= -1
State.WALK:
animation_player.play("walk")
if not floorchecker.is_colliding():
direction *= -1
floorchecker.force_raycast_update()
State.RUN:
animation_player.play("run")

4. 换了精灵图后解决动画反向问题

  1. 新的精灵图动画和实际运动方向不一致

    • 解决:在 _ready() 中整体 scale.x *= -1 翻转。
  2. 碰撞检测朝向错误

    • 原因:RayCast2D 没有随精灵一起翻转。
    • 解决:翻转的是整个敌人根节点(包含所有碰撞与检测节点),而不是单独 Sprite2D。
  3. 动画关键帧错位

    • 原因:播放动画时 Sprite 的位置、缩放被覆盖。
    • 解决:确保 Animation 只修改所需属性,不覆盖 scale。

    初始化翻转

    _ready() 中对整个节点进行一次横向翻转,使精灵图与检测器统一朝向:

    1
    2
    func _ready() -> void:
    scale.x *= -1

    玩家检测

5. 最终脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
extends Enemy

enum State {
IDLE,
WALK,
RUN
}

@onready var wallchecker: RayCast2D = $Graphics/Wallchecker
@onready var playerchecker: RayCast2D = $Graphics/Playerchecker
@onready var floorchecker: RayCast2D = $Graphics/Floorchecker
@onready var calm_down_timer: Timer = $CalmDownTimer

func _ready() -> void:
scale.x *= -1

func can_see_player() -> bool:
if not playerchecker.is_colliding():
return false
return playerchecker.get_collider() is Player

func tick_physics(state: State, delta: float) -> void:
match state:
State.IDLE:
move(0.0, delta)
State.WALK:
move(max_speed / 3, delta)
State.RUN:
if wallchecker.is_colliding() or not floorchecker.is_colliding():
direction *= -1
move(max_speed, delta)
if can_see_player():
calm_down_timer.start()

func get_next_state(state: State) -> State:
if can_see_player():
return State.RUN
match state:
State.IDLE:
if state_machine.state_time > 2:
return State.WALK
State.WALK:
if wallchecker.is_colliding() or not floorchecker.is_colliding():
return State.IDLE
State.RUN:
if calm_down_timer.is_stopped():
return State.WALK
return state

func transition_state(from: State, to: State) -> void:
match to:
State.IDLE:
animation_player.play("idle")
if wallchecker.is_colliding():
direction *= -1
State.WALK:
animation_player.play("walk")
if not floorchecker.is_colliding():
direction *= -1
floorchecker.force_raycast_update()
State.RUN:
animation_player.play("run")

func _on_hurtbox_hurt(hitbox: Hitbox) -> void:
queue_free()

6. 心得与优化方向

  • 状态机的扩展性很好,可以方便地增加 ATTACK、HIT、DEAD 等状态。
  • Title: 敌ai重写及状态机的应用
  • Author: Heart
  • Created at : 2025-08-11 15:18:01
  • Updated at : 2025-12-31 20:54:53
  • Link: https://yhalo-wyh.github.io/2025/08/11/敌ai重写及状态机的应用/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments