敌ai开发

Heart Lv462

目标:

在Godot中创建一个2D平台游戏的敌人,它需要具备一套基础但智能的AI行为:在自己的区域巡逻,当玩家靠近时进入警戒状态,当玩家进入攻击范围时发动攻击,并在玩家离开后恢复之前的状态。

一切始于一个简单的想法和两个脚本:一个主角,一个敌人。我为敌人编写了基础的if/elif/else逻辑,试图根据与玩家的距离来区分巡逻、追踪和攻击。

遇到的第一个问题:

敌人完全不理会我。它只是朝着一个固定的方向,不停地播放攻击动画。

我怀疑是AI的核心逻辑出了问题,导致它永远卡在攻击的判断分支里。

我换上了一个带有大量print()语句的调试脚本,

致命错误:敌人的 ‘player’ 变量是空的!

与玩家的距离: 0.40

发现一(致命错误):

我犯了Godot初学者最经典的错误之一——忘记在编辑器中将被导出的节点变量赋值!

修复上一个问题后,我发现即使我和敌人俩站在一起,距离也只是一个极小的值。这说明我的“攻击范围”和“追踪范围”设置得太大了,导致AI在游戏开始的第一帧就认为应该攻击。

  • 代码的正确性,仅仅是成功的一半。引擎编辑器的正确配置,同等重要。
  • print() 是你最忠实的朋友,它能告诉你代码运行时最真实的情况。

解决了变量和距离参数后,一个新的问题出现了:

“我明明和敌人重合了,为什么实时距离显示是799?”

这其实是一个更深层次的问题,关乎对Godot节点系统的理解。

根本原因: global_position.distance_to() 计算的是两个节点原点(十字准星)的距离,而不是我们肉眼所见的精灵图片的距离。我的角色和敌人的精灵图片(AnimatedSprite2D)都严重偏离了它们父节点(CharacterBody2D)的原点。

解决方案:

我将主角和敌人的所有子节点(精灵、碰撞体等)的 Transform -> Position 全部重置为 (0, 0):

  • 一个节点的“身体”(原点)和它的“外貌”(子节点)必须保持一致。
  • 始终围绕着父节点的原点来构建角色。

随着需求的细化(比如加入“不动”的警戒状态),我发现简单的if/elif/else结构开始变得混乱,很容易出现“原地转圈”或逻辑冲突。

是时候引入更专业的:状态机(State Machine)

我为敌人定义了清晰的状态:PATROL、ALERT、ATTACK。_physics_process() 函数不再负责复杂的判断,只负责根据 current_state 去调用对应的状态函数。

这让代码的结构立刻变得清晰、健壮,也为解决后续更棘手的问题打下了坚实的基础。

引入状态机并不意味着一劳永逸,一系列新的、更微妙的BUG:

  • 无限攻击循环: 敌人一旦进入攻击范围,就会在ATTACK和ALERT状态间疯狂切换,导致攻击动画根本播不出来。
  • 方向错乱: 精灵的朝向和移动方向相反,因为它没有考虑到我的美术素材“默认朝左”这个前提。
  • 攻击时“傻站着”: 攻击动画只播放了1帧,就被下一帧的idle待机动画覆盖了。

这些问题都通过对状态机逻辑的精细打磨得到了解决,比如引入 can_attack 攻击冷却变量,以及在播放 idle 动画前检查当前动画是否已经是 attack1

最终的解决方案:

change_state() 函数中加入“清理”逻辑。每当状态要从ATTACK离开时,就强制把 can_attack 重置为 true。


最终代码 (enemy.gd)

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
extends CharacterBody2D

@export var player: CharacterBody2D

const ATTACK_RANGE = 100
const ALERT_RANGE = 400
const SPEED = 70
const GRAVITY = 1500

enum State { PATROL, CHASE, ATTACK }
var current_state = State.PATROL
var can_attack = true

@onready var sprite = $AnimatedSprite2D
@onready var attack_timer = $Timer
var patrol_direction = -1

func _ready():
if not is_instance_valid(player):
push_error("敌人的'player'变量未设置!")
if not attack_timer.is_connected("timeout", Callable(self, "_on_timer_timeout")):
push_warning("敌人的Timer 'timeout' 信号未连接!")

func _physics_process(delta):
if not is_on_floor():
velocity.y += GRAVITY * delta

match current_state:
State.PATROL:
patrol_state()
State.CHASE:
chase_state()
State.ATTACK:
attack_state()

move_and_slide()

func patrol_state():
if is_on_wall():
patrol_direction *= -1
velocity.x = patrol_direction * SPEED
set_sprite_direction(patrol_direction)
sprite.play("move")
if is_player_in_range(ALERT_RANGE):
change_state(State.CHASE)

func chase_state():
var direction = get_direction_to_player()
velocity.x = direction * SPEED
set_sprite_direction(direction)
sprite.play("move")
if is_player_in_range(ATTACK_RANGE):
change_state(State.ATTACK)
elif not is_player_in_range(ALERT_RANGE):
change_state(State.PATROL)

func attack_state():
velocity.x = 0

if not is_player_in_range(ATTACK_RANGE):
change_state(State.CHASE)
return

if can_attack:
can_attack = false
look_at_player()
sprite.play("attack1")
attack_timer.start()
elif sprite.animation != "attack1":
look_at_player()
sprite.play("idle")

func change_state(new_state):
if current_state == new_state: return

if current_state == State.ATTACK:
can_attack = true
attack_timer.stop()

current_state = new_state

func _on_timer_timeout():
can_attack = true

func is_player_in_range(range_distance: float) -> bool:
if not is_instance_valid(player):
return false
return global_position.distance_to(player.global_position) <= range_distance

func get_direction_to_player() -> int:
if not is_instance_valid(player):
return 0
return sign(player.global_position.x - global_position.x)

func look_at_player():
set_sprite_direction(get_direction_to_player())

func set_sprite_direction(direction: int):
if direction != 0:
sprite.flip_h = (direction == 1)

  • Title: 敌ai开发
  • Author: Heart
  • Created at : 2025-08-08 14:47:45
  • Updated at : 2025-12-31 20:54:52
  • Link: https://yhalo-wyh.github.io/2025/08/08/敌ai开发/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments