鸿蒙游戏 AI NPC:行为树原理 + 实战代码


大家好,我是 子玥酱,一名长期深耕在一线的前端程序媛 👩💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。
我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括 前端工程化、小程序、React / RN、Flutter、跨端方案,
在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。
技术方向:前端 / 跨端 / 小程序 / 移动端工程化
内容平台:掘金、知乎、CSDN、简书
创作特点:实战导向、源码拆解、少空谈多落地
文章状态:长期稳定更新,大量原创输出
我的内容主要围绕 前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读 展开。文章不会停留在“API 怎么用”,而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍,希望能帮你在实际工作中少走弯路。
子玥酱 · 前端成长记录官 ✨
👋 如果你正在做前端,或准备长期走前端这条路
📚 关注我,第一时间获取前端行业趋势与实践总结
🎁 可领取 11 类前端进阶学习资源(工程化 / 框架 / 跨端 / 面试 / 架构)
💡 一起把技术学“明白”,也用“到位”
持续写作,持续进阶。
愿我们都能在代码和生活里,走得更稳一点 🌱
文章目录
引言
很多开发者第一次给鸿蒙游戏加 NPC 时,都会这样写:
if (playerNear) {
attack()
}
或者:
if (hp < 30) {
runAway()
}
刚开始感觉没问题:
能跑
能打
能追人
但随着游戏复杂度增加:
巡逻
追击
攻击
释放技能
逃跑
求援
Boss机制
很快就会出现一个问题:
if-else 地狱。
代码可能变成:
if (playerNear) {
if (hp > 50) {
if (skillReady) {
castSkill()
} else {
attack()
}
} else {
if (friendNear) {
callHelp()
} else {
runAway()
}
}
}
几个月后连自己都不敢改,很多人会觉得:
是不是 AI 太复杂了?
其实不是,问题在于:
你在用条件判断实现决策系统。
而现代游戏普遍采用:
Behavior Tree
(行为树)
来驱动 NPC。
一、什么是行为树?
先看一个最简单的 NPC,需求:
发现玩家
↓
攻击玩家
传统写法:
if (canSeePlayer()) {
attack()
}
没问题,但增加需求:
发现玩家
↓
距离近
↓
攻击
距离远
↓
追击
逻辑开始复杂,行为树则表示为:
发现玩家?
↓
是
↓
距离是否足够?
↓ ↓
是 否
↓ ↓
攻击 追击
本质上:
行为树是一种可视化决策流程。
二、为什么游戏都在用行为树?
因为 NPC 本质上就是:
不断决策
例如,怪物:
巡逻
发现敌人
追击
攻击
逃跑
Boss:
一阶段
二阶段
狂暴阶段
释放技能
召唤小怪
这些本质都是:状态选择问题,行为树非常适合处理。
三、行为树的三大节点
最常见的是:
Selector
Sequence
Action
Selector(选择器)
意思:
从左到右尝试
成功一个就结束
例如:
攻击
↓失败
追击
↓失败
巡逻
表现:
优先攻击
其次追击
最后巡逻
Sequence(顺序器)
意思:
全部成功才算成功
例如:
发现玩家
↓
接近玩家
↓
攻击
任何一步失败:
整个流程失败
Action(动作节点)
真正执行行为,例如:
Attack
Move
Patrol
RunAway
四、设计一个简单 NPC
需求:
发现玩家
↓
攻击
否则
↓
巡逻
行为树:
Selector
├── AttackPlayer
└── Patrol
对应结构:
root = Selector(
AttackPlayer(),
Patrol()
)
五、定义行为树节点
基础接口:
enum NodeStatus {
Success,
Failure,
Running
}
interface BTNode {
tick(): NodeStatus
}
所有节点统一:
tick()
驱动。
六、实现 Action 节点
巡逻:
class PatrolNode implements BTNode {
tick(): NodeStatus {
console.info('Patrol')
return NodeStatus.Success
}
}
攻击:
class AttackNode implements BTNode {
tick(): NodeStatus {
console.info('Attack')
return NodeStatus.Success
}
}
七、实现 Selector
核心逻辑:
class SelectorNode implements BTNode {
constructor(
private children: BTNode[]
) {}
tick(): NodeStatus {
for (const node of this.children) {
const result = node.tick()
if (
result === NodeStatus.Success
) {
return result
}
}
return NodeStatus.Failure
}
}
作用:
优先执行最重要行为
八、加入条件节点
例如:
发现玩家?
判断,实现:
class SeePlayerNode implements BTNode {
constructor(
private npc: NPC
) {}
tick(): NodeStatus {
return this.npc.canSeePlayer()
? NodeStatus.Success
: NodeStatus.Failure
}
}
九、实现追击逻辑
需求:
发现玩家
↓
追击
↓
攻击
行为树:
Sequence
├── SeePlayer
├── MoveToPlayer
└── Attack
代码:
const combatTree =
new SequenceNode([
new SeePlayerNode(npc),
new MoveNode(npc),
new AttackNode(npc)
])
十、AISystem 驱动行为树
前面我们讲过:
Store
System
HUD
架构,行为树应该属于:
AISystem
而不是:
UI
例如:
class AISystem {
update(npc: NPC) {
npc.behaviorTree.tick()
}
}
十一、与 Store 结合
NPC状态:
class NPCStore {
hp = 100
targetId = 0
position = {
x: 0,
y: 0
}
}
行为树读取:Store 决策。
然后:
AISystem
修改:
Store
状态,形成:
Store
↓
BehaviorTree
↓
AISystem
↓
Store
闭环。
十二、Boss 行为树实战
Boss需求:
HP > 50%
普通攻击
HP < 50%
释放大招
HP < 20%
狂暴模式
行为树:
Selector
├── RageMode
├── UltimateSkill
└── NormalAttack
代码:
Selector(
RageNode(),
UltimateNode(),
AttackNode()
)
结构非常清晰。
十三、为什么行为树比 if-else 更强?
传统:
if()
if()
if()
if()
最终:
无法维护
行为树:
节点化
可组合
可扩展
增加新行为:
SkillNode
插入即可,无需重写全部逻辑。
十四、行为树与多端同步
前面我们讲:
Store一致
System一致
才能实现:
多端一致性
行为树天然符合,因为:
输入一致
↓
行为树一致
↓
决策一致
↓
状态一致
例如:
手机NPC
PC NPC
执行同一行为树,结果相同。
十五、未来升级:行为树 + AI Agent
未来 NPC 不再只是:
固定规则
而是:
行为树
+
大模型
例如:
行为树
负责框架
LLM
负责决策
结构:
Selector
↓
AskLLM()
↓
Attack
Move
Trade
Talk
NPC 会变得更加智能。
十六、一个关键认知升级
初学者认为:
AI = if-else
进阶开发者认为:
AI = 行为树
而大型项目最终理解:
行为树不是 AI,而是管理 AI 决策复杂度的架构。
因为真正的问题从来不是:
NPC会不会攻击
而是:
当NPC拥有100种行为时
系统还能不能维护
总结
鸿蒙游戏中的 AI NPC 推荐架构:
Store
↓
BehaviorTree
↓
AISystem
↓
Store
核心节点:
Selector
Sequence
Action
核心优势:
逻辑清晰
可视化
易扩展
易测试
支持多端同步
如果用一句话总结:
行为树的本质,不是让 NPC 更聪明,而是让复杂 AI 行为变得可管理、可组合、可扩展。
更多推荐




所有评论(0)