游戏开发核心架构指南
深入理解游戏开发中的核心架构模式:ECS、MVC、MVP、MVVM、OOP、DOP、FSM、HFSM、事件驱动、GOAP、Behavior Tree、Utility AI、Component、Observer、Ability System
目录
- 游戏开发核心架构指南
-
- [1. OOP(面向对象编程)](#1. OOP(面向对象编程))
- [2. Component(组件模式)](#2. Component(组件模式))
- [3. DOP(数据导向编程)](#3. DOP(数据导向编程))
- [4. ECS(实体组件系统)](#4. ECS(实体组件系统))
- [5. Ability System(能力系统)](#5. Ability System(能力系统))
- [6. MVC(模型-视图-控制器)](#6. MVC(模型-视图-控制器))
- [7. MVP(模型-视图-展示器)](#7. MVP(模型-视图-展示器))
- [8. MVVM(模型-视图-视图模型)](#8. MVVM(模型-视图-视图模型))
-
- 概述
- 架构图
- [ViewModel 实现](#ViewModel 实现)
- 数据绑定
- 优缺点
- 游戏UI架构的实际情况
- [9. Observer(观察者模式)与Event(事件系统)](#9. Observer(观察者模式)与Event(事件系统))
- [10. FSM(有限状态机)](#10. FSM(有限状态机))
- [11. HFSM(分层有限状态机)](#11. HFSM(分层有限状态机))
- [12. Behavior Tree(行为树)](#12. Behavior Tree(行为树))
- [13. GOAP(目标导向行为规划)](#13. GOAP(目标导向行为规划))
- [14. Utility AI(效用AI)](#14. Utility AI(效用AI))
- [15. 架构对比总结](#15. 架构对比总结)
- [16. Unreal Engine 架构映射](#16. Unreal Engine 架构映射)
- 结语
1. OOP(面向对象编程)
概述
OOP(Object-Oriented Programming)是最经典的游戏开发范式,以"对象"为核心,通过封装、继承、多态三大特性组织代码。
核心概念
#mermaid-svg-wAClDCpA0oxAUunC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wAClDCpA0oxAUunC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wAClDCpA0oxAUunC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wAClDCpA0oxAUunC .error-icon{fill:#552222;}#mermaid-svg-wAClDCpA0oxAUunC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wAClDCpA0oxAUunC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wAClDCpA0oxAUunC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wAClDCpA0oxAUunC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wAClDCpA0oxAUunC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wAClDCpA0oxAUunC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wAClDCpA0oxAUunC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wAClDCpA0oxAUunC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wAClDCpA0oxAUunC .marker.cross{stroke:#333333;}#mermaid-svg-wAClDCpA0oxAUunC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wAClDCpA0oxAUunC p{margin:0;}#mermaid-svg-wAClDCpA0oxAUunC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wAClDCpA0oxAUunC .cluster-label text{fill:#333;}#mermaid-svg-wAClDCpA0oxAUunC .cluster-label span{color:#333;}#mermaid-svg-wAClDCpA0oxAUunC .cluster-label span p{background-color:transparent;}#mermaid-svg-wAClDCpA0oxAUunC .label text,#mermaid-svg-wAClDCpA0oxAUunC span{fill:#333;color:#333;}#mermaid-svg-wAClDCpA0oxAUunC .node rect,#mermaid-svg-wAClDCpA0oxAUunC .node circle,#mermaid-svg-wAClDCpA0oxAUunC .node ellipse,#mermaid-svg-wAClDCpA0oxAUunC .node polygon,#mermaid-svg-wAClDCpA0oxAUunC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wAClDCpA0oxAUunC .rough-node .label text,#mermaid-svg-wAClDCpA0oxAUunC .node .label text,#mermaid-svg-wAClDCpA0oxAUunC .image-shape .label,#mermaid-svg-wAClDCpA0oxAUunC .icon-shape .label{text-anchor:middle;}#mermaid-svg-wAClDCpA0oxAUunC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wAClDCpA0oxAUunC .rough-node .label,#mermaid-svg-wAClDCpA0oxAUunC .node .label,#mermaid-svg-wAClDCpA0oxAUunC .image-shape .label,#mermaid-svg-wAClDCpA0oxAUunC .icon-shape .label{text-align:center;}#mermaid-svg-wAClDCpA0oxAUunC .node.clickable{cursor:pointer;}#mermaid-svg-wAClDCpA0oxAUunC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wAClDCpA0oxAUunC .arrowheadPath{fill:#333333;}#mermaid-svg-wAClDCpA0oxAUunC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wAClDCpA0oxAUunC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wAClDCpA0oxAUunC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wAClDCpA0oxAUunC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wAClDCpA0oxAUunC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wAClDCpA0oxAUunC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wAClDCpA0oxAUunC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wAClDCpA0oxAUunC .cluster text{fill:#333;}#mermaid-svg-wAClDCpA0oxAUunC .cluster span{color:#333;}#mermaid-svg-wAClDCpA0oxAUunC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wAClDCpA0oxAUunC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wAClDCpA0oxAUunC rect.text{fill:none;stroke-width:0;}#mermaid-svg-wAClDCpA0oxAUunC .icon-shape,#mermaid-svg-wAClDCpA0oxAUunC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wAClDCpA0oxAUunC .icon-shape p,#mermaid-svg-wAClDCpA0oxAUunC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wAClDCpA0oxAUunC .icon-shape .label rect,#mermaid-svg-wAClDCpA0oxAUunC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wAClDCpA0oxAUunC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wAClDCpA0oxAUunC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wAClDCpA0oxAUunC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 类 Class
封装 Encapsulation
继承 Inheritance
多态 Polymorphism
数据隐藏
代码复用
动态分发
游戏中的典型应用
cpp
// 基类:游戏角色
class Character
{
protected:
std::string name;
int hp;
int maxHp;
float speed;
public:
Character(const std::string& n, int h, float s)
: name(n), hp(h), maxHp(h), speed(s) {}
virtual void Update(float deltaTime)
{
// 基础更新逻辑
}
virtual void TakeDamage(int damage)
{
hp -= damage;
if (hp <= 0) Die();
}
virtual void Die() { /* 死亡逻辑 */ }
virtual ~Character() = default;
};
// 派生类:玩家
class Player : public Character
{
private:
int experience;
int playerLevel;
public:
Player(const std::string& name)
: Character(name, 100, 5.0f), experience(0), playerLevel(1) {}
void Update(float deltaTime) override
{
HandleInput();
Character::Update(deltaTime);
}
void GainExperience(int exp)
{
experience += exp;
if (experience >= playerLevel * 100) LevelUp();
}
private:
void HandleInput() { /* 键盘/手柄输入处理 */ }
void LevelUp() {
playerLevel++;
maxHp += 20;
hp = maxHp;
speed += 0.5f;
}
};
继承层次结构
#mermaid-svg-TcarcaxFivl4xHWy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TcarcaxFivl4xHWy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TcarcaxFivl4xHWy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TcarcaxFivl4xHWy .error-icon{fill:#552222;}#mermaid-svg-TcarcaxFivl4xHWy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TcarcaxFivl4xHWy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TcarcaxFivl4xHWy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TcarcaxFivl4xHWy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TcarcaxFivl4xHWy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TcarcaxFivl4xHWy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TcarcaxFivl4xHWy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TcarcaxFivl4xHWy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TcarcaxFivl4xHWy .marker.cross{stroke:#333333;}#mermaid-svg-TcarcaxFivl4xHWy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TcarcaxFivl4xHWy p{margin:0;}#mermaid-svg-TcarcaxFivl4xHWy g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-TcarcaxFivl4xHWy g.classGroup text .title{font-weight:bolder;}#mermaid-svg-TcarcaxFivl4xHWy .cluster-label text{fill:#333;}#mermaid-svg-TcarcaxFivl4xHWy .cluster-label span{color:#333;}#mermaid-svg-TcarcaxFivl4xHWy .cluster-label span p{background-color:transparent;}#mermaid-svg-TcarcaxFivl4xHWy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TcarcaxFivl4xHWy .cluster text{fill:#333;}#mermaid-svg-TcarcaxFivl4xHWy .cluster span{color:#333;}#mermaid-svg-TcarcaxFivl4xHWy .nodeLabel,#mermaid-svg-TcarcaxFivl4xHWy .edgeLabel{color:#131300;}#mermaid-svg-TcarcaxFivl4xHWy .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-TcarcaxFivl4xHWy .label text{fill:#131300;}#mermaid-svg-TcarcaxFivl4xHWy .labelBkg{background:#ECECFF;}#mermaid-svg-TcarcaxFivl4xHWy .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-TcarcaxFivl4xHWy .classTitle{font-weight:bolder;}#mermaid-svg-TcarcaxFivl4xHWy .node rect,#mermaid-svg-TcarcaxFivl4xHWy .node circle,#mermaid-svg-TcarcaxFivl4xHWy .node ellipse,#mermaid-svg-TcarcaxFivl4xHWy .node polygon,#mermaid-svg-TcarcaxFivl4xHWy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TcarcaxFivl4xHWy .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy g.clickable{cursor:pointer;}#mermaid-svg-TcarcaxFivl4xHWy g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-TcarcaxFivl4xHWy g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-TcarcaxFivl4xHWy .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-TcarcaxFivl4xHWy .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-TcarcaxFivl4xHWy .dashed-line{stroke-dasharray:3;}#mermaid-svg-TcarcaxFivl4xHWy .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-TcarcaxFivl4xHWy #compositionStart,#mermaid-svg-TcarcaxFivl4xHWy .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy #compositionEnd,#mermaid-svg-TcarcaxFivl4xHWy .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy #dependencyStart,#mermaid-svg-TcarcaxFivl4xHWy .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy #dependencyStart,#mermaid-svg-TcarcaxFivl4xHWy .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy #extensionStart,#mermaid-svg-TcarcaxFivl4xHWy .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy #extensionEnd,#mermaid-svg-TcarcaxFivl4xHWy .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy #aggregationStart,#mermaid-svg-TcarcaxFivl4xHWy .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy #aggregationEnd,#mermaid-svg-TcarcaxFivl4xHWy .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy #lollipopStart,#mermaid-svg-TcarcaxFivl4xHWy .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy #lollipopEnd,#mermaid-svg-TcarcaxFivl4xHWy .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-TcarcaxFivl4xHWy .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-TcarcaxFivl4xHWy .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TcarcaxFivl4xHWy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TcarcaxFivl4xHWy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TcarcaxFivl4xHWy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Character
+string name
+int hp
+int maxHp
+float speed
+Update(float dt)
+TakeDamage(int dmg)
+Die()
Player
-int experience
-int playerLevel
+HandleInput()
+GainExperience(int)
Enemy
#AIState state
#float detectionRange
+UpdateAI(float dt)
Boss
+int phase
+int specialAttackCooldown
+PhaseTransition()
NPC
+string dialogue
+Interact()
现代游戏开发趋势:组合优于继承
传统OOP在游戏行业正在逐渐弱化深度继承,转而强化组合(Composition)。
cpp
// ❌ 传统深度继承(已不推荐)
class Monster : public Character {};
class FireMonster : public Monster {};
class FireBossMonster : public FireMonster {};
// ✅ 推荐:OOP + Component(UE风格)
class ACharacter
{
UCameraComponent* Camera;
UInventoryComponent* Inventory;
UAbilitySystemComponent* Abilities;
UEquipmentComponent* Equipment;
};
核心原则 :现代游戏开发更推荐组合优于继承(Composition over Inheritance)。用组件拼装对象功能,而非用继承链定义对象类型。
优缺点
| 对比维度 | 传统深层继承(Inheritance) | 现代组件组合(Composition) |
|---|---|---|
| 核心逻辑 | "是"关系 (Is-A) 通过父类是谁来决定自己拥有什么功能。 | "有"关系 (Has-A) 通过自己挂载了什么组件来决定拥有什么功能。 |
| 解耦程度 | 强耦合(白盒复用) 基类的修改会波及整条继承链,引发"脆弱基类"问题。 | 强解耦(黑盒复用) 组件之间相互独立,行为内聚,接口清晰。 |
| 动态扩展性 | 编译期决定(死板) 无法在游戏运行时动态剥离或增加某个基类行为。 | 运行期决定(极度灵活) 支持在运行时通过代码或编辑器动态 AddComponent / DestroyComponent。 |
| 类体量控制 | 类爆炸 / 菱形继承 为了组合不同功能(如:既能飞又能游泳的怪物),不得不陷入多重继承灾难。 | 线性增长(N个功能 = N个组件) 新功能只需抽象成独立组件,直接挂载到宿主即可,无视继承关系。 |
| 生产力协同 | 程序包揽(不利于策划) 新类型的定制必须由程序编写派生类,关卡设计师无法自由组装。 | 数据驱动(策划友好) 程序写好组件,策划可以在编辑器中通过蓝图或预制体自由拼装、调整参数。 |
| 主要代价/缺点 | 1. 继承链深了以后,代码极难重构与维护。 2. 虚函数虚表查找(V-Table Click)带来额外的 CPU 间接寻址开销。 | 1. 组件间通信(如物理组件调用动画组件)需要特定的解耦设计。 2. 挂载过多组件时,生命周期管理与依赖检查有额外开销。 |
2. Component(组件模式)
概述
Component模式是OOP的演进产物,将对象功能拆分为独立组件,通过组合方式构建复杂对象。UE的ActorComponent、Unity的MonoBehaviour都是这一模式的典型实现。
架构对比
#mermaid-svg-qhlTBmt47w8FEVjp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qhlTBmt47w8FEVjp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qhlTBmt47w8FEVjp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qhlTBmt47w8FEVjp .error-icon{fill:#552222;}#mermaid-svg-qhlTBmt47w8FEVjp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qhlTBmt47w8FEVjp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qhlTBmt47w8FEVjp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qhlTBmt47w8FEVjp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qhlTBmt47w8FEVjp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qhlTBmt47w8FEVjp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qhlTBmt47w8FEVjp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qhlTBmt47w8FEVjp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qhlTBmt47w8FEVjp .marker.cross{stroke:#333333;}#mermaid-svg-qhlTBmt47w8FEVjp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qhlTBmt47w8FEVjp p{margin:0;}#mermaid-svg-qhlTBmt47w8FEVjp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qhlTBmt47w8FEVjp .cluster-label text{fill:#333;}#mermaid-svg-qhlTBmt47w8FEVjp .cluster-label span{color:#333;}#mermaid-svg-qhlTBmt47w8FEVjp .cluster-label span p{background-color:transparent;}#mermaid-svg-qhlTBmt47w8FEVjp .label text,#mermaid-svg-qhlTBmt47w8FEVjp span{fill:#333;color:#333;}#mermaid-svg-qhlTBmt47w8FEVjp .node rect,#mermaid-svg-qhlTBmt47w8FEVjp .node circle,#mermaid-svg-qhlTBmt47w8FEVjp .node ellipse,#mermaid-svg-qhlTBmt47w8FEVjp .node polygon,#mermaid-svg-qhlTBmt47w8FEVjp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qhlTBmt47w8FEVjp .rough-node .label text,#mermaid-svg-qhlTBmt47w8FEVjp .node .label text,#mermaid-svg-qhlTBmt47w8FEVjp .image-shape .label,#mermaid-svg-qhlTBmt47w8FEVjp .icon-shape .label{text-anchor:middle;}#mermaid-svg-qhlTBmt47w8FEVjp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qhlTBmt47w8FEVjp .rough-node .label,#mermaid-svg-qhlTBmt47w8FEVjp .node .label,#mermaid-svg-qhlTBmt47w8FEVjp .image-shape .label,#mermaid-svg-qhlTBmt47w8FEVjp .icon-shape .label{text-align:center;}#mermaid-svg-qhlTBmt47w8FEVjp .node.clickable{cursor:pointer;}#mermaid-svg-qhlTBmt47w8FEVjp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qhlTBmt47w8FEVjp .arrowheadPath{fill:#333333;}#mermaid-svg-qhlTBmt47w8FEVjp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qhlTBmt47w8FEVjp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qhlTBmt47w8FEVjp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qhlTBmt47w8FEVjp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qhlTBmt47w8FEVjp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qhlTBmt47w8FEVjp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qhlTBmt47w8FEVjp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qhlTBmt47w8FEVjp .cluster text{fill:#333;}#mermaid-svg-qhlTBmt47w8FEVjp .cluster span{color:#333;}#mermaid-svg-qhlTBmt47w8FEVjp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qhlTBmt47w8FEVjp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qhlTBmt47w8FEVjp rect.text{fill:none;stroke-width:0;}#mermaid-svg-qhlTBmt47w8FEVjp .icon-shape,#mermaid-svg-qhlTBmt47w8FEVjp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qhlTBmt47w8FEVjp .icon-shape p,#mermaid-svg-qhlTBmt47w8FEVjp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qhlTBmt47w8FEVjp .icon-shape .label rect,#mermaid-svg-qhlTBmt47w8FEVjp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qhlTBmt47w8FEVjp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qhlTBmt47w8FEVjp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qhlTBmt47w8FEVjp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 组件组合
Entity
MovementComponent
AnimationComponent
ShootingComponent
✅ 自由组合
N个功能 = N个组件
传统继承树
Entity
MoveableEntity
AnimatedEntity
ShootableEntity
MoveableAnimatedEntity
MoveableShootableEntity
❌ 功能组合越多
类爆炸
Component C++ 实现(UE风格)
cpp
class Actor
{
private:
std::vector<std::unique_ptr<Component>> components;
public:
~Actor() = default;
template<typename T>
T* AddComponent()
{
static_assert(std::is_base_of_v<Component, T>, "T must derive from Component");
auto comp = std::make_unique<T>(this);
T* ptr = comp.get();
comp->Initialize();
components.push_back(std::move(comp));
return ptr;
}
template<typename T>
T* GetComponent()
{
for (auto& c : components)
{
if (auto* casted = dynamic_cast<T*>(c.get()))
return casted;
}
return nullptr;
}
void Update(float dt)
{
for (auto& c : components) c->Tick(dt);
}
};
// 组件基类
class Component
{
protected:
Actor* owner;
public:
Component(Actor* o) : owner(o) {}
virtual ~Component() = default;
virtual void Initialize() {}
virtual void Tick(float dt) {}
};
// 具体组件
class MovementComponent : public Component
{
Vector3 velocity;
public:
void Tick(float dt) override
{
owner->SetPosition(owner->GetPosition() + velocity * dt);
}
};
class HealthComponent : public Component
{
int hp = 100;
public:
void TakeDamage(int dmg) { hp -= dmg; }
bool IsAlive() const { return hp > 0; }
};
// 使用
Actor player;
player.AddComponent<MovementComponent>();
player.AddComponent<HealthComponent>();
player.AddComponent<InventoryComponent>();
优缺点
| 优点 | 缺点 |
|---|---|
| 灵活组合功能 | 组件间通信复杂 |
| 避免继承爆炸 | 运行时类型检查开销 |
| 易于复用和测试 | 组件依赖管理 |
| UE/Unity原生支持 | 过度拆分组件的风险 |
3. DOP(数据导向编程)
概述
DOP(Data-Oriented Programming)数据导向编程,强调以数据为中心组织程序,关注数据在内存中的布局和访问模式,最大化CPU缓存利用率。
核心思想
#mermaid-svg-wwTYMAIyf6Uze4zF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wwTYMAIyf6Uze4zF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wwTYMAIyf6Uze4zF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wwTYMAIyf6Uze4zF .error-icon{fill:#552222;}#mermaid-svg-wwTYMAIyf6Uze4zF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wwTYMAIyf6Uze4zF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wwTYMAIyf6Uze4zF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wwTYMAIyf6Uze4zF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wwTYMAIyf6Uze4zF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wwTYMAIyf6Uze4zF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wwTYMAIyf6Uze4zF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wwTYMAIyf6Uze4zF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wwTYMAIyf6Uze4zF .marker.cross{stroke:#333333;}#mermaid-svg-wwTYMAIyf6Uze4zF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wwTYMAIyf6Uze4zF p{margin:0;}#mermaid-svg-wwTYMAIyf6Uze4zF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wwTYMAIyf6Uze4zF .cluster-label text{fill:#333;}#mermaid-svg-wwTYMAIyf6Uze4zF .cluster-label span{color:#333;}#mermaid-svg-wwTYMAIyf6Uze4zF .cluster-label span p{background-color:transparent;}#mermaid-svg-wwTYMAIyf6Uze4zF .label text,#mermaid-svg-wwTYMAIyf6Uze4zF span{fill:#333;color:#333;}#mermaid-svg-wwTYMAIyf6Uze4zF .node rect,#mermaid-svg-wwTYMAIyf6Uze4zF .node circle,#mermaid-svg-wwTYMAIyf6Uze4zF .node ellipse,#mermaid-svg-wwTYMAIyf6Uze4zF .node polygon,#mermaid-svg-wwTYMAIyf6Uze4zF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wwTYMAIyf6Uze4zF .rough-node .label text,#mermaid-svg-wwTYMAIyf6Uze4zF .node .label text,#mermaid-svg-wwTYMAIyf6Uze4zF .image-shape .label,#mermaid-svg-wwTYMAIyf6Uze4zF .icon-shape .label{text-anchor:middle;}#mermaid-svg-wwTYMAIyf6Uze4zF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wwTYMAIyf6Uze4zF .rough-node .label,#mermaid-svg-wwTYMAIyf6Uze4zF .node .label,#mermaid-svg-wwTYMAIyf6Uze4zF .image-shape .label,#mermaid-svg-wwTYMAIyf6Uze4zF .icon-shape .label{text-align:center;}#mermaid-svg-wwTYMAIyf6Uze4zF .node.clickable{cursor:pointer;}#mermaid-svg-wwTYMAIyf6Uze4zF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wwTYMAIyf6Uze4zF .arrowheadPath{fill:#333333;}#mermaid-svg-wwTYMAIyf6Uze4zF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wwTYMAIyf6Uze4zF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wwTYMAIyf6Uze4zF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wwTYMAIyf6Uze4zF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wwTYMAIyf6Uze4zF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wwTYMAIyf6Uze4zF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wwTYMAIyf6Uze4zF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wwTYMAIyf6Uze4zF .cluster text{fill:#333;}#mermaid-svg-wwTYMAIyf6Uze4zF .cluster span{color:#333;}#mermaid-svg-wwTYMAIyf6Uze4zF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wwTYMAIyf6Uze4zF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wwTYMAIyf6Uze4zF rect.text{fill:none;stroke-width:0;}#mermaid-svg-wwTYMAIyf6Uze4zF .icon-shape,#mermaid-svg-wwTYMAIyf6Uze4zF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wwTYMAIyf6Uze4zF .icon-shape p,#mermaid-svg-wwTYMAIyf6Uze4zF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wwTYMAIyf6Uze4zF .icon-shape .label rect,#mermaid-svg-wwTYMAIyf6Uze4zF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wwTYMAIyf6Uze4zF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wwTYMAIyf6Uze4zF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wwTYMAIyf6Uze4zF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ✅ DOP 内存布局(SoA)
流式访问
流式访问
HP数组: 1,2,3,4,...
PosX数组: 1,2,3,4,...
PosY数组: 1,2,3,4,...
❌ OOP 内存布局(AoS)
跳跃访问
对象1: HP|PosX|PosY
对象2: HP|PosX|PosY
对象3: HP|PosX|PosY
注意 :SoA(Struct of Arrays)是DOP的一种实现方式,而非DOP本身 。DOP的核心思想是按访问模式组织数据,SoA只是最常见的实现手段。
C++ 实现
cpp
// DOP 风格:数据与逻辑分离,SoA布局
struct GameData
{
std::vector<float> posX;
std::vector<float> posY;
std::vector<float> velocityX;
std::vector<float> velocityY;
std::vector<int> hp;
std::vector<uint8_t> isActive;
};
void UpdatePositions(GameData& data, float dt)
{
const size_t count = std::min({
data.posX.size(), data.posY.size(),
data.velocityX.size(), data.velocityY.size()
});
float* px = data.posX.data();
float* py = data.posY.data();
float* vx = data.velocityX.data();
float* vy = data.velocityY.data();
for (size_t i = 0; i < count; ++i)
{
px[i] += vx[i] * dt;
py[i] += vy[i] * dt;
}
}
struct HotData {
std::vector<float> posX;
std::vector<float> posY;
std::vector<int> hp;
std::vector<uint8_t> active;
};
struct ColdData
{
std::vector<std::string> name;
std::vector<float> respawnTimer;
};
性能对比
cpp
constexpr size_t ENTITY_COUNT = 1000000;
// AoS (OOP 风格) - Cache不友好
struct EntityAoS { float x, y, vx, vy; int hp; };
std::vector<EntityAoS> entitiesAoS(ENTITY_COUNT);
// SoA (DOP 风格) - Cache友好
std::unique_ptr<float[]> x(new float[ENTITY_COUNT]);
std::unique_ptr<float[]> y(new float[ENTITY_COUNT]);
std::unique_ptr<float[]> vx(new float[ENTITY_COUNT]);
std::unique_ptr<float[]> vy(new float[ENTITY_COUNT]);
void UpdateAoS(float dt)
{
for (auto& e : entitiesAoS) {
e.x += e.vx * dt;
e.y += e.vy * dt;
}
}
void UpdateSoA(float dt)
{
for (size_t i = 0; i < ENTITY_COUNT; ++i)
{
x[i] += vx[i] * dt;
y[i] += vy[i] * dt;
}
}
优缺点
| 优点 | 缺点 |
|---|---|
| 极高的缓存效率 | 代码可读性较差 |
| 易于并行化(SIMD/多线程) | 开发效率较低 |
| 性能可预测 | 难以建模复杂关系 |
| 适合大规模数据处理 | 调试困难 |
4. ECS(实体组件系统)
概述
ECS(Entity Component System)是游戏开发中广泛使用的架构模式,将数据(Component)与行为(System)彻底分离,实体(Entity)只是一个ID。
架构图
#mermaid-svg-OpgiH3Uxet4E9GQM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OpgiH3Uxet4E9GQM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OpgiH3Uxet4E9GQM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OpgiH3Uxet4E9GQM .error-icon{fill:#552222;}#mermaid-svg-OpgiH3Uxet4E9GQM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OpgiH3Uxet4E9GQM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OpgiH3Uxet4E9GQM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OpgiH3Uxet4E9GQM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OpgiH3Uxet4E9GQM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OpgiH3Uxet4E9GQM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OpgiH3Uxet4E9GQM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OpgiH3Uxet4E9GQM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OpgiH3Uxet4E9GQM .marker.cross{stroke:#333333;}#mermaid-svg-OpgiH3Uxet4E9GQM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OpgiH3Uxet4E9GQM p{margin:0;}#mermaid-svg-OpgiH3Uxet4E9GQM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OpgiH3Uxet4E9GQM .cluster-label text{fill:#333;}#mermaid-svg-OpgiH3Uxet4E9GQM .cluster-label span{color:#333;}#mermaid-svg-OpgiH3Uxet4E9GQM .cluster-label span p{background-color:transparent;}#mermaid-svg-OpgiH3Uxet4E9GQM .label text,#mermaid-svg-OpgiH3Uxet4E9GQM span{fill:#333;color:#333;}#mermaid-svg-OpgiH3Uxet4E9GQM .node rect,#mermaid-svg-OpgiH3Uxet4E9GQM .node circle,#mermaid-svg-OpgiH3Uxet4E9GQM .node ellipse,#mermaid-svg-OpgiH3Uxet4E9GQM .node polygon,#mermaid-svg-OpgiH3Uxet4E9GQM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OpgiH3Uxet4E9GQM .rough-node .label text,#mermaid-svg-OpgiH3Uxet4E9GQM .node .label text,#mermaid-svg-OpgiH3Uxet4E9GQM .image-shape .label,#mermaid-svg-OpgiH3Uxet4E9GQM .icon-shape .label{text-anchor:middle;}#mermaid-svg-OpgiH3Uxet4E9GQM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OpgiH3Uxet4E9GQM .rough-node .label,#mermaid-svg-OpgiH3Uxet4E9GQM .node .label,#mermaid-svg-OpgiH3Uxet4E9GQM .image-shape .label,#mermaid-svg-OpgiH3Uxet4E9GQM .icon-shape .label{text-align:center;}#mermaid-svg-OpgiH3Uxet4E9GQM .node.clickable{cursor:pointer;}#mermaid-svg-OpgiH3Uxet4E9GQM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OpgiH3Uxet4E9GQM .arrowheadPath{fill:#333333;}#mermaid-svg-OpgiH3Uxet4E9GQM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OpgiH3Uxet4E9GQM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OpgiH3Uxet4E9GQM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OpgiH3Uxet4E9GQM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OpgiH3Uxet4E9GQM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OpgiH3Uxet4E9GQM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OpgiH3Uxet4E9GQM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OpgiH3Uxet4E9GQM .cluster text{fill:#333;}#mermaid-svg-OpgiH3Uxet4E9GQM .cluster span{color:#333;}#mermaid-svg-OpgiH3Uxet4E9GQM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OpgiH3Uxet4E9GQM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OpgiH3Uxet4E9GQM rect.text{fill:none;stroke-width:0;}#mermaid-svg-OpgiH3Uxet4E9GQM .icon-shape,#mermaid-svg-OpgiH3Uxet4E9GQM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OpgiH3Uxet4E9GQM .icon-shape p,#mermaid-svg-OpgiH3Uxet4E9GQM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OpgiH3Uxet4E9GQM .icon-shape .label rect,#mermaid-svg-OpgiH3Uxet4E9GQM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OpgiH3Uxet4E9GQM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OpgiH3Uxet4E9GQM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OpgiH3Uxet4E9GQM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ECS 架构
Entity
实体(仅ID)
Position
Velocity
Sprite
MovementSystem
RenderSystem
Component
组件(纯数据)
System
系统(纯逻辑)
核心概念
cpp
// ============ 1. Entity(实体):仅为ID ============
struct Entity
{
uint32_t index;
uint32_t generation;
bool operator==(const Entity& other) const
{
return index == other.index && generation == other.generation;
}
};
// ============ 2. Component(组件):纯数据 ============
struct Position { float x, y; };
struct Velocity { float vx, vy; };
struct Sprite { std::string textureName; int w, h; };
struct Health { int current, max; };
// ============ 3. System(系统):纯逻辑 ============
class MovementSystem
{
public:
void Update(ArchetypeManager& am, float dt)
{
am.ForEach<Position, Velocity>([dt](Position& pos, Velocity& vel)
{
pos.x += vel.vx * dt;
pos.y += vel.vy * dt;
});
}
};
现代 ECS(Archetype/Chunk模型)
#mermaid-svg-EJn5UBRKf5CF7Ykr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EJn5UBRKf5CF7Ykr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EJn5UBRKf5CF7Ykr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EJn5UBRKf5CF7Ykr .error-icon{fill:#552222;}#mermaid-svg-EJn5UBRKf5CF7Ykr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EJn5UBRKf5CF7Ykr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EJn5UBRKf5CF7Ykr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EJn5UBRKf5CF7Ykr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EJn5UBRKf5CF7Ykr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EJn5UBRKf5CF7Ykr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EJn5UBRKf5CF7Ykr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EJn5UBRKf5CF7Ykr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EJn5UBRKf5CF7Ykr .marker.cross{stroke:#333333;}#mermaid-svg-EJn5UBRKf5CF7Ykr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EJn5UBRKf5CF7Ykr p{margin:0;}#mermaid-svg-EJn5UBRKf5CF7Ykr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EJn5UBRKf5CF7Ykr .cluster-label text{fill:#333;}#mermaid-svg-EJn5UBRKf5CF7Ykr .cluster-label span{color:#333;}#mermaid-svg-EJn5UBRKf5CF7Ykr .cluster-label span p{background-color:transparent;}#mermaid-svg-EJn5UBRKf5CF7Ykr .label text,#mermaid-svg-EJn5UBRKf5CF7Ykr span{fill:#333;color:#333;}#mermaid-svg-EJn5UBRKf5CF7Ykr .node rect,#mermaid-svg-EJn5UBRKf5CF7Ykr .node circle,#mermaid-svg-EJn5UBRKf5CF7Ykr .node ellipse,#mermaid-svg-EJn5UBRKf5CF7Ykr .node polygon,#mermaid-svg-EJn5UBRKf5CF7Ykr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EJn5UBRKf5CF7Ykr .rough-node .label text,#mermaid-svg-EJn5UBRKf5CF7Ykr .node .label text,#mermaid-svg-EJn5UBRKf5CF7Ykr .image-shape .label,#mermaid-svg-EJn5UBRKf5CF7Ykr .icon-shape .label{text-anchor:middle;}#mermaid-svg-EJn5UBRKf5CF7Ykr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EJn5UBRKf5CF7Ykr .rough-node .label,#mermaid-svg-EJn5UBRKf5CF7Ykr .node .label,#mermaid-svg-EJn5UBRKf5CF7Ykr .image-shape .label,#mermaid-svg-EJn5UBRKf5CF7Ykr .icon-shape .label{text-align:center;}#mermaid-svg-EJn5UBRKf5CF7Ykr .node.clickable{cursor:pointer;}#mermaid-svg-EJn5UBRKf5CF7Ykr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EJn5UBRKf5CF7Ykr .arrowheadPath{fill:#333333;}#mermaid-svg-EJn5UBRKf5CF7Ykr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EJn5UBRKf5CF7Ykr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EJn5UBRKf5CF7Ykr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EJn5UBRKf5CF7Ykr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EJn5UBRKf5CF7Ykr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EJn5UBRKf5CF7Ykr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EJn5UBRKf5CF7Ykr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EJn5UBRKf5CF7Ykr .cluster text{fill:#333;}#mermaid-svg-EJn5UBRKf5CF7Ykr .cluster span{color:#333;}#mermaid-svg-EJn5UBRKf5CF7Ykr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EJn5UBRKf5CF7Ykr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EJn5UBRKf5CF7Ykr rect.text{fill:none;stroke-width:0;}#mermaid-svg-EJn5UBRKf5CF7Ykr .icon-shape,#mermaid-svg-EJn5UBRKf5CF7Ykr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EJn5UBRKf5CF7Ykr .icon-shape p,#mermaid-svg-EJn5UBRKf5CF7Ykr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EJn5UBRKf5CF7Ykr .icon-shape .label rect,#mermaid-svg-EJn5UBRKf5CF7Ykr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EJn5UBRKf5CF7Ykr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EJn5UBRKf5CF7Ykr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EJn5UBRKf5CF7Ykr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Archetype 模型
Chunk(固定大小内存块)
查询: Position + Velocity
查询: Position + Sprite
Archetype A
Position + Velocity
Archetype B
Position + Sprite
Entity\[\] (Index+Gen)
Position\[\] (连续数组)
Velocity\[\] (连续数组)
MovementSystem
RenderSystem
ArchetypeManager 简化实现
cpp
class ArchetypeManager
{
private:
struct Archetype
{
std::bitset<32> mask;
std::vector<uint8_t> buffer;
size_t entityCount;
};
std::vector<Archetype> archetypes;
template<typename T>
static constexpr size_t GetComponentId() { return 0; }
public:
template<typename... Comps>
void ForEach(std::function<void(Comps&...)> callback)
{
std::bitset<32> requiredMask;
(requiredMask.set(GetComponentId<Comps>()), ...);
for (auto& arch : archetypes)
{
if ((arch.mask & requiredMask) != requiredMask) continue;
size_t offset = 0;
for (size_t i = 0; i < arch.entityCount; ++i)
{
size_t localOff = offset;
callback(*reinterpret_cast<Comps*>(arch.buffer.data() + localOff)...);
((localOff += sizeof(Comps)), ...);
}
}
}
};
对比:OOP vs ECS
| 维度 | OOP | ECS |
|---|---|---|
| 实体定义 | 对象实例 | 数字ID + Generation |
| 数据存储 | 分散在对象中 | 连续数组(SoA) |
| 逻辑组织 | 类方法 | 独立System |
| 缓存效率 | 低 | 极高 |
| 扩展方式 | 继承/组合 | 新增Component/System |
优缺点
| 优点 | 缺点 |
|---|---|
| 数据与逻辑完全分离 | 学习曲线陡峭 |
| 天然缓存友好 | 小项目过度设计 |
| 易于扩展新功能 | 组件通信复杂 |
| 适合大型复杂游戏 | 调试困难 |
| 便于多线程并行 | 某些模式难以表达 |
5. Ability System(能力系统)
概述
Ability System(技能能力系统)是现代商业游戏(尤其是UE项目)中管理技能、Buff、状态效果的核心架构,以数据驱动组装替代传统硬编码技能逻辑。
UE的 Gameplay Ability System(GAS) 是其代表实现,广泛应用于MMO、ARPG、MOBA等品类。
架构图
#mermaid-svg-YdKRHfiKdxil4BNK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YdKRHfiKdxil4BNK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YdKRHfiKdxil4BNK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YdKRHfiKdxil4BNK .error-icon{fill:#552222;}#mermaid-svg-YdKRHfiKdxil4BNK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YdKRHfiKdxil4BNK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YdKRHfiKdxil4BNK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YdKRHfiKdxil4BNK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YdKRHfiKdxil4BNK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YdKRHfiKdxil4BNK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YdKRHfiKdxil4BNK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YdKRHfiKdxil4BNK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YdKRHfiKdxil4BNK .marker.cross{stroke:#333333;}#mermaid-svg-YdKRHfiKdxil4BNK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YdKRHfiKdxil4BNK p{margin:0;}#mermaid-svg-YdKRHfiKdxil4BNK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YdKRHfiKdxil4BNK .cluster-label text{fill:#333;}#mermaid-svg-YdKRHfiKdxil4BNK .cluster-label span{color:#333;}#mermaid-svg-YdKRHfiKdxil4BNK .cluster-label span p{background-color:transparent;}#mermaid-svg-YdKRHfiKdxil4BNK .label text,#mermaid-svg-YdKRHfiKdxil4BNK span{fill:#333;color:#333;}#mermaid-svg-YdKRHfiKdxil4BNK .node rect,#mermaid-svg-YdKRHfiKdxil4BNK .node circle,#mermaid-svg-YdKRHfiKdxil4BNK .node ellipse,#mermaid-svg-YdKRHfiKdxil4BNK .node polygon,#mermaid-svg-YdKRHfiKdxil4BNK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YdKRHfiKdxil4BNK .rough-node .label text,#mermaid-svg-YdKRHfiKdxil4BNK .node .label text,#mermaid-svg-YdKRHfiKdxil4BNK .image-shape .label,#mermaid-svg-YdKRHfiKdxil4BNK .icon-shape .label{text-anchor:middle;}#mermaid-svg-YdKRHfiKdxil4BNK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YdKRHfiKdxil4BNK .rough-node .label,#mermaid-svg-YdKRHfiKdxil4BNK .node .label,#mermaid-svg-YdKRHfiKdxil4BNK .image-shape .label,#mermaid-svg-YdKRHfiKdxil4BNK .icon-shape .label{text-align:center;}#mermaid-svg-YdKRHfiKdxil4BNK .node.clickable{cursor:pointer;}#mermaid-svg-YdKRHfiKdxil4BNK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YdKRHfiKdxil4BNK .arrowheadPath{fill:#333333;}#mermaid-svg-YdKRHfiKdxil4BNK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YdKRHfiKdxil4BNK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YdKRHfiKdxil4BNK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YdKRHfiKdxil4BNK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YdKRHfiKdxil4BNK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YdKRHfiKdxil4BNK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YdKRHfiKdxil4BNK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YdKRHfiKdxil4BNK .cluster text{fill:#333;}#mermaid-svg-YdKRHfiKdxil4BNK .cluster span{color:#333;}#mermaid-svg-YdKRHfiKdxil4BNK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YdKRHfiKdxil4BNK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YdKRHfiKdxil4BNK rect.text{fill:none;stroke-width:0;}#mermaid-svg-YdKRHfiKdxil4BNK .icon-shape,#mermaid-svg-YdKRHfiKdxil4BNK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YdKRHfiKdxil4BNK .icon-shape p,#mermaid-svg-YdKRHfiKdxil4BNK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YdKRHfiKdxil4BNK .icon-shape .label rect,#mermaid-svg-YdKRHfiKdxil4BNK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YdKRHfiKdxil4BNK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YdKRHfiKdxil4BNK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YdKRHfiKdxil4BNK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Ability System 核心概念
拥有/管理
应用
触发
消耗/条件
修改属性
产生
AbilitySystemComponent
能力系统组件
负责: 注册/激活/应用
GameplayAbility
技能/能力
生命周期: 激活→执行→结束
GameplayEffect
效果/Buff/伤害
Instant/Duration/Infinite
GameplayCue
视觉/音效反馈
轻量级通知
GameplayTag
标签系统
分类/条件/限制
AttributeSet
属性集
HP/MP/ATK等
技能生命周期
GC AttributeSet GameplayEffect GameplayAbility AbilitySystemComponent 玩家输入 GC AttributeSet GameplayEffect GameplayAbility AbilitySystemComponent 玩家输入 #mermaid-svg-UbP5A9yh0o3IrLXq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UbP5A9yh0o3IrLXq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UbP5A9yh0o3IrLXq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UbP5A9yh0o3IrLXq .error-icon{fill:#552222;}#mermaid-svg-UbP5A9yh0o3IrLXq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UbP5A9yh0o3IrLXq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UbP5A9yh0o3IrLXq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UbP5A9yh0o3IrLXq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UbP5A9yh0o3IrLXq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UbP5A9yh0o3IrLXq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UbP5A9yh0o3IrLXq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UbP5A9yh0o3IrLXq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UbP5A9yh0o3IrLXq .marker.cross{stroke:#333333;}#mermaid-svg-UbP5A9yh0o3IrLXq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UbP5A9yh0o3IrLXq p{margin:0;}#mermaid-svg-UbP5A9yh0o3IrLXq .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UbP5A9yh0o3IrLXq text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-UbP5A9yh0o3IrLXq .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-UbP5A9yh0o3IrLXq .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-UbP5A9yh0o3IrLXq .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-UbP5A9yh0o3IrLXq .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-UbP5A9yh0o3IrLXq #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-UbP5A9yh0o3IrLXq .sequenceNumber{fill:white;}#mermaid-svg-UbP5A9yh0o3IrLXq #sequencenumber{fill:#333;}#mermaid-svg-UbP5A9yh0o3IrLXq #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-UbP5A9yh0o3IrLXq .messageText{fill:#333;stroke:none;}#mermaid-svg-UbP5A9yh0o3IrLXq .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UbP5A9yh0o3IrLXq .labelText,#mermaid-svg-UbP5A9yh0o3IrLXq .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-UbP5A9yh0o3IrLXq .loopText,#mermaid-svg-UbP5A9yh0o3IrLXq .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-UbP5A9yh0o3IrLXq .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-UbP5A9yh0o3IrLXq .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-UbP5A9yh0o3IrLXq .noteText,#mermaid-svg-UbP5A9yh0o3IrLXq .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-UbP5A9yh0o3IrLXq .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UbP5A9yh0o3IrLXq .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UbP5A9yh0o3IrLXq .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UbP5A9yh0o3IrLXq .actorPopupMenu{position:absolute;}#mermaid-svg-UbP5A9yh0o3IrLXq .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-UbP5A9yh0o3IrLXq .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UbP5A9yh0o3IrLXq .actor-man circle,#mermaid-svg-UbP5A9yh0o3IrLXq line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-UbP5A9yh0o3IrLXq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 激活技能 CanActivate(检查Tag/消耗) 扣资源/CD 播放前摇动画 应用伤害/效果 修改属性 触发反馈 结束生命周期
C++ 简化实现
cpp
// ============ AttributeSet(属性集) ============
struct AttributeSet
{
float maxHp = 100;
float currentHp = 100;
float maxMana = 50;
float currentMana = 50;
float attackPower = 10;
float defensePower = 5;
float moveSpeed = 300;
};
// ============ GameplayTag(标签) ============
struct GameplayTag
{
std::string tag;
bool Matches(const std::string& pattern) const
{
return tag == pattern || tag.find(pattern) == 0;
}
};
// ============ GameplayEffect = 效果/Buff系统 ============
enum class EffectPolicy
{
Instant,
Duration,
Infinite
};
enum class AttributeType
{
MaxHp, CurrentHp, MaxMana, CurrentMana,
AttackPower, DefensePower, MoveSpeed
};
using TagContainer = std::vector<GameplayTag>;
enum class ModifierOp { Add, Multiply, Override };
struct ModifierOperation
{
AttributeType targetAttribute;
float value;
ModifierOp op;
};
class GameplayEffect
{
public:
std::string name;
EffectPolicy policy;
float duration;
std::vector<ModifierOperation> modifiers;
std::vector<GameplayTag> grantedTags;
std::vector<GameplayTag> requiredTags;
bool CanApplyTo(const TagContainer& tags) const
{
for (auto& req : requiredTags)
{
bool found = false;
for (auto& t : tags)
{
if (t.tag == req.tag) { found = true; break; }
}
if (!found) return false;
}
return true;
}
void Execute(AttributeSet& attributes) const
{
for (auto& mod : modifiers)
{
float* attr = nullptr;
switch (mod.targetAttribute)
{
case AttributeType::CurrentHp: attr = &attributes.currentHp; break;
case AttributeType::MaxHp: attr = &attributes.maxHp; break;
case AttributeType::CurrentMana: attr = &attributes.currentMana; break;
case AttributeType::MaxMana: attr = &attributes.maxMana; break;
case AttributeType::AttackPower: attr = &attributes.attackPower; break;
case AttributeType::DefensePower: attr = &attributes.defensePower; break;
case AttributeType::MoveSpeed: attr = &attributes.moveSpeed; break;
}
if (!attr) continue;
if (mod.op == ModifierOp::Add) *attr += mod.value;
else if (mod.op == ModifierOp::Multiply) *attr *= mod.value;
else if (mod.op == ModifierOp::Override) *attr = mod.value;
}
}
};
// ============ GameplayAbility = 技能 ============
class GameplayAbility
{
public:
std::string name;
float cooldown = 0;
float manaCost = 0;
float castTime = 0;
GameplayTag abilityTag;
std::vector<GameplayTag> requiredTags;
std::vector<GameplayTag> blockedTags;
std::vector<GameplayTag> cancelTags;
std::vector<std::shared_ptr<GameplayEffect>> effects;
virtual bool CanActivate(const TagContainer& ownerTags) const
{
for (auto& req : requiredTags)
{
bool found = false;
for (auto& t : ownerTags)
{
if (t.tag == req.tag) { found = true; break; }
}
if (!found) return false;
}
for (auto& blocked : blockedTags)
{
for (auto& t : ownerTags)
{
if (t.tag == blocked.tag) return false;
}
}
return true;
}
virtual void OnActivate(AttributeSet& attributes)
{
attributes.currentMana -= manaCost;
}
virtual void OnExecute(AttributeSet& attributes)
{
for (auto& effect : effects)
effect->Execute(attributes);
}
virtual void OnEnd() {}
virtual void OnCancel() {}
};
// ============ AbilitySystemComponent = 核心管理器 ============
class AbilitySystemComponent
{
private:
std::vector<std::shared_ptr<GameplayAbility>> abilities;
std::vector<std::shared_ptr<GameplayEffect>> activeEffects;
AttributeSet attributes;
TagContainer tags;
public:
void GiveAbility(std::shared_ptr<GameplayAbility> ability)
{
abilities.push_back(std::move(ability));
}
bool TryActivateAbility(const std::string& abilityName)
{
for (auto& ability : abilities)
{
if (ability->name == abilityName && ability->CanActivate(tags))
{
ability->OnActivate(attributes);
return true;
}
}
return false;
}
void ApplyEffect(std::shared_ptr<GameplayEffect> effect)
{
effect->Execute(attributes);
if (effect->policy == EffectPolicy::Duration)
{
activeEffects.push_back(std::move(effect));
}
}
void TickEffects(float dt)
{
for (auto it = activeEffects.begin(); it != activeEffects.end(); )
{
auto& effect = *it;
if (effect->policy == EffectPolicy::Duration)
{
effect->duration -= dt;
if (effect->duration <= 0)
{
it = activeEffects.erase(it);
continue;
}
effect->Execute(attributes);
}
++it;
}
}
};
技能定义示例
cpp
auto fireball = std::make_shared<GameplayAbility>();
fireball->name = "Fireball";
fireball->manaCost = 20;
fireball->cooldown = 3.0f;
fireball->castTime = 1.5f;
fireball->abilityTag = GameplayTag{"Ability.Fireball"};
fireball->blockedTags = { GameplayTag{"State.Stun"}, GameplayTag{"State.Silence"} };
auto fireDamage = std::make_shared<GameplayEffect>();
fireDamage->name = "FireDamage";
fireDamage->policy = EffectPolicy::Instant;
fireDamage->modifiers = {
{ AttributeType::CurrentHp, -30, ModifierOp::Add }
};
fireball->effects.push_back(fireDamage);
auto burnEffect = std::make_shared<GameplayEffect>();
burnEffect->name = "Burn";
burnEffect->policy = EffectPolicy::Duration;
burnEffect->duration = 5.0f;
burnEffect->modifiers =
{
{ AttributeType::CurrentHp, -5, ModifierOp::Add }
};
fireball->effects.push_back(burnEffect);
GAS 核心流程
#mermaid-svg-xXR0NWmMLLBvMdcm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xXR0NWmMLLBvMdcm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xXR0NWmMLLBvMdcm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xXR0NWmMLLBvMdcm .error-icon{fill:#552222;}#mermaid-svg-xXR0NWmMLLBvMdcm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xXR0NWmMLLBvMdcm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xXR0NWmMLLBvMdcm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xXR0NWmMLLBvMdcm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xXR0NWmMLLBvMdcm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xXR0NWmMLLBvMdcm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xXR0NWmMLLBvMdcm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xXR0NWmMLLBvMdcm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xXR0NWmMLLBvMdcm .marker.cross{stroke:#333333;}#mermaid-svg-xXR0NWmMLLBvMdcm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xXR0NWmMLLBvMdcm p{margin:0;}#mermaid-svg-xXR0NWmMLLBvMdcm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xXR0NWmMLLBvMdcm .cluster-label text{fill:#333;}#mermaid-svg-xXR0NWmMLLBvMdcm .cluster-label span{color:#333;}#mermaid-svg-xXR0NWmMLLBvMdcm .cluster-label span p{background-color:transparent;}#mermaid-svg-xXR0NWmMLLBvMdcm .label text,#mermaid-svg-xXR0NWmMLLBvMdcm span{fill:#333;color:#333;}#mermaid-svg-xXR0NWmMLLBvMdcm .node rect,#mermaid-svg-xXR0NWmMLLBvMdcm .node circle,#mermaid-svg-xXR0NWmMLLBvMdcm .node ellipse,#mermaid-svg-xXR0NWmMLLBvMdcm .node polygon,#mermaid-svg-xXR0NWmMLLBvMdcm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xXR0NWmMLLBvMdcm .rough-node .label text,#mermaid-svg-xXR0NWmMLLBvMdcm .node .label text,#mermaid-svg-xXR0NWmMLLBvMdcm .image-shape .label,#mermaid-svg-xXR0NWmMLLBvMdcm .icon-shape .label{text-anchor:middle;}#mermaid-svg-xXR0NWmMLLBvMdcm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xXR0NWmMLLBvMdcm .rough-node .label,#mermaid-svg-xXR0NWmMLLBvMdcm .node .label,#mermaid-svg-xXR0NWmMLLBvMdcm .image-shape .label,#mermaid-svg-xXR0NWmMLLBvMdcm .icon-shape .label{text-align:center;}#mermaid-svg-xXR0NWmMLLBvMdcm .node.clickable{cursor:pointer;}#mermaid-svg-xXR0NWmMLLBvMdcm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xXR0NWmMLLBvMdcm .arrowheadPath{fill:#333333;}#mermaid-svg-xXR0NWmMLLBvMdcm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xXR0NWmMLLBvMdcm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xXR0NWmMLLBvMdcm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xXR0NWmMLLBvMdcm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xXR0NWmMLLBvMdcm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xXR0NWmMLLBvMdcm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xXR0NWmMLLBvMdcm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xXR0NWmMLLBvMdcm .cluster text{fill:#333;}#mermaid-svg-xXR0NWmMLLBvMdcm .cluster span{color:#333;}#mermaid-svg-xXR0NWmMLLBvMdcm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xXR0NWmMLLBvMdcm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xXR0NWmMLLBvMdcm rect.text{fill:none;stroke-width:0;}#mermaid-svg-xXR0NWmMLLBvMdcm .icon-shape,#mermaid-svg-xXR0NWmMLLBvMdcm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xXR0NWmMLLBvMdcm .icon-shape p,#mermaid-svg-xXR0NWmMLLBvMdcm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xXR0NWmMLLBvMdcm .icon-shape .label rect,#mermaid-svg-xXR0NWmMLLBvMdcm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xXR0NWmMLLBvMdcm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xXR0NWmMLLBvMdcm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xXR0NWmMLLBvMdcm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Effect 生命周期
Instant
瞬间生效
GameplayEffect
Duration
周期性执行
到期移除
Infinite
手动移除
GAS 运行时流程
成功
失败
激活技能
检查条件
资源/Tag/CD
提交消耗
冷却/资源
结束/等待
执行技能
应用Effect
优缺点
| 优点 | 缺点 |
|---|---|
| 数据驱动,技能热更新 | 学习曲线陡峭 |
| 逻辑与数据分离 | 调试复杂(多层嵌套Effect) |
| Tag系统灵活组合 | 设计过度抽象的风险 |
| 优秀的Buff/状态管理 | 小项目过度工程化 |
| 社区已验证(Fortnite/帕鲁) | Effect间交互逻辑复杂 |
| UE原生支持(GAS Plugin) | 仅在UE生态成熟 |
与ECS/Component的关系
#mermaid-svg-XsuMWHVQEzrzPwp8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XsuMWHVQEzrzPwp8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XsuMWHVQEzrzPwp8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XsuMWHVQEzrzPwp8 .error-icon{fill:#552222;}#mermaid-svg-XsuMWHVQEzrzPwp8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XsuMWHVQEzrzPwp8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XsuMWHVQEzrzPwp8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XsuMWHVQEzrzPwp8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XsuMWHVQEzrzPwp8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XsuMWHVQEzrzPwp8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XsuMWHVQEzrzPwp8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XsuMWHVQEzrzPwp8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XsuMWHVQEzrzPwp8 .marker.cross{stroke:#333333;}#mermaid-svg-XsuMWHVQEzrzPwp8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XsuMWHVQEzrzPwp8 p{margin:0;}#mermaid-svg-XsuMWHVQEzrzPwp8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XsuMWHVQEzrzPwp8 .cluster-label text{fill:#333;}#mermaid-svg-XsuMWHVQEzrzPwp8 .cluster-label span{color:#333;}#mermaid-svg-XsuMWHVQEzrzPwp8 .cluster-label span p{background-color:transparent;}#mermaid-svg-XsuMWHVQEzrzPwp8 .label text,#mermaid-svg-XsuMWHVQEzrzPwp8 span{fill:#333;color:#333;}#mermaid-svg-XsuMWHVQEzrzPwp8 .node rect,#mermaid-svg-XsuMWHVQEzrzPwp8 .node circle,#mermaid-svg-XsuMWHVQEzrzPwp8 .node ellipse,#mermaid-svg-XsuMWHVQEzrzPwp8 .node polygon,#mermaid-svg-XsuMWHVQEzrzPwp8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XsuMWHVQEzrzPwp8 .rough-node .label text,#mermaid-svg-XsuMWHVQEzrzPwp8 .node .label text,#mermaid-svg-XsuMWHVQEzrzPwp8 .image-shape .label,#mermaid-svg-XsuMWHVQEzrzPwp8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-XsuMWHVQEzrzPwp8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XsuMWHVQEzrzPwp8 .rough-node .label,#mermaid-svg-XsuMWHVQEzrzPwp8 .node .label,#mermaid-svg-XsuMWHVQEzrzPwp8 .image-shape .label,#mermaid-svg-XsuMWHVQEzrzPwp8 .icon-shape .label{text-align:center;}#mermaid-svg-XsuMWHVQEzrzPwp8 .node.clickable{cursor:pointer;}#mermaid-svg-XsuMWHVQEzrzPwp8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XsuMWHVQEzrzPwp8 .arrowheadPath{fill:#333333;}#mermaid-svg-XsuMWHVQEzrzPwp8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XsuMWHVQEzrzPwp8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XsuMWHVQEzrzPwp8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XsuMWHVQEzrzPwp8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XsuMWHVQEzrzPwp8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XsuMWHVQEzrzPwp8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XsuMWHVQEzrzPwp8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XsuMWHVQEzrzPwp8 .cluster text{fill:#333;}#mermaid-svg-XsuMWHVQEzrzPwp8 .cluster span{color:#333;}#mermaid-svg-XsuMWHVQEzrzPwp8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XsuMWHVQEzrzPwp8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XsuMWHVQEzrzPwp8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-XsuMWHVQEzrzPwp8 .icon-shape,#mermaid-svg-XsuMWHVQEzrzPwp8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XsuMWHVQEzrzPwp8 .icon-shape p,#mermaid-svg-XsuMWHVQEzrzPwp8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XsuMWHVQEzrzPwp8 .icon-shape .label rect,#mermaid-svg-XsuMWHVQEzrzPwp8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XsuMWHVQEzrzPwp8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XsuMWHVQEzrzPwp8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XsuMWHVQEzrzPwp8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 影响
限制
修改
Actor
角色
AbilitySystemComponent
能力组件
HealthComponent
血量组件
MovementComponent
移动组件
技能库
活跃Buff列表
AttributeSet
属性集
GameplayTags
标签集
GAS可以看作 Component 模式的进阶应用,用标签(Tag)驱动数值变化和状态管理,适合MMO/ARPG等技能机制复杂的项目。
6. MVC(模型-视图-控制器)
概述
MVC 将应用程序分为三个核心部分:Model(数据与业务逻辑)、View(界面展示)、Controller(输入处理与协调)。
架构图
Model (模型) Controller (控制器) View (视图) 用户 Model (模型) Controller (控制器) View (视图) 用户 #mermaid-svg-ZTkASm6G7KABpRvj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZTkASm6G7KABpRvj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZTkASm6G7KABpRvj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZTkASm6G7KABpRvj .error-icon{fill:#552222;}#mermaid-svg-ZTkASm6G7KABpRvj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZTkASm6G7KABpRvj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZTkASm6G7KABpRvj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZTkASm6G7KABpRvj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZTkASm6G7KABpRvj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZTkASm6G7KABpRvj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZTkASm6G7KABpRvj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZTkASm6G7KABpRvj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZTkASm6G7KABpRvj .marker.cross{stroke:#333333;}#mermaid-svg-ZTkASm6G7KABpRvj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZTkASm6G7KABpRvj p{margin:0;}#mermaid-svg-ZTkASm6G7KABpRvj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZTkASm6G7KABpRvj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ZTkASm6G7KABpRvj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZTkASm6G7KABpRvj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ZTkASm6G7KABpRvj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ZTkASm6G7KABpRvj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ZTkASm6G7KABpRvj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ZTkASm6G7KABpRvj .sequenceNumber{fill:white;}#mermaid-svg-ZTkASm6G7KABpRvj #sequencenumber{fill:#333;}#mermaid-svg-ZTkASm6G7KABpRvj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ZTkASm6G7KABpRvj .messageText{fill:#333;stroke:none;}#mermaid-svg-ZTkASm6G7KABpRvj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZTkASm6G7KABpRvj .labelText,#mermaid-svg-ZTkASm6G7KABpRvj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ZTkASm6G7KABpRvj .loopText,#mermaid-svg-ZTkASm6G7KABpRvj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ZTkASm6G7KABpRvj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZTkASm6G7KABpRvj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ZTkASm6G7KABpRvj .noteText,#mermaid-svg-ZTkASm6G7KABpRvj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ZTkASm6G7KABpRvj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZTkASm6G7KABpRvj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZTkASm6G7KABpRvj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZTkASm6G7KABpRvj .actorPopupMenu{position:absolute;}#mermaid-svg-ZTkASm6G7KABpRvj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ZTkASm6G7KABpRvj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZTkASm6G7KABpRvj .actor-man circle,#mermaid-svg-ZTkASm6G7KABpRvj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ZTkASm6G7KABpRvj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 点击/按键 转发事件 更新数据 通知更新 刷新显示
C++ 实现
cpp
class Observer;
// ============ Model ============
class PlayerModel
{
private:
int hp;
int maxHp;
int mana;
std::vector<Observer*> observers;
public:
PlayerModel() : hp(100), maxHp(100), mana(50) {}
void TakeDamage(int damage)
{
hp = std::max(0, hp - damage);
NotifyObservers();
}
void Heal(int amount)
{
hp = std::min(maxHp, hp + amount);
NotifyObservers();
}
int GetHp() const { return hp; }
int GetMaxHp() const { return maxHp; }
void Attach(Observer* obs) { observers.push_back(obs); }
void NotifyObservers()
{
for (auto* obs : observers) obs->Update();
}
};
// ============ View ============
class HUDView : public Observer
{
private:
PlayerModel* model;
UIWidget* hpBar;
public:
HUDView(PlayerModel* m) : model(m)
{
hpBar = new UIProgressBar(100, 20);
}
void Update() override
{
float hpPercent = (float)model->GetHp() / (float)model->GetMaxHp();
hpBar->SetProgress(hpPercent);
}
void Render() { hpBar->Draw(); }
};
// ============ Controller ============
class GameController
{
private:
PlayerModel* model;
HUDView* view;
public:
GameController(PlayerModel* m, HUDView* v) : model(m), view(v)
{
model->Attach(view);
}
void HandleInput(InputEvent event)
{
switch (event.type)
{
case InputType::KEY_F:
model->Heal(25);
break;
}
}
};
优缺点
| 优点 | 缺点 |
|---|---|
| 关注点分离清晰 | 视图与模型仍有耦合 |
| 可同时开发多部分 | 控制器可能变得臃肿 |
| 便于测试(Model独立) | 简单UI过度设计 |
| 广泛应用,资料丰富 | 视图更新机制复杂 |
7. MVP(模型-视图-展示器)
概述
MVP 是 MVC 的变体,由 Presenter 完全接管视图逻辑,View 接口化,Model 与 View 完全解耦。
架构对比
#mermaid-svg-VVATLNqWSnbr14xW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VVATLNqWSnbr14xW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VVATLNqWSnbr14xW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VVATLNqWSnbr14xW .error-icon{fill:#552222;}#mermaid-svg-VVATLNqWSnbr14xW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VVATLNqWSnbr14xW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VVATLNqWSnbr14xW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VVATLNqWSnbr14xW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VVATLNqWSnbr14xW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VVATLNqWSnbr14xW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VVATLNqWSnbr14xW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VVATLNqWSnbr14xW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VVATLNqWSnbr14xW .marker.cross{stroke:#333333;}#mermaid-svg-VVATLNqWSnbr14xW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VVATLNqWSnbr14xW p{margin:0;}#mermaid-svg-VVATLNqWSnbr14xW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VVATLNqWSnbr14xW .cluster-label text{fill:#333;}#mermaid-svg-VVATLNqWSnbr14xW .cluster-label span{color:#333;}#mermaid-svg-VVATLNqWSnbr14xW .cluster-label span p{background-color:transparent;}#mermaid-svg-VVATLNqWSnbr14xW .label text,#mermaid-svg-VVATLNqWSnbr14xW span{fill:#333;color:#333;}#mermaid-svg-VVATLNqWSnbr14xW .node rect,#mermaid-svg-VVATLNqWSnbr14xW .node circle,#mermaid-svg-VVATLNqWSnbr14xW .node ellipse,#mermaid-svg-VVATLNqWSnbr14xW .node polygon,#mermaid-svg-VVATLNqWSnbr14xW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VVATLNqWSnbr14xW .rough-node .label text,#mermaid-svg-VVATLNqWSnbr14xW .node .label text,#mermaid-svg-VVATLNqWSnbr14xW .image-shape .label,#mermaid-svg-VVATLNqWSnbr14xW .icon-shape .label{text-anchor:middle;}#mermaid-svg-VVATLNqWSnbr14xW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VVATLNqWSnbr14xW .rough-node .label,#mermaid-svg-VVATLNqWSnbr14xW .node .label,#mermaid-svg-VVATLNqWSnbr14xW .image-shape .label,#mermaid-svg-VVATLNqWSnbr14xW .icon-shape .label{text-align:center;}#mermaid-svg-VVATLNqWSnbr14xW .node.clickable{cursor:pointer;}#mermaid-svg-VVATLNqWSnbr14xW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VVATLNqWSnbr14xW .arrowheadPath{fill:#333333;}#mermaid-svg-VVATLNqWSnbr14xW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VVATLNqWSnbr14xW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VVATLNqWSnbr14xW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VVATLNqWSnbr14xW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VVATLNqWSnbr14xW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VVATLNqWSnbr14xW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VVATLNqWSnbr14xW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VVATLNqWSnbr14xW .cluster text{fill:#333;}#mermaid-svg-VVATLNqWSnbr14xW .cluster span{color:#333;}#mermaid-svg-VVATLNqWSnbr14xW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VVATLNqWSnbr14xW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VVATLNqWSnbr14xW rect.text{fill:none;stroke-width:0;}#mermaid-svg-VVATLNqWSnbr14xW .icon-shape,#mermaid-svg-VVATLNqWSnbr14xW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VVATLNqWSnbr14xW .icon-shape p,#mermaid-svg-VVATLNqWSnbr14xW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VVATLNqWSnbr14xW .icon-shape .label rect,#mermaid-svg-VVATLNqWSnbr14xW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VVATLNqWSnbr14xW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VVATLNqWSnbr14xW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VVATLNqWSnbr14xW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MVP
通过接口
View Interface
Presenter
Model
MVC
View
Controller
Model
Presenter 核心实现
cpp
struct ItemData
{
std::string name;
int slotIndex;
int quantity;
bool isValid = false;
bool isUsable = false;
std::string GetDescription() const
{
return name + " x" + std::to_string(quantity);
}
};
class InventoryModel
{
private:
std::vector<ItemData> items;
public:
InventoryModel() : items(20)
{
for (int i = 0; i < 20; ++i) items[i].slotIndex = i;
}
ItemData GetItem(int slot) const
{
if (slot >= 0 && slot < (int)items.size() && items[slot].isValid)
return items[slot];
return ItemData{};
}
std::vector<ItemData> GetAllItems() const { return items; }
void RemoveItem(int slot)
{
if (slot >= 0 && slot < (int)items.size())
items[slot] = ItemData{};
}
};
// View 接口
class IInventoryView
{
public:
virtual ~IInventoryView() = default;
virtual void ShowItem(int slot, const ItemData& item) = 0;
virtual void ClearSlot(int slot) = 0;
virtual void HighlightSlot(int slot) = 0;
virtual void ShowTooltip(const std::string& text) = 0;
virtual void RefreshAll(const std::vector<ItemData>& items) = 0;
};
// Presenter:完全控制视图逻辑
class InventoryPresenter
{
private:
IInventoryView* view;
InventoryModel* model;
public:
void OnItemClicked(int slot)
{
ItemData item = model->GetItem(slot);
if (item.isValid) {
view->HighlightSlot(slot);
view->ShowTooltip(item.GetDescription());
}
}
void OnItemUsed(int slot)
{
if (model->GetItem(slot).isUsable)
{
model->RemoveItem(slot);
RefreshView();
}
}
private:
void RefreshView()
{
view->RefreshAll(model->GetAllItems());
}
};
优缺点
| 优点 | 缺点 |
|---|---|
| View完全与Model解耦 | Presenter变得庞大 |
| View易于替换和测试 | 接口数量多 |
| 测试性极佳(Mock View) | 简单场景过度抽象 |
8. MVVM(模型-视图-视图模型)
概述
MVVM 通过**数据绑定(Data Binding)**将 View 与 ViewModel 自动同步,ViewModel 暴露可观察的属性和命令。
架构图
#mermaid-svg-HSfFvwPsJgOvk941{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HSfFvwPsJgOvk941 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HSfFvwPsJgOvk941 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HSfFvwPsJgOvk941 .error-icon{fill:#552222;}#mermaid-svg-HSfFvwPsJgOvk941 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HSfFvwPsJgOvk941 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HSfFvwPsJgOvk941 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HSfFvwPsJgOvk941 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HSfFvwPsJgOvk941 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HSfFvwPsJgOvk941 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HSfFvwPsJgOvk941 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HSfFvwPsJgOvk941 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HSfFvwPsJgOvk941 .marker.cross{stroke:#333333;}#mermaid-svg-HSfFvwPsJgOvk941 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HSfFvwPsJgOvk941 p{margin:0;}#mermaid-svg-HSfFvwPsJgOvk941 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HSfFvwPsJgOvk941 .cluster-label text{fill:#333;}#mermaid-svg-HSfFvwPsJgOvk941 .cluster-label span{color:#333;}#mermaid-svg-HSfFvwPsJgOvk941 .cluster-label span p{background-color:transparent;}#mermaid-svg-HSfFvwPsJgOvk941 .label text,#mermaid-svg-HSfFvwPsJgOvk941 span{fill:#333;color:#333;}#mermaid-svg-HSfFvwPsJgOvk941 .node rect,#mermaid-svg-HSfFvwPsJgOvk941 .node circle,#mermaid-svg-HSfFvwPsJgOvk941 .node ellipse,#mermaid-svg-HSfFvwPsJgOvk941 .node polygon,#mermaid-svg-HSfFvwPsJgOvk941 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HSfFvwPsJgOvk941 .rough-node .label text,#mermaid-svg-HSfFvwPsJgOvk941 .node .label text,#mermaid-svg-HSfFvwPsJgOvk941 .image-shape .label,#mermaid-svg-HSfFvwPsJgOvk941 .icon-shape .label{text-anchor:middle;}#mermaid-svg-HSfFvwPsJgOvk941 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HSfFvwPsJgOvk941 .rough-node .label,#mermaid-svg-HSfFvwPsJgOvk941 .node .label,#mermaid-svg-HSfFvwPsJgOvk941 .image-shape .label,#mermaid-svg-HSfFvwPsJgOvk941 .icon-shape .label{text-align:center;}#mermaid-svg-HSfFvwPsJgOvk941 .node.clickable{cursor:pointer;}#mermaid-svg-HSfFvwPsJgOvk941 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HSfFvwPsJgOvk941 .arrowheadPath{fill:#333333;}#mermaid-svg-HSfFvwPsJgOvk941 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HSfFvwPsJgOvk941 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HSfFvwPsJgOvk941 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HSfFvwPsJgOvk941 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HSfFvwPsJgOvk941 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HSfFvwPsJgOvk941 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HSfFvwPsJgOvk941 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HSfFvwPsJgOvk941 .cluster text{fill:#333;}#mermaid-svg-HSfFvwPsJgOvk941 .cluster span{color:#333;}#mermaid-svg-HSfFvwPsJgOvk941 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HSfFvwPsJgOvk941 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HSfFvwPsJgOvk941 rect.text{fill:none;stroke-width:0;}#mermaid-svg-HSfFvwPsJgOvk941 .icon-shape,#mermaid-svg-HSfFvwPsJgOvk941 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HSfFvwPsJgOvk941 .icon-shape p,#mermaid-svg-HSfFvwPsJgOvk941 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HSfFvwPsJgOvk941 .icon-shape .label rect,#mermaid-svg-HSfFvwPsJgOvk941 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HSfFvwPsJgOvk941 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HSfFvwPsJgOvk941 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HSfFvwPsJgOvk941 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MVVM
数据绑定
命令绑定
Model
业务数据+逻辑
ViewModel
可观察属性
命令
View
UI绑定
ViewModel 实现
cpp
class ObservableObject
{
protected:
void OnPropertyChanged(const std::string& propName)
{
auto it = propertyListeners.find(propName);
if (it != propertyListeners.end())
{
for (auto& callback : it->second) callback();
}
}
public:
void BindProperty(const std::string& propName, std::function<void()> callback)
{
propertyListeners[propName].push_back(std::move(callback));
}
std::unordered_map<std::string, std::vector<std::function<void()>>> propertyListeners;
};
struct PlayerStatsModel
{
int baseHp = 100;
int currentHp = 100;
int maxHp = 100;
float mana = 50;
struct Equipment { std::string name; int hpBonus; };
std::vector<Equipment> equipment;
};
class PlayerViewModel : public ObservableObject
{
private:
PlayerStatsModel* model;
public:
std::string DisplayedHp;
std::string DisplayedMaxHp;
std::string DisplayedMana;
float HpPercent;
bool IsAlive;
std::function<void()> UseHealthPotion;
std::function<void(int)> EquipItem;
PlayerViewModel(PlayerStatsModel* m) : model(m)
{
UseHealthPotion = [this]()
{
model->currentHp = std::min(model->currentHp + 50, model->maxHp);
RefreshStats();
};
RefreshStats();
}
void RefreshStats()
{
int totalCurrentHp = model->currentHp;
int totalMaxHp = model->maxHp;
for (auto& eq : model->equipment) totalMaxHp += eq.hpBonus;
DisplayedHp = std::to_string(totalCurrentHp);
DisplayedMaxHp = std::to_string(totalMaxHp);
HpPercent = totalMaxHp > 0 ? (float)totalCurrentHp / (float)totalMaxHp : 0.0f;
IsAlive = totalCurrentHp > 0;
OnPropertyChanged("DisplayedHp");
OnPropertyChanged("DisplayedMaxHp");
OnPropertyChanged("HpPercent");
}
};
数据绑定
cpp
class UIBinder
{
public:
static void BindText(UILabel* label, PlayerViewModel* vm)
{
vm->BindProperty("DisplayedHp", [label, vm]() {
label->SetText(vm->DisplayedHp);
});
}
static void BindProgress(UIProgressBar* bar, PlayerViewModel* vm)
{
vm->BindProperty("HpPercent", [bar, vm]()
{
bar->SetProgress(vm->HpPercent);
});
}
};
优缺点
| 优点 | 缺点 |
|---|---|
| 双向数据绑定自动同步 | 数据绑定调试困难 |
| View与逻辑完全解耦 | 过度绑定导致性能问题 |
| 适合复杂UI交互 | 学习曲线较高 |
| 易于单元测试 | 内存泄漏风险(忘记解绑) |
游戏UI架构的实际情况
很多新人会以为"游戏UI一定用MVVM",实际上并非如此。
| 引擎/框架 | 官方推荐 | 项目实际 |
|---|---|---|
| UE UMG | MVVM(UE5.2 Experimental,UE5.3+逐渐成熟) | 大量项目用Widget+Controller,更接近MVP |
| Unity uGUI | 无限制 | MVC / MVP / MVVM 皆有 |
| Cocos Creator | 无限制 | 多为MVC变体 |
| 自研引擎 | 视团队而定 | 通常是混合体 |
实际工程中,MVC、MVP、MVVM往往是混合使用的,没有绝对界限。大型3A项目中自研UI框架的比例也不低(如GTA、巫师、战神等),并不一定使用MVVM。
9. Observer(观察者模式)与Event(事件系统)
概述
Observer模式和事件驱动架构是游戏系统间解耦通信的基础。UE中的Delegate/MulticastDelegate、各种事件总线都基于此。
观察者模式
#mermaid-svg-0O6z9HE3YLihuSx9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0O6z9HE3YLihuSx9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0O6z9HE3YLihuSx9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0O6z9HE3YLihuSx9 .error-icon{fill:#552222;}#mermaid-svg-0O6z9HE3YLihuSx9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0O6z9HE3YLihuSx9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0O6z9HE3YLihuSx9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0O6z9HE3YLihuSx9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0O6z9HE3YLihuSx9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0O6z9HE3YLihuSx9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0O6z9HE3YLihuSx9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0O6z9HE3YLihuSx9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0O6z9HE3YLihuSx9 .marker.cross{stroke:#333333;}#mermaid-svg-0O6z9HE3YLihuSx9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0O6z9HE3YLihuSx9 p{margin:0;}#mermaid-svg-0O6z9HE3YLihuSx9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0O6z9HE3YLihuSx9 .cluster-label text{fill:#333;}#mermaid-svg-0O6z9HE3YLihuSx9 .cluster-label span{color:#333;}#mermaid-svg-0O6z9HE3YLihuSx9 .cluster-label span p{background-color:transparent;}#mermaid-svg-0O6z9HE3YLihuSx9 .label text,#mermaid-svg-0O6z9HE3YLihuSx9 span{fill:#333;color:#333;}#mermaid-svg-0O6z9HE3YLihuSx9 .node rect,#mermaid-svg-0O6z9HE3YLihuSx9 .node circle,#mermaid-svg-0O6z9HE3YLihuSx9 .node ellipse,#mermaid-svg-0O6z9HE3YLihuSx9 .node polygon,#mermaid-svg-0O6z9HE3YLihuSx9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0O6z9HE3YLihuSx9 .rough-node .label text,#mermaid-svg-0O6z9HE3YLihuSx9 .node .label text,#mermaid-svg-0O6z9HE3YLihuSx9 .image-shape .label,#mermaid-svg-0O6z9HE3YLihuSx9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-0O6z9HE3YLihuSx9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0O6z9HE3YLihuSx9 .rough-node .label,#mermaid-svg-0O6z9HE3YLihuSx9 .node .label,#mermaid-svg-0O6z9HE3YLihuSx9 .image-shape .label,#mermaid-svg-0O6z9HE3YLihuSx9 .icon-shape .label{text-align:center;}#mermaid-svg-0O6z9HE3YLihuSx9 .node.clickable{cursor:pointer;}#mermaid-svg-0O6z9HE3YLihuSx9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0O6z9HE3YLihuSx9 .arrowheadPath{fill:#333333;}#mermaid-svg-0O6z9HE3YLihuSx9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0O6z9HE3YLihuSx9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0O6z9HE3YLihuSx9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0O6z9HE3YLihuSx9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0O6z9HE3YLihuSx9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0O6z9HE3YLihuSx9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0O6z9HE3YLihuSx9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0O6z9HE3YLihuSx9 .cluster text{fill:#333;}#mermaid-svg-0O6z9HE3YLihuSx9 .cluster span{color:#333;}#mermaid-svg-0O6z9HE3YLihuSx9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0O6z9HE3YLihuSx9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0O6z9HE3YLihuSx9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-0O6z9HE3YLihuSx9 .icon-shape,#mermaid-svg-0O6z9HE3YLihuSx9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0O6z9HE3YLihuSx9 .icon-shape p,#mermaid-svg-0O6z9HE3YLihuSx9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0O6z9HE3YLihuSx9 .icon-shape .label rect,#mermaid-svg-0O6z9HE3YLihuSx9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0O6z9HE3YLihuSx9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0O6z9HE3YLihuSx9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0O6z9HE3YLihuSx9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Observer 模式
Subject 主题
状态变化通知
Observer A
更新
Observer B
更新
Observer C
更新
C++ 实现
cpp
// ============ Observer 基类 ============
class Observer
{
public:
virtual ~Observer() = default;
virtual void OnNotify(const std::string& event, void* data) = 0;
virtual void Update() {}
};
// ============ Subject / 事件源 ============
class Subject
{
private:
std::vector<Observer*> observers;
public:
void Attach(Observer* obs) { observers.push_back(obs); }
void Detach(Observer* obs)
{
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void Notify(const std::string& event, void* data = nullptr)
{
for (auto* obs : observers)
{
obs->OnNotify(event, data);
}
}
};
// ============ UE风格:Delegate ============
template<typename... Args>
class MulticastDelegate
{
private:
std::vector<std::function<void(Args...)>> listeners;
public:
void AddListener(std::function<void(Args...)> callback)
{
listeners.push_back(std::move(callback));
}
void Broadcast(Args... args)
{
for (auto& listener : listeners)
{
listener(args...);
}
}
};
// 使用
MulticastDelegate<int> OnDamageTaken;
OnDamageTaken.AddListener([](int dmg)
{
// 更新血条
});
OnDamageTaken.AddListener([](int dmg)
{
// 播放受伤音效
});
OnDamageTaken.Broadcast(50);
事件驱动架构(Event Bus)
cpp
enum class EventType
{
DamageTaken, HealthChanged, PlayerDied,
EnemyKilled, ItemCollected, QuestUpdated,
GamePaused, GameSaved, LevelLoaded
};
struct Event
{
EventType type;
void* data = nullptr;
};
// ============ 事件总线(双缓冲队列) ============
class EventBus
{
private:
using EventHandler = std::function<void(const Event&)>;
std::unordered_map<EventType, std::vector<EventHandler>> listeners;
std::queue<Event> eventQueue;
std::queue<Event> pendingQueue;
public:
void Subscribe(EventType type, EventHandler handler)
{
listeners[type].push_back(handler);
}
void Publish(const Event& event)
{
auto it = listeners.find(event.type);
if (it != listeners.end())
{
for (auto& handler : it->second) handler(event);
}
}
void QueueEvent(const Event& event)
{
pendingQueue.push(event);
}
void DispatchQueuedEvents()
{
std::swap(eventQueue, pendingQueue);
while (!pendingQueue.empty())
{
pendingQueue.pop();
}
while (!eventQueue.empty())
{
Publish(eventQueue.front());
eventQueue.pop();
}
}
};
事件风暴(Event Storm)问题
#mermaid-svg-k8DwBETdovie0o67{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-k8DwBETdovie0o67 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-k8DwBETdovie0o67 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-k8DwBETdovie0o67 .error-icon{fill:#552222;}#mermaid-svg-k8DwBETdovie0o67 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-k8DwBETdovie0o67 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-k8DwBETdovie0o67 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-k8DwBETdovie0o67 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-k8DwBETdovie0o67 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-k8DwBETdovie0o67 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-k8DwBETdovie0o67 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-k8DwBETdovie0o67 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-k8DwBETdovie0o67 .marker.cross{stroke:#333333;}#mermaid-svg-k8DwBETdovie0o67 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-k8DwBETdovie0o67 p{margin:0;}#mermaid-svg-k8DwBETdovie0o67 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-k8DwBETdovie0o67 .cluster-label text{fill:#333;}#mermaid-svg-k8DwBETdovie0o67 .cluster-label span{color:#333;}#mermaid-svg-k8DwBETdovie0o67 .cluster-label span p{background-color:transparent;}#mermaid-svg-k8DwBETdovie0o67 .label text,#mermaid-svg-k8DwBETdovie0o67 span{fill:#333;color:#333;}#mermaid-svg-k8DwBETdovie0o67 .node rect,#mermaid-svg-k8DwBETdovie0o67 .node circle,#mermaid-svg-k8DwBETdovie0o67 .node ellipse,#mermaid-svg-k8DwBETdovie0o67 .node polygon,#mermaid-svg-k8DwBETdovie0o67 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-k8DwBETdovie0o67 .rough-node .label text,#mermaid-svg-k8DwBETdovie0o67 .node .label text,#mermaid-svg-k8DwBETdovie0o67 .image-shape .label,#mermaid-svg-k8DwBETdovie0o67 .icon-shape .label{text-anchor:middle;}#mermaid-svg-k8DwBETdovie0o67 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-k8DwBETdovie0o67 .rough-node .label,#mermaid-svg-k8DwBETdovie0o67 .node .label,#mermaid-svg-k8DwBETdovie0o67 .image-shape .label,#mermaid-svg-k8DwBETdovie0o67 .icon-shape .label{text-align:center;}#mermaid-svg-k8DwBETdovie0o67 .node.clickable{cursor:pointer;}#mermaid-svg-k8DwBETdovie0o67 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-k8DwBETdovie0o67 .arrowheadPath{fill:#333333;}#mermaid-svg-k8DwBETdovie0o67 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-k8DwBETdovie0o67 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-k8DwBETdovie0o67 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k8DwBETdovie0o67 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-k8DwBETdovie0o67 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k8DwBETdovie0o67 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-k8DwBETdovie0o67 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-k8DwBETdovie0o67 .cluster text{fill:#333;}#mermaid-svg-k8DwBETdovie0o67 .cluster span{color:#333;}#mermaid-svg-k8DwBETdovie0o67 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-k8DwBETdovie0o67 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-k8DwBETdovie0o67 rect.text{fill:none;stroke-width:0;}#mermaid-svg-k8DwBETdovie0o67 .icon-shape,#mermaid-svg-k8DwBETdovie0o67 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k8DwBETdovie0o67 .icon-shape p,#mermaid-svg-k8DwBETdovie0o67 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-k8DwBETdovie0o67 .icon-shape .label rect,#mermaid-svg-k8DwBETdovie0o67 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k8DwBETdovie0o67 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-k8DwBETdovie0o67 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-k8DwBETdovie0o67 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} DamageEvent
伤害事件
-> UIEvent
更新血条
-> SoundEvent
播放音效
-> QuestEvent
任务追踪
-> AchievementEvent
成就解锁
-> AnalyticsEvent
数据统计
... 更多级联...
⚠️ 事件链路过长
导致不可追踪
性能问题
事件驱动的主要性能问题不在于虚函数调用,而是:
- Cache Miss - 事件处理函数分散在不同内存位置
- 间接调用 - 函数指针/虚函数影响分支预测
- 事件风暴 - 单个事件触发链式反应,事件链路无限增长
优缺点
| 优点 | 缺点 |
|---|---|
| 系统间完全解耦 | 事件追踪困难 |
| 易于扩展新系统 | 循环事件风险 |
| 代码组织清晰 | 事件风暴(级联爆炸) |
| 支持异步处理 | 全局状态管理复杂 |
| 灵活的事件组合 | 调试困难(调用链不透明) |
10. FSM(有限状态机)
概述
FSM(Finite State Machine)是游戏AI和行为控制最经典的模式,实体在有限个状态间切换,每个状态有进入、更新、退出逻辑。
⚠️ 重要 :FSM更适合作为局部行为控制器,而非整个AI系统。实际商业游戏中,FSM常与Behavior Tree、HFSM、Utility AI等组合使用。
状态图
#mermaid-svg-MxqOxqBSwNTPo8pk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MxqOxqBSwNTPo8pk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MxqOxqBSwNTPo8pk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MxqOxqBSwNTPo8pk .error-icon{fill:#552222;}#mermaid-svg-MxqOxqBSwNTPo8pk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MxqOxqBSwNTPo8pk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MxqOxqBSwNTPo8pk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MxqOxqBSwNTPo8pk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MxqOxqBSwNTPo8pk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MxqOxqBSwNTPo8pk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MxqOxqBSwNTPo8pk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MxqOxqBSwNTPo8pk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MxqOxqBSwNTPo8pk .marker.cross{stroke:#333333;}#mermaid-svg-MxqOxqBSwNTPo8pk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MxqOxqBSwNTPo8pk p{margin:0;}#mermaid-svg-MxqOxqBSwNTPo8pk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MxqOxqBSwNTPo8pk .cluster-label text{fill:#333;}#mermaid-svg-MxqOxqBSwNTPo8pk .cluster-label span{color:#333;}#mermaid-svg-MxqOxqBSwNTPo8pk .cluster-label span p{background-color:transparent;}#mermaid-svg-MxqOxqBSwNTPo8pk .label text,#mermaid-svg-MxqOxqBSwNTPo8pk span{fill:#333;color:#333;}#mermaid-svg-MxqOxqBSwNTPo8pk .node rect,#mermaid-svg-MxqOxqBSwNTPo8pk .node circle,#mermaid-svg-MxqOxqBSwNTPo8pk .node ellipse,#mermaid-svg-MxqOxqBSwNTPo8pk .node polygon,#mermaid-svg-MxqOxqBSwNTPo8pk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MxqOxqBSwNTPo8pk .rough-node .label text,#mermaid-svg-MxqOxqBSwNTPo8pk .node .label text,#mermaid-svg-MxqOxqBSwNTPo8pk .image-shape .label,#mermaid-svg-MxqOxqBSwNTPo8pk .icon-shape .label{text-anchor:middle;}#mermaid-svg-MxqOxqBSwNTPo8pk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MxqOxqBSwNTPo8pk .rough-node .label,#mermaid-svg-MxqOxqBSwNTPo8pk .node .label,#mermaid-svg-MxqOxqBSwNTPo8pk .image-shape .label,#mermaid-svg-MxqOxqBSwNTPo8pk .icon-shape .label{text-align:center;}#mermaid-svg-MxqOxqBSwNTPo8pk .node.clickable{cursor:pointer;}#mermaid-svg-MxqOxqBSwNTPo8pk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MxqOxqBSwNTPo8pk .arrowheadPath{fill:#333333;}#mermaid-svg-MxqOxqBSwNTPo8pk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MxqOxqBSwNTPo8pk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MxqOxqBSwNTPo8pk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MxqOxqBSwNTPo8pk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MxqOxqBSwNTPo8pk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MxqOxqBSwNTPo8pk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MxqOxqBSwNTPo8pk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MxqOxqBSwNTPo8pk .cluster text{fill:#333;}#mermaid-svg-MxqOxqBSwNTPo8pk .cluster span{color:#333;}#mermaid-svg-MxqOxqBSwNTPo8pk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MxqOxqBSwNTPo8pk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MxqOxqBSwNTPo8pk rect.text{fill:none;stroke-width:0;}#mermaid-svg-MxqOxqBSwNTPo8pk .icon-shape,#mermaid-svg-MxqOxqBSwNTPo8pk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MxqOxqBSwNTPo8pk .icon-shape p,#mermaid-svg-MxqOxqBSwNTPo8pk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MxqOxqBSwNTPo8pk .icon-shape .label rect,#mermaid-svg-MxqOxqBSwNTPo8pk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MxqOxqBSwNTPo8pk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MxqOxqBSwNTPo8pk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MxqOxqBSwNTPo8pk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 发现敌人
受伤
进入范围
丢失目标
敌人死亡
血量过低
远离危险
Start
Idle 待机
Chase 追击
Flee 逃跑
Attack 攻击
状态转换表
cpp
enum class State
{
Idle, Chase, Attack, Flee, Patrol, Search, Alert
};
enum class Condition
{
EnemyDetected, InAttackRange, TargetLost,
TargetDead, LowHealth, SafeDistance
};
struct Transition
{
State from;
State to;
Condition condition;
};
std::vector<Transition> enemyTransitions =
{
{State::Idle, State::Chase, Condition::EnemyDetected},
{State::Chase, State::Attack, Condition::InAttackRange},
{State::Chase, State::Idle, Condition::TargetLost},
{State::Attack, State::Idle, Condition::TargetDead},
{State::Attack, State::Flee, Condition::LowHealth},
{State::Flee, State::Idle, Condition::SafeDistance},
};
优缺点
| 优点 | 缺点 |
|---|---|
| 简单直观,易于实现 | 状态爆炸(复杂行为) |
| 行为可预测 | 状态间耦合 |
| 调试容易(可见状态) | 难以表达并行状态 |
| 性能开销小 | 代码重复(共享逻辑) |
11. HFSM(分层有限状态机)
概述
HFSM(Hierarchical FSM)通过将状态组织成层次结构解决FSM的状态爆炸问题,子状态继承父状态的行为,支持嵌套和事件穿透。
层次结构
#mermaid-svg-pqDtXOTn7PtGIXZ1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .error-icon{fill:#552222;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .marker.cross{stroke:#333333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pqDtXOTn7PtGIXZ1 p{margin:0;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .cluster-label text{fill:#333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .cluster-label span{color:#333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .cluster-label span p{background-color:transparent;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .label text,#mermaid-svg-pqDtXOTn7PtGIXZ1 span{fill:#333;color:#333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .node rect,#mermaid-svg-pqDtXOTn7PtGIXZ1 .node circle,#mermaid-svg-pqDtXOTn7PtGIXZ1 .node ellipse,#mermaid-svg-pqDtXOTn7PtGIXZ1 .node polygon,#mermaid-svg-pqDtXOTn7PtGIXZ1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .rough-node .label text,#mermaid-svg-pqDtXOTn7PtGIXZ1 .node .label text,#mermaid-svg-pqDtXOTn7PtGIXZ1 .image-shape .label,#mermaid-svg-pqDtXOTn7PtGIXZ1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .rough-node .label,#mermaid-svg-pqDtXOTn7PtGIXZ1 .node .label,#mermaid-svg-pqDtXOTn7PtGIXZ1 .image-shape .label,#mermaid-svg-pqDtXOTn7PtGIXZ1 .icon-shape .label{text-align:center;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .node.clickable{cursor:pointer;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .arrowheadPath{fill:#333333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pqDtXOTn7PtGIXZ1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pqDtXOTn7PtGIXZ1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pqDtXOTn7PtGIXZ1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .cluster text{fill:#333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .cluster span{color:#333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pqDtXOTn7PtGIXZ1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .icon-shape,#mermaid-svg-pqDtXOTn7PtGIXZ1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .icon-shape p,#mermaid-svg-pqDtXOTn7PtGIXZ1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .icon-shape .label rect,#mermaid-svg-pqDtXOTn7PtGIXZ1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pqDtXOTn7PtGIXZ1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pqDtXOTn7PtGIXZ1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pqDtXOTn7PtGIXZ1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 发现敌人
敌人消灭
Patrol 巡逻(父状态)
Walk 行走
Wait 等待
Investigate 调查
Combat 战斗(父状态)
Defend 子状态
Attack 子状态
Block 格挡
战斗状态
Attack 攻击
Defend 防御
Reload 换弹
MeleeAttack 近战
RangedAttack 远程
SpecialAttack 特殊
Dodge 闪避
Counter 反击
Patrol
Combat
分层状态实现
cpp
class HierarchicalState
{
protected:
HierarchicalState* parentState = nullptr;
std::vector<std::unique_ptr<HierarchicalState>> children;
HierarchicalState* currentChild = nullptr;
std::string stateName;
public:
virtual ~HierarchicalState() = default;
virtual void Enter()
{
if (!children.empty() && !currentChild)
{
SetChildState(children[0].get());
}
}
virtual void Update(float dt)
{
if (currentChild) currentChild->Update(dt);
}
virtual void Exit()
{
if (currentChild)
{
currentChild->Exit();
currentChild = nullptr;
}
}
virtual bool HandleEvent(const GameEvent& event)
{
if (currentChild && currentChild->HandleEvent(event))
return true;
return false;
}
void SetChildState(HierarchicalState* child)
{
assert(std::find_if(children.begin(), children.end(),
[child](const auto& p) { return p.get() == child; })
!= children.end());
if (currentChild)
{
currentChild->Exit();
}
currentChild = child;
if (currentChild)
{
currentChild->Enter();
}
}
};
FSM vs HFSM 对比
#mermaid-svg-uFFGZzE2hlCRdQap{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uFFGZzE2hlCRdQap .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uFFGZzE2hlCRdQap .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uFFGZzE2hlCRdQap .error-icon{fill:#552222;}#mermaid-svg-uFFGZzE2hlCRdQap .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uFFGZzE2hlCRdQap .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uFFGZzE2hlCRdQap .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uFFGZzE2hlCRdQap .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uFFGZzE2hlCRdQap .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uFFGZzE2hlCRdQap .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uFFGZzE2hlCRdQap .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uFFGZzE2hlCRdQap .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uFFGZzE2hlCRdQap .marker.cross{stroke:#333333;}#mermaid-svg-uFFGZzE2hlCRdQap svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uFFGZzE2hlCRdQap p{margin:0;}#mermaid-svg-uFFGZzE2hlCRdQap .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uFFGZzE2hlCRdQap .cluster-label text{fill:#333;}#mermaid-svg-uFFGZzE2hlCRdQap .cluster-label span{color:#333;}#mermaid-svg-uFFGZzE2hlCRdQap .cluster-label span p{background-color:transparent;}#mermaid-svg-uFFGZzE2hlCRdQap .label text,#mermaid-svg-uFFGZzE2hlCRdQap span{fill:#333;color:#333;}#mermaid-svg-uFFGZzE2hlCRdQap .node rect,#mermaid-svg-uFFGZzE2hlCRdQap .node circle,#mermaid-svg-uFFGZzE2hlCRdQap .node ellipse,#mermaid-svg-uFFGZzE2hlCRdQap .node polygon,#mermaid-svg-uFFGZzE2hlCRdQap .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uFFGZzE2hlCRdQap .rough-node .label text,#mermaid-svg-uFFGZzE2hlCRdQap .node .label text,#mermaid-svg-uFFGZzE2hlCRdQap .image-shape .label,#mermaid-svg-uFFGZzE2hlCRdQap .icon-shape .label{text-anchor:middle;}#mermaid-svg-uFFGZzE2hlCRdQap .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uFFGZzE2hlCRdQap .rough-node .label,#mermaid-svg-uFFGZzE2hlCRdQap .node .label,#mermaid-svg-uFFGZzE2hlCRdQap .image-shape .label,#mermaid-svg-uFFGZzE2hlCRdQap .icon-shape .label{text-align:center;}#mermaid-svg-uFFGZzE2hlCRdQap .node.clickable{cursor:pointer;}#mermaid-svg-uFFGZzE2hlCRdQap .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uFFGZzE2hlCRdQap .arrowheadPath{fill:#333333;}#mermaid-svg-uFFGZzE2hlCRdQap .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uFFGZzE2hlCRdQap .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uFFGZzE2hlCRdQap .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uFFGZzE2hlCRdQap .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uFFGZzE2hlCRdQap .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uFFGZzE2hlCRdQap .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uFFGZzE2hlCRdQap .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uFFGZzE2hlCRdQap .cluster text{fill:#333;}#mermaid-svg-uFFGZzE2hlCRdQap .cluster span{color:#333;}#mermaid-svg-uFFGZzE2hlCRdQap div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uFFGZzE2hlCRdQap .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uFFGZzE2hlCRdQap rect.text{fill:none;stroke-width:0;}#mermaid-svg-uFFGZzE2hlCRdQap .icon-shape,#mermaid-svg-uFFGZzE2hlCRdQap .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uFFGZzE2hlCRdQap .icon-shape p,#mermaid-svg-uFFGZzE2hlCRdQap .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uFFGZzE2hlCRdQap .icon-shape .label rect,#mermaid-svg-uFFGZzE2hlCRdQap .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uFFGZzE2hlCRdQap .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uFFGZzE2hlCRdQap .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uFFGZzE2hlCRdQap :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HFSM: 分层状态(优雅组织)
Base_AI
Idle_Group
Idle
Patrol
Combat_Group
Chase
Attack
Flee
FSM: 平面状态(状态爆炸)
Idle
Chase
Attack
Flee
Patrol
Search
Alert
优缺点
| 优点 | 缺点 |
|---|---|
| 减少状态数量 | 实现复杂度高 |
| 代码复用(父子共享) | 调试困难(嵌套层次) |
| 行为组织清晰 | 层次设计需要经验 |
| 易于扩展 | 状态穿透逻辑复杂 |
12. Behavior Tree(行为树)
概述
Behavior Tree(行为树)是现代商业游戏中最常见的高层AI决策架构之一,尤其常见于角色驱动型游戏(ACT、ARPG、TPS、FPS)。通常与FSM、HFSM等状态机结合使用。UE、Unity、Cocos等引擎均原生支持。
架构对比
#mermaid-svg-0RuQriOw7ViSFdtx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0RuQriOw7ViSFdtx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0RuQriOw7ViSFdtx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0RuQriOw7ViSFdtx .error-icon{fill:#552222;}#mermaid-svg-0RuQriOw7ViSFdtx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0RuQriOw7ViSFdtx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0RuQriOw7ViSFdtx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0RuQriOw7ViSFdtx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0RuQriOw7ViSFdtx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0RuQriOw7ViSFdtx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0RuQriOw7ViSFdtx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0RuQriOw7ViSFdtx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0RuQriOw7ViSFdtx .marker.cross{stroke:#333333;}#mermaid-svg-0RuQriOw7ViSFdtx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0RuQriOw7ViSFdtx p{margin:0;}#mermaid-svg-0RuQriOw7ViSFdtx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0RuQriOw7ViSFdtx .cluster-label text{fill:#333;}#mermaid-svg-0RuQriOw7ViSFdtx .cluster-label span{color:#333;}#mermaid-svg-0RuQriOw7ViSFdtx .cluster-label span p{background-color:transparent;}#mermaid-svg-0RuQriOw7ViSFdtx .label text,#mermaid-svg-0RuQriOw7ViSFdtx span{fill:#333;color:#333;}#mermaid-svg-0RuQriOw7ViSFdtx .node rect,#mermaid-svg-0RuQriOw7ViSFdtx .node circle,#mermaid-svg-0RuQriOw7ViSFdtx .node ellipse,#mermaid-svg-0RuQriOw7ViSFdtx .node polygon,#mermaid-svg-0RuQriOw7ViSFdtx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0RuQriOw7ViSFdtx .rough-node .label text,#mermaid-svg-0RuQriOw7ViSFdtx .node .label text,#mermaid-svg-0RuQriOw7ViSFdtx .image-shape .label,#mermaid-svg-0RuQriOw7ViSFdtx .icon-shape .label{text-anchor:middle;}#mermaid-svg-0RuQriOw7ViSFdtx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0RuQriOw7ViSFdtx .rough-node .label,#mermaid-svg-0RuQriOw7ViSFdtx .node .label,#mermaid-svg-0RuQriOw7ViSFdtx .image-shape .label,#mermaid-svg-0RuQriOw7ViSFdtx .icon-shape .label{text-align:center;}#mermaid-svg-0RuQriOw7ViSFdtx .node.clickable{cursor:pointer;}#mermaid-svg-0RuQriOw7ViSFdtx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0RuQriOw7ViSFdtx .arrowheadPath{fill:#333333;}#mermaid-svg-0RuQriOw7ViSFdtx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0RuQriOw7ViSFdtx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0RuQriOw7ViSFdtx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0RuQriOw7ViSFdtx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0RuQriOw7ViSFdtx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0RuQriOw7ViSFdtx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0RuQriOw7ViSFdtx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0RuQriOw7ViSFdtx .cluster text{fill:#333;}#mermaid-svg-0RuQriOw7ViSFdtx .cluster span{color:#333;}#mermaid-svg-0RuQriOw7ViSFdtx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0RuQriOw7ViSFdtx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0RuQriOw7ViSFdtx rect.text{fill:none;stroke-width:0;}#mermaid-svg-0RuQriOw7ViSFdtx .icon-shape,#mermaid-svg-0RuQriOw7ViSFdtx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0RuQriOw7ViSFdtx .icon-shape p,#mermaid-svg-0RuQriOw7ViSFdtx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0RuQriOw7ViSFdtx .icon-shape .label rect,#mermaid-svg-0RuQriOw7ViSFdtx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0RuQriOw7ViSFdtx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0RuQriOw7ViSFdtx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0RuQriOw7ViSFdtx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 游戏AI架构概览
Behavior Tree
行为树
UE EQS + BT
Unity Behavior Tree
Cocos Behavior Tree
FSM
有限状态机
局部AI控制
动画状态机
UI状态机
GOAP
目标导向规划
开放世界NPC
策略游戏
Utility AI
效用AI
模拟/策略游戏
一般商业项目中,Behavior Tree 与 FSM 最为常见,HFSM、Utility AI、GOAP通常作为补充方案。两者通常是结合使用的。
核心节点类型
#mermaid-svg-zfpWntf09oNRhiEK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zfpWntf09oNRhiEK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zfpWntf09oNRhiEK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zfpWntf09oNRhiEK .error-icon{fill:#552222;}#mermaid-svg-zfpWntf09oNRhiEK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zfpWntf09oNRhiEK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zfpWntf09oNRhiEK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zfpWntf09oNRhiEK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zfpWntf09oNRhiEK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zfpWntf09oNRhiEK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zfpWntf09oNRhiEK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zfpWntf09oNRhiEK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zfpWntf09oNRhiEK .marker.cross{stroke:#333333;}#mermaid-svg-zfpWntf09oNRhiEK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zfpWntf09oNRhiEK p{margin:0;}#mermaid-svg-zfpWntf09oNRhiEK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zfpWntf09oNRhiEK .cluster-label text{fill:#333;}#mermaid-svg-zfpWntf09oNRhiEK .cluster-label span{color:#333;}#mermaid-svg-zfpWntf09oNRhiEK .cluster-label span p{background-color:transparent;}#mermaid-svg-zfpWntf09oNRhiEK .label text,#mermaid-svg-zfpWntf09oNRhiEK span{fill:#333;color:#333;}#mermaid-svg-zfpWntf09oNRhiEK .node rect,#mermaid-svg-zfpWntf09oNRhiEK .node circle,#mermaid-svg-zfpWntf09oNRhiEK .node ellipse,#mermaid-svg-zfpWntf09oNRhiEK .node polygon,#mermaid-svg-zfpWntf09oNRhiEK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zfpWntf09oNRhiEK .rough-node .label text,#mermaid-svg-zfpWntf09oNRhiEK .node .label text,#mermaid-svg-zfpWntf09oNRhiEK .image-shape .label,#mermaid-svg-zfpWntf09oNRhiEK .icon-shape .label{text-anchor:middle;}#mermaid-svg-zfpWntf09oNRhiEK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zfpWntf09oNRhiEK .rough-node .label,#mermaid-svg-zfpWntf09oNRhiEK .node .label,#mermaid-svg-zfpWntf09oNRhiEK .image-shape .label,#mermaid-svg-zfpWntf09oNRhiEK .icon-shape .label{text-align:center;}#mermaid-svg-zfpWntf09oNRhiEK .node.clickable{cursor:pointer;}#mermaid-svg-zfpWntf09oNRhiEK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zfpWntf09oNRhiEK .arrowheadPath{fill:#333333;}#mermaid-svg-zfpWntf09oNRhiEK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zfpWntf09oNRhiEK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zfpWntf09oNRhiEK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zfpWntf09oNRhiEK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zfpWntf09oNRhiEK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zfpWntf09oNRhiEK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zfpWntf09oNRhiEK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zfpWntf09oNRhiEK .cluster text{fill:#333;}#mermaid-svg-zfpWntf09oNRhiEK .cluster span{color:#333;}#mermaid-svg-zfpWntf09oNRhiEK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zfpWntf09oNRhiEK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zfpWntf09oNRhiEK rect.text{fill:none;stroke-width:0;}#mermaid-svg-zfpWntf09oNRhiEK .icon-shape,#mermaid-svg-zfpWntf09oNRhiEK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zfpWntf09oNRhiEK .icon-shape p,#mermaid-svg-zfpWntf09oNRhiEK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zfpWntf09oNRhiEK .icon-shape .label rect,#mermaid-svg-zfpWntf09oNRhiEK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zfpWntf09oNRhiEK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zfpWntf09oNRhiEK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zfpWntf09oNRhiEK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Behavior Tree 节点
Root 根节点
Selector 选择器
OR: 依次执行子节点
第一个成功则返回
Sequence 顺序器
AND: 依次执行子节点
全部成功才返回
Condition 条件节点
检查条件
Action 动作节点
执行行为
Action 动作节点
执行行为
Decorator 装饰器
取反/循环/超时
Action 动作节点
执行行为
行为树结构
#mermaid-svg-lusteLU60zilq9SC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lusteLU60zilq9SC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lusteLU60zilq9SC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lusteLU60zilq9SC .error-icon{fill:#552222;}#mermaid-svg-lusteLU60zilq9SC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lusteLU60zilq9SC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lusteLU60zilq9SC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lusteLU60zilq9SC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lusteLU60zilq9SC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lusteLU60zilq9SC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lusteLU60zilq9SC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lusteLU60zilq9SC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lusteLU60zilq9SC .marker.cross{stroke:#333333;}#mermaid-svg-lusteLU60zilq9SC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lusteLU60zilq9SC p{margin:0;}#mermaid-svg-lusteLU60zilq9SC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lusteLU60zilq9SC .cluster-label text{fill:#333;}#mermaid-svg-lusteLU60zilq9SC .cluster-label span{color:#333;}#mermaid-svg-lusteLU60zilq9SC .cluster-label span p{background-color:transparent;}#mermaid-svg-lusteLU60zilq9SC .label text,#mermaid-svg-lusteLU60zilq9SC span{fill:#333;color:#333;}#mermaid-svg-lusteLU60zilq9SC .node rect,#mermaid-svg-lusteLU60zilq9SC .node circle,#mermaid-svg-lusteLU60zilq9SC .node ellipse,#mermaid-svg-lusteLU60zilq9SC .node polygon,#mermaid-svg-lusteLU60zilq9SC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lusteLU60zilq9SC .rough-node .label text,#mermaid-svg-lusteLU60zilq9SC .node .label text,#mermaid-svg-lusteLU60zilq9SC .image-shape .label,#mermaid-svg-lusteLU60zilq9SC .icon-shape .label{text-anchor:middle;}#mermaid-svg-lusteLU60zilq9SC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lusteLU60zilq9SC .rough-node .label,#mermaid-svg-lusteLU60zilq9SC .node .label,#mermaid-svg-lusteLU60zilq9SC .image-shape .label,#mermaid-svg-lusteLU60zilq9SC .icon-shape .label{text-align:center;}#mermaid-svg-lusteLU60zilq9SC .node.clickable{cursor:pointer;}#mermaid-svg-lusteLU60zilq9SC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lusteLU60zilq9SC .arrowheadPath{fill:#333333;}#mermaid-svg-lusteLU60zilq9SC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lusteLU60zilq9SC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lusteLU60zilq9SC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lusteLU60zilq9SC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lusteLU60zilq9SC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lusteLU60zilq9SC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lusteLU60zilq9SC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lusteLU60zilq9SC .cluster text{fill:#333;}#mermaid-svg-lusteLU60zilq9SC .cluster span{color:#333;}#mermaid-svg-lusteLU60zilq9SC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lusteLU60zilq9SC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lusteLU60zilq9SC rect.text{fill:none;stroke-width:0;}#mermaid-svg-lusteLU60zilq9SC .icon-shape,#mermaid-svg-lusteLU60zilq9SC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lusteLU60zilq9SC .icon-shape p,#mermaid-svg-lusteLU60zilq9SC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lusteLU60zilq9SC .icon-shape .label rect,#mermaid-svg-lusteLU60zilq9SC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lusteLU60zilq9SC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lusteLU60zilq9SC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lusteLU60zilq9SC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Root: AI Controller
Selector
选择策略
Sequence
攻击流程
Has Target?
In Range?
Attack
Sequence
追击流程
Has Target?
Chase
Sequence
巡逻流程
Patrol
Wait
C++ 实现
cpp
class Blackboard
{
private:
std::unordered_map<std::string, std::any> data;
public:
template<typename T>
void Set(const std::string& key, T value) { data[key] = value; }
template<typename T>
T Get(const std::string& key) const
{
return std::any_cast<T>(data.at(key));
}
};
// ============ 节点基类 ============
enum class Status { Success, Failure, Running };
class BTNode
{
public:
virtual ~BTNode() = default;
virtual Status Tick(Blackboard& bb, float dt) = 0;
};
// ============ 叶子节点 ============
class ConditionNode : public BTNode
{
private:
std::function<bool(Blackboard&)> condition;
public:
ConditionNode(std::function<bool(Blackboard&)> fn) : condition(std::move(fn)) {}
Status Tick(Blackboard& bb, float dt) override
{
return condition(bb) ? Status::Success : Status::Failure;
}
};
class ActionNode : public BTNode
{
private:
std::function<Status(Blackboard&, float)> action;
public:
ActionNode(std::function<Status(Blackboard&, float)> fn) : action(std::move(fn)) {}
Status Tick(Blackboard& bb, float dt) override
{
return action(bb, dt);
}
};
class Selector : public BTNode
{
private:
std::vector<std::unique_ptr<BTNode>> children;
public:
void AddChild(std::unique_ptr<BTNode> child)
{
children.push_back(std::move(child));
}
Status Tick(Blackboard& bb, float dt) override
{
for (size_t i = 0; i < children.size(); ++i) {
Status s = children[i]->Tick(bb, dt);
if (s != Status::Failure) return s;
}
return Status::Failure;
}
};
class Sequence : public BTNode
{
private:
std::vector<std::unique_ptr<BTNode>> children;
public:
void AddChild(std::unique_ptr<BTNode> child)
{
children.push_back(std::move(child));
}
Status Tick(Blackboard& bb, float dt) override
{
for (size_t i = 0; i < children.size(); ++i)
{
Status s = children[i]->Tick(bb, dt);
if (s != Status::Success) return s;
}
return Status::Success;
}
};
// ============ 装饰器 ============
class Inverter : public BTNode
{
private:
std::unique_ptr<BTNode> child;
public:
Status Tick(Blackboard& bb, float dt) override
{
Status s = child->Tick(bb, dt);
if (s == Status::Success) return Status::Failure;
if (s == Status::Failure) return Status::Success;
return Status::Running;
}
};
// ============ 行为树执行器 ============
class BehaviorTreeRunner
{
private:
std::unique_ptr<BTNode> root;
public:
BehaviorTreeRunner(std::unique_ptr<BTNode> r) : root(std::move(r)) {}
void Tick(Blackboard& bb, float dt)
{
if (root) root->Tick(bb, dt);
}
};
// ============ 构建行为树 ============
BehaviorTreeRunner BuildEnemyBT(Blackboard& bb)
{
auto root = std::make_unique<Selector>();
// 攻击分支
auto attackSeq = std::make_unique<Sequence>();
attackSeq->AddChild(std::make_unique<ConditionNode>(
[](Blackboard& b) { return b.Get<bool>("has_target"); }
));
attackSeq->AddChild(std::make_unique<ConditionNode>(
[](Blackboard& b) { return b.Get<float>("distance") < 5.0f; }
));
attackSeq->AddChild(std::make_unique<ActionNode>(
[](Blackboard& b, float) -> Status
{
// 执行攻击
return Status::Success;
}
));
// 追击分支
auto chaseSeq = std::make_unique<Sequence>();
chaseSeq->AddChild(std::make_unique<ConditionNode>(
[](Blackboard& b) { return b.Get<bool>("has_target"); }
));
chaseSeq->AddChild(std::make_unique<ActionNode>(
[](Blackboard& b, float dt) -> Status
{
// 追击目标
return Status::Running;
}
));
// 巡逻分支
auto patrolSeq = std::make_unique<Sequence>();
patrolSeq->AddChild(std::make_unique<ActionNode>(
[](Blackboard& b, float dt) -> Status
{
// 巡逻
return Status::Running;
}
));
root->AddChild(std::move(attackSeq));
root->AddChild(std::move(chaseSeq));
root->AddChild(std::move(patrolSeq));
return BehaviorTreeRunner(std::move(root));
}
Behavior Tree vs FSM
| 维度 | FSM | Behavior Tree |
|---|---|---|
| 状态管理 | 显式状态 | 隐式(节点状态) |
| 扩展性 | 状态爆炸 | 轻松添加分支 |
| 可读性 | 中等 | 可视化极佳 |
| 复用性 | 低 | 高(子树复用) |
| 调试 | 容易 | 可视化调试 |
| UE支持 | 动画状态机 | Behavior Tree + EQS |
优缺点
| 优点 | 缺点 |
|---|---|
| 可视化编辑,直觉性强 | 树结构可能过于复杂 |
| 模块化,子树可复用 | Running状态管理复杂 |
| 条件与动作分离 | 不适合高度动态规划 |
| UE/Unity原生支持 | 调试父子节点关系 |
| 适合复杂AI行为 | 内存占用相对较高 |
13. GOAP(目标导向行为规划)
概述
GOAP(Goal-Oriented Action Planning)是一种基于规划的AI架构,AI Agent 根据当前世界状态和目标,动态规划一系列动作来达成目标。
经典代表作:《FEAR》(F.E.A.R.)的AI系统。
架构图
#mermaid-svg-j7ayrEugoPPueqSi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-j7ayrEugoPPueqSi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-j7ayrEugoPPueqSi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-j7ayrEugoPPueqSi .error-icon{fill:#552222;}#mermaid-svg-j7ayrEugoPPueqSi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-j7ayrEugoPPueqSi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-j7ayrEugoPPueqSi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-j7ayrEugoPPueqSi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-j7ayrEugoPPueqSi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-j7ayrEugoPPueqSi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-j7ayrEugoPPueqSi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-j7ayrEugoPPueqSi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-j7ayrEugoPPueqSi .marker.cross{stroke:#333333;}#mermaid-svg-j7ayrEugoPPueqSi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-j7ayrEugoPPueqSi p{margin:0;}#mermaid-svg-j7ayrEugoPPueqSi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-j7ayrEugoPPueqSi .cluster-label text{fill:#333;}#mermaid-svg-j7ayrEugoPPueqSi .cluster-label span{color:#333;}#mermaid-svg-j7ayrEugoPPueqSi .cluster-label span p{background-color:transparent;}#mermaid-svg-j7ayrEugoPPueqSi .label text,#mermaid-svg-j7ayrEugoPPueqSi span{fill:#333;color:#333;}#mermaid-svg-j7ayrEugoPPueqSi .node rect,#mermaid-svg-j7ayrEugoPPueqSi .node circle,#mermaid-svg-j7ayrEugoPPueqSi .node ellipse,#mermaid-svg-j7ayrEugoPPueqSi .node polygon,#mermaid-svg-j7ayrEugoPPueqSi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-j7ayrEugoPPueqSi .rough-node .label text,#mermaid-svg-j7ayrEugoPPueqSi .node .label text,#mermaid-svg-j7ayrEugoPPueqSi .image-shape .label,#mermaid-svg-j7ayrEugoPPueqSi .icon-shape .label{text-anchor:middle;}#mermaid-svg-j7ayrEugoPPueqSi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-j7ayrEugoPPueqSi .rough-node .label,#mermaid-svg-j7ayrEugoPPueqSi .node .label,#mermaid-svg-j7ayrEugoPPueqSi .image-shape .label,#mermaid-svg-j7ayrEugoPPueqSi .icon-shape .label{text-align:center;}#mermaid-svg-j7ayrEugoPPueqSi .node.clickable{cursor:pointer;}#mermaid-svg-j7ayrEugoPPueqSi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-j7ayrEugoPPueqSi .arrowheadPath{fill:#333333;}#mermaid-svg-j7ayrEugoPPueqSi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-j7ayrEugoPPueqSi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-j7ayrEugoPPueqSi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j7ayrEugoPPueqSi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-j7ayrEugoPPueqSi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j7ayrEugoPPueqSi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-j7ayrEugoPPueqSi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-j7ayrEugoPPueqSi .cluster text{fill:#333;}#mermaid-svg-j7ayrEugoPPueqSi .cluster span{color:#333;}#mermaid-svg-j7ayrEugoPPueqSi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-j7ayrEugoPPueqSi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-j7ayrEugoPPueqSi rect.text{fill:none;stroke-width:0;}#mermaid-svg-j7ayrEugoPPueqSi .icon-shape,#mermaid-svg-j7ayrEugoPPueqSi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j7ayrEugoPPueqSi .icon-shape p,#mermaid-svg-j7ayrEugoPPueqSi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-j7ayrEugoPPueqSi .icon-shape .label rect,#mermaid-svg-j7ayrEugoPPueqSi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j7ayrEugoPPueqSi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-j7ayrEugoPPueqSi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-j7ayrEugoPPueqSi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 规划过程
Start
Step 1: 寻路
Step 2: 移动
Step 3: 拾取武器
Step 4: 攻击
Goal: 消灭敌人
GOAP 架构
输入
输入
输入
输出: 动作序列
WorldState
世界状态
Planner
规划器
Goals
目标列表
Actions
动作集合
Plan
行动计划
核心数据结构
cpp
struct WorldStateBool
{
std::unordered_map<std::string, bool> bools;
};
struct WorldStateNumeric
{
std::unordered_map<std::string, float> values;
};
class GoapAction
{
public:
std::string name;
float cost;
WorldStateBool preconditions;
WorldStateBool effects;
bool inProgress = false;
float elapsedTime = 0;
virtual ~GoapAction() = default;
bool IsUsable(const WorldStateBool& state) const
{
for (auto& [key, value] : preconditions.bools)
{
auto it = state.bools.find(key);
if (it == state.bools.end() || it->second != value)
return false;
}
return true;
}
void ApplyEffects(WorldStateBool& state) const
{
for (auto& [key, value] : effects.bools)
{
state.bools[key] = value;
}
}
virtual bool Perform() = 0;
virtual float GetDuration() const { return 1.0f; }
};
规划器(A*)
cpp
class GoapPlanner
{
public:
std::vector<std::shared_ptr<GoapAction>> Plan(
const WorldStateBool& startState,
const Goal& goal,
const std::vector<std::shared_ptr<GoapAction>>& availableActions)
{
// A* 搜索最优动作序列
return {};
}
};
FSM vs GOAP 对比
#mermaid-svg-OwePZ3AMXCd4h4o9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OwePZ3AMXCd4h4o9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OwePZ3AMXCd4h4o9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OwePZ3AMXCd4h4o9 .error-icon{fill:#552222;}#mermaid-svg-OwePZ3AMXCd4h4o9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OwePZ3AMXCd4h4o9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OwePZ3AMXCd4h4o9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OwePZ3AMXCd4h4o9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OwePZ3AMXCd4h4o9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OwePZ3AMXCd4h4o9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OwePZ3AMXCd4h4o9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OwePZ3AMXCd4h4o9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OwePZ3AMXCd4h4o9 .marker.cross{stroke:#333333;}#mermaid-svg-OwePZ3AMXCd4h4o9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OwePZ3AMXCd4h4o9 p{margin:0;}#mermaid-svg-OwePZ3AMXCd4h4o9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OwePZ3AMXCd4h4o9 .cluster-label text{fill:#333;}#mermaid-svg-OwePZ3AMXCd4h4o9 .cluster-label span{color:#333;}#mermaid-svg-OwePZ3AMXCd4h4o9 .cluster-label span p{background-color:transparent;}#mermaid-svg-OwePZ3AMXCd4h4o9 .label text,#mermaid-svg-OwePZ3AMXCd4h4o9 span{fill:#333;color:#333;}#mermaid-svg-OwePZ3AMXCd4h4o9 .node rect,#mermaid-svg-OwePZ3AMXCd4h4o9 .node circle,#mermaid-svg-OwePZ3AMXCd4h4o9 .node ellipse,#mermaid-svg-OwePZ3AMXCd4h4o9 .node polygon,#mermaid-svg-OwePZ3AMXCd4h4o9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OwePZ3AMXCd4h4o9 .rough-node .label text,#mermaid-svg-OwePZ3AMXCd4h4o9 .node .label text,#mermaid-svg-OwePZ3AMXCd4h4o9 .image-shape .label,#mermaid-svg-OwePZ3AMXCd4h4o9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-OwePZ3AMXCd4h4o9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OwePZ3AMXCd4h4o9 .rough-node .label,#mermaid-svg-OwePZ3AMXCd4h4o9 .node .label,#mermaid-svg-OwePZ3AMXCd4h4o9 .image-shape .label,#mermaid-svg-OwePZ3AMXCd4h4o9 .icon-shape .label{text-align:center;}#mermaid-svg-OwePZ3AMXCd4h4o9 .node.clickable{cursor:pointer;}#mermaid-svg-OwePZ3AMXCd4h4o9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OwePZ3AMXCd4h4o9 .arrowheadPath{fill:#333333;}#mermaid-svg-OwePZ3AMXCd4h4o9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OwePZ3AMXCd4h4o9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OwePZ3AMXCd4h4o9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OwePZ3AMXCd4h4o9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OwePZ3AMXCd4h4o9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OwePZ3AMXCd4h4o9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OwePZ3AMXCd4h4o9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OwePZ3AMXCd4h4o9 .cluster text{fill:#333;}#mermaid-svg-OwePZ3AMXCd4h4o9 .cluster span{color:#333;}#mermaid-svg-OwePZ3AMXCd4h4o9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OwePZ3AMXCd4h4o9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OwePZ3AMXCd4h4o9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-OwePZ3AMXCd4h4o9 .icon-shape,#mermaid-svg-OwePZ3AMXCd4h4o9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OwePZ3AMXCd4h4o9 .icon-shape p,#mermaid-svg-OwePZ3AMXCd4h4o9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OwePZ3AMXCd4h4o9 .icon-shape .label rect,#mermaid-svg-OwePZ3AMXCd4h4o9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OwePZ3AMXCd4h4o9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OwePZ3AMXCd4h4o9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OwePZ3AMXCd4h4o9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} GOAP: 自动规划
方案A
方案B
方案C
目标: 消灭敌人
规划器
拾取武器→攻击
使用魔法→攻击
呼叫支援→围剿
✅ 动态选择最优方案
FSM: 人工设计
敌人可见
无敌人
血量低
可攻击
角色状态
追击
巡逻
逃跑
攻击
❌ 所有逻辑硬编码
优缺点
| 优点 | 缺点 |
|---|---|
| 高度灵活的动态行为 | 规划计算开销大 |
| 无需手动设计状态转换 | 复杂目标设计困难 |
| 适应性强(应对意外情况) | 调试困难 |
| 可复用动作组件 | 实时性要求高时可能卡顿 |
14. Utility AI(效用AI)
概述
Utility AI(效用AI)基于评分系统而非状态转换。AI Agent为每个行为计算一个效用值(Utility Score),选择最高分的行动执行。
公开资料中常见案例包括:《模拟人生》系列、《文明》系列等。注意《全面战争》系列实际上是Utility AI、Rule-Based与FSM的混合体。
核心思想
#mermaid-svg-eq47Yyn7E00UqA4D{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eq47Yyn7E00UqA4D .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eq47Yyn7E00UqA4D .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eq47Yyn7E00UqA4D .error-icon{fill:#552222;}#mermaid-svg-eq47Yyn7E00UqA4D .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eq47Yyn7E00UqA4D .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eq47Yyn7E00UqA4D .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eq47Yyn7E00UqA4D .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eq47Yyn7E00UqA4D .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eq47Yyn7E00UqA4D .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eq47Yyn7E00UqA4D .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eq47Yyn7E00UqA4D .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eq47Yyn7E00UqA4D .marker.cross{stroke:#333333;}#mermaid-svg-eq47Yyn7E00UqA4D svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eq47Yyn7E00UqA4D p{margin:0;}#mermaid-svg-eq47Yyn7E00UqA4D .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eq47Yyn7E00UqA4D .cluster-label text{fill:#333;}#mermaid-svg-eq47Yyn7E00UqA4D .cluster-label span{color:#333;}#mermaid-svg-eq47Yyn7E00UqA4D .cluster-label span p{background-color:transparent;}#mermaid-svg-eq47Yyn7E00UqA4D .label text,#mermaid-svg-eq47Yyn7E00UqA4D span{fill:#333;color:#333;}#mermaid-svg-eq47Yyn7E00UqA4D .node rect,#mermaid-svg-eq47Yyn7E00UqA4D .node circle,#mermaid-svg-eq47Yyn7E00UqA4D .node ellipse,#mermaid-svg-eq47Yyn7E00UqA4D .node polygon,#mermaid-svg-eq47Yyn7E00UqA4D .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eq47Yyn7E00UqA4D .rough-node .label text,#mermaid-svg-eq47Yyn7E00UqA4D .node .label text,#mermaid-svg-eq47Yyn7E00UqA4D .image-shape .label,#mermaid-svg-eq47Yyn7E00UqA4D .icon-shape .label{text-anchor:middle;}#mermaid-svg-eq47Yyn7E00UqA4D .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eq47Yyn7E00UqA4D .rough-node .label,#mermaid-svg-eq47Yyn7E00UqA4D .node .label,#mermaid-svg-eq47Yyn7E00UqA4D .image-shape .label,#mermaid-svg-eq47Yyn7E00UqA4D .icon-shape .label{text-align:center;}#mermaid-svg-eq47Yyn7E00UqA4D .node.clickable{cursor:pointer;}#mermaid-svg-eq47Yyn7E00UqA4D .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eq47Yyn7E00UqA4D .arrowheadPath{fill:#333333;}#mermaid-svg-eq47Yyn7E00UqA4D .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eq47Yyn7E00UqA4D .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eq47Yyn7E00UqA4D .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eq47Yyn7E00UqA4D .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eq47Yyn7E00UqA4D .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eq47Yyn7E00UqA4D .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eq47Yyn7E00UqA4D .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eq47Yyn7E00UqA4D .cluster text{fill:#333;}#mermaid-svg-eq47Yyn7E00UqA4D .cluster span{color:#333;}#mermaid-svg-eq47Yyn7E00UqA4D div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eq47Yyn7E00UqA4D .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eq47Yyn7E00UqA4D rect.text{fill:none;stroke-width:0;}#mermaid-svg-eq47Yyn7E00UqA4D .icon-shape,#mermaid-svg-eq47Yyn7E00UqA4D .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eq47Yyn7E00UqA4D .icon-shape p,#mermaid-svg-eq47Yyn7E00UqA4D .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eq47Yyn7E00UqA4D .icon-shape .label rect,#mermaid-svg-eq47Yyn7E00UqA4D .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eq47Yyn7E00UqA4D .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eq47Yyn7E00UqA4D .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eq47Yyn7E00UqA4D :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Utility AI 评分系统
输入: 距离敌人 5m
攻击效用
= 距离因子 × 血量因子
输入: HP 30/100
逃跑效用
= 1/血量因子 × 安全距离
输入: 弹药 10
换弹效用
= 1/弹药因子
比较最高分
CompareCompare
选择: 逃跑
C++ 实现
cpp
class Curve
{
private:
std::function<float(float)> function;
public:
static Curve Linear()
{
return Curve{[](float x) { return std::clamp(x, 0.0f, 1.0f); }};
}
static Curve Logistic(float steepness = 4.0f)
{
return Curve{[steepness](float x)
{
return 1.0f / (1.0f + std::exp(-steepness * (x - 0.5f)));
}};
}
static Curve Exponential(float power = 2.0f)
{
return Curve{[power](float x)
{
return std::pow(std::clamp(x, 0.0f, 1.0f), power);
}};
}
float Evaluate(float input) const { return function(input); }
};
struct Consideration
{
std::function<float(Blackboard&)> inputGetter;
Curve curve;float weight;
float Score(Blackboard& bb) const
{
float rawInput = inputGetter(bb);
float mapped = curve.Evaluate(rawInput);
return mapped * weight;
}
};
struct UtilityAction
{
std::string name;
std::vector<Consideration> considerations;
float CalculateScore(Blackboard& bb) const
{
float score = 1.0f;
for (auto& c : considerations)
{
score *= c.Score(bb);
}
return score;
}
};
class UtilityAIAgent
{
private:
std::vector<UtilityAction> actions;
Blackboard& blackboard;
float evaluationInterval = 0.5f;
float timer = 0;
public:
void AddAction(UtilityAction action)
{
actions.push_back(std::move(action));
}
UtilityAction* SelectBestAction(float dt)
{
timer += dt;
if (timer < evaluationInterval) return nullptr;
timer = 0;
UtilityAction* bestAction = nullptr;
float bestScore = 0;
for (auto& action : actions)
{
float score = action.CalculateScore(blackboard);
if (score > bestScore)
{
bestScore = score;
bestAction = &action;
}
}
return bestAction;
}
};
void SetupAI(UtilityAIAgent& agent, Blackboard& bb) {UtilityAction attack;
attack.name = "Attack";
attack.considerations =
{
Consideration
{
[](Blackboard& b) { return 1.0f - b.Get<float>("distance") / 100.0f; },
Curve::Logistic(), 1.0f
},
Consideration
{
[](Blackboard& b) { return b.Get<float>("hp") / 100.0f; },
Curve::Linear(), 1.0f
},
Consideration
{
[](Blackboard& b) { return b.Get<int>("ammo") / 30.0f; },
Curve::Exponential(2.0f), 1.5f
}
};
UtilityAction flee;
flee.name = "Flee";
flee.considerations =
{
Consideration
{
[](Blackboard& b) { return 1.0f - b.Get<float>("hp") / 100.0f; },
Curve::Exponential(3.0f), 2.0f
}
};
agent.AddAction(std::move(attack));
agent.AddAction(std::move(flee));
}
FSM vs Utility AI
#mermaid-svg-d0JTJoGTcxGxBLqC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-d0JTJoGTcxGxBLqC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d0JTJoGTcxGxBLqC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d0JTJoGTcxGxBLqC .error-icon{fill:#552222;}#mermaid-svg-d0JTJoGTcxGxBLqC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d0JTJoGTcxGxBLqC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d0JTJoGTcxGxBLqC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d0JTJoGTcxGxBLqC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d0JTJoGTcxGxBLqC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d0JTJoGTcxGxBLqC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d0JTJoGTcxGxBLqC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d0JTJoGTcxGxBLqC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d0JTJoGTcxGxBLqC .marker.cross{stroke:#333333;}#mermaid-svg-d0JTJoGTcxGxBLqC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d0JTJoGTcxGxBLqC p{margin:0;}#mermaid-svg-d0JTJoGTcxGxBLqC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-d0JTJoGTcxGxBLqC .cluster-label text{fill:#333;}#mermaid-svg-d0JTJoGTcxGxBLqC .cluster-label span{color:#333;}#mermaid-svg-d0JTJoGTcxGxBLqC .cluster-label span p{background-color:transparent;}#mermaid-svg-d0JTJoGTcxGxBLqC .label text,#mermaid-svg-d0JTJoGTcxGxBLqC span{fill:#333;color:#333;}#mermaid-svg-d0JTJoGTcxGxBLqC .node rect,#mermaid-svg-d0JTJoGTcxGxBLqC .node circle,#mermaid-svg-d0JTJoGTcxGxBLqC .node ellipse,#mermaid-svg-d0JTJoGTcxGxBLqC .node polygon,#mermaid-svg-d0JTJoGTcxGxBLqC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d0JTJoGTcxGxBLqC .rough-node .label text,#mermaid-svg-d0JTJoGTcxGxBLqC .node .label text,#mermaid-svg-d0JTJoGTcxGxBLqC .image-shape .label,#mermaid-svg-d0JTJoGTcxGxBLqC .icon-shape .label{text-anchor:middle;}#mermaid-svg-d0JTJoGTcxGxBLqC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-d0JTJoGTcxGxBLqC .rough-node .label,#mermaid-svg-d0JTJoGTcxGxBLqC .node .label,#mermaid-svg-d0JTJoGTcxGxBLqC .image-shape .label,#mermaid-svg-d0JTJoGTcxGxBLqC .icon-shape .label{text-align:center;}#mermaid-svg-d0JTJoGTcxGxBLqC .node.clickable{cursor:pointer;}#mermaid-svg-d0JTJoGTcxGxBLqC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-d0JTJoGTcxGxBLqC .arrowheadPath{fill:#333333;}#mermaid-svg-d0JTJoGTcxGxBLqC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d0JTJoGTcxGxBLqC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d0JTJoGTcxGxBLqC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d0JTJoGTcxGxBLqC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-d0JTJoGTcxGxBLqC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d0JTJoGTcxGxBLqC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-d0JTJoGTcxGxBLqC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d0JTJoGTcxGxBLqC .cluster text{fill:#333;}#mermaid-svg-d0JTJoGTcxGxBLqC .cluster span{color:#333;}#mermaid-svg-d0JTJoGTcxGxBLqC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-d0JTJoGTcxGxBLqC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-d0JTJoGTcxGxBLqC rect.text{fill:none;stroke-width:0;}#mermaid-svg-d0JTJoGTcxGxBLqC .icon-shape,#mermaid-svg-d0JTJoGTcxGxBLqC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d0JTJoGTcxGxBLqC .icon-shape p,#mermaid-svg-d0JTJoGTcxGxBLqC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-d0JTJoGTcxGxBLqC .icon-shape .label rect,#mermaid-svg-d0JTJoGTcxGxBLqC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d0JTJoGTcxGxBLqC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-d0JTJoGTcxGxBLqC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-d0JTJoGTcxGxBLqC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Utility AI: 连续评分
HP=80
HP=50
HP=20
评分曲线
攻击 0.9
攻击 0.5 / 逃跑 0.3
逃跑 0.8
✅ 平滑过渡
自然行为表现
FSM: 非黑即白
HP > 30
HP <= 30
状态
战斗
逃跑
❌ 边界问题
29HP逃跑 31HP战斗
优缺点
| 优点 | 缺点 |
|---|---|
| 自然平滑的行为 | 调参困难(曲线调试) |
| 无硬编码状态转换 | 评分公式复杂 |
| 易于微调(修改曲线) | 难以预测行为 |
| 适合复杂决策 | 大量行为频繁评估时会产生额外CPU开销 |
| 《模拟人生》验证 | 极端情况难控制 |
15. 架构对比总结
分层架构体系
#mermaid-svg-jBquupgxhXIE3jlg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jBquupgxhXIE3jlg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jBquupgxhXIE3jlg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jBquupgxhXIE3jlg .error-icon{fill:#552222;}#mermaid-svg-jBquupgxhXIE3jlg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jBquupgxhXIE3jlg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jBquupgxhXIE3jlg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jBquupgxhXIE3jlg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jBquupgxhXIE3jlg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jBquupgxhXIE3jlg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jBquupgxhXIE3jlg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jBquupgxhXIE3jlg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jBquupgxhXIE3jlg .marker.cross{stroke:#333333;}#mermaid-svg-jBquupgxhXIE3jlg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jBquupgxhXIE3jlg p{margin:0;}#mermaid-svg-jBquupgxhXIE3jlg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jBquupgxhXIE3jlg .cluster-label text{fill:#333;}#mermaid-svg-jBquupgxhXIE3jlg .cluster-label span{color:#333;}#mermaid-svg-jBquupgxhXIE3jlg .cluster-label span p{background-color:transparent;}#mermaid-svg-jBquupgxhXIE3jlg .label text,#mermaid-svg-jBquupgxhXIE3jlg span{fill:#333;color:#333;}#mermaid-svg-jBquupgxhXIE3jlg .node rect,#mermaid-svg-jBquupgxhXIE3jlg .node circle,#mermaid-svg-jBquupgxhXIE3jlg .node ellipse,#mermaid-svg-jBquupgxhXIE3jlg .node polygon,#mermaid-svg-jBquupgxhXIE3jlg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jBquupgxhXIE3jlg .rough-node .label text,#mermaid-svg-jBquupgxhXIE3jlg .node .label text,#mermaid-svg-jBquupgxhXIE3jlg .image-shape .label,#mermaid-svg-jBquupgxhXIE3jlg .icon-shape .label{text-anchor:middle;}#mermaid-svg-jBquupgxhXIE3jlg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jBquupgxhXIE3jlg .rough-node .label,#mermaid-svg-jBquupgxhXIE3jlg .node .label,#mermaid-svg-jBquupgxhXIE3jlg .image-shape .label,#mermaid-svg-jBquupgxhXIE3jlg .icon-shape .label{text-align:center;}#mermaid-svg-jBquupgxhXIE3jlg .node.clickable{cursor:pointer;}#mermaid-svg-jBquupgxhXIE3jlg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jBquupgxhXIE3jlg .arrowheadPath{fill:#333333;}#mermaid-svg-jBquupgxhXIE3jlg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jBquupgxhXIE3jlg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jBquupgxhXIE3jlg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jBquupgxhXIE3jlg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jBquupgxhXIE3jlg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jBquupgxhXIE3jlg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jBquupgxhXIE3jlg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jBquupgxhXIE3jlg .cluster text{fill:#333;}#mermaid-svg-jBquupgxhXIE3jlg .cluster span{color:#333;}#mermaid-svg-jBquupgxhXIE3jlg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jBquupgxhXIE3jlg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jBquupgxhXIE3jlg rect.text{fill:none;stroke-width:0;}#mermaid-svg-jBquupgxhXIE3jlg .icon-shape,#mermaid-svg-jBquupgxhXIE3jlg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jBquupgxhXIE3jlg .icon-shape p,#mermaid-svg-jBquupgxhXIE3jlg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jBquupgxhXIE3jlg .icon-shape .label rect,#mermaid-svg-jBquupgxhXIE3jlg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jBquupgxhXIE3jlg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jBquupgxhXIE3jlg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jBquupgxhXIE3jlg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 游戏架构分层
基础层
OOP + Component + Event
性能层
DOP + ECS (Mass)
能力系统层
Ability System / GAS
UI层
MVC + MVP + MVVM
AI层
FSM + HFSM + BehaviorTree + GOAP + UtilityAI
选择指南
#mermaid-svg-9PWW9YrRqmjuDyrj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9PWW9YrRqmjuDyrj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9PWW9YrRqmjuDyrj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9PWW9YrRqmjuDyrj .error-icon{fill:#552222;}#mermaid-svg-9PWW9YrRqmjuDyrj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9PWW9YrRqmjuDyrj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9PWW9YrRqmjuDyrj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9PWW9YrRqmjuDyrj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9PWW9YrRqmjuDyrj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9PWW9YrRqmjuDyrj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9PWW9YrRqmjuDyrj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9PWW9YrRqmjuDyrj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9PWW9YrRqmjuDyrj .marker.cross{stroke:#333333;}#mermaid-svg-9PWW9YrRqmjuDyrj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9PWW9YrRqmjuDyrj p{margin:0;}#mermaid-svg-9PWW9YrRqmjuDyrj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9PWW9YrRqmjuDyrj .cluster-label text{fill:#333;}#mermaid-svg-9PWW9YrRqmjuDyrj .cluster-label span{color:#333;}#mermaid-svg-9PWW9YrRqmjuDyrj .cluster-label span p{background-color:transparent;}#mermaid-svg-9PWW9YrRqmjuDyrj .label text,#mermaid-svg-9PWW9YrRqmjuDyrj span{fill:#333;color:#333;}#mermaid-svg-9PWW9YrRqmjuDyrj .node rect,#mermaid-svg-9PWW9YrRqmjuDyrj .node circle,#mermaid-svg-9PWW9YrRqmjuDyrj .node ellipse,#mermaid-svg-9PWW9YrRqmjuDyrj .node polygon,#mermaid-svg-9PWW9YrRqmjuDyrj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9PWW9YrRqmjuDyrj .rough-node .label text,#mermaid-svg-9PWW9YrRqmjuDyrj .node .label text,#mermaid-svg-9PWW9YrRqmjuDyrj .image-shape .label,#mermaid-svg-9PWW9YrRqmjuDyrj .icon-shape .label{text-anchor:middle;}#mermaid-svg-9PWW9YrRqmjuDyrj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9PWW9YrRqmjuDyrj .rough-node .label,#mermaid-svg-9PWW9YrRqmjuDyrj .node .label,#mermaid-svg-9PWW9YrRqmjuDyrj .image-shape .label,#mermaid-svg-9PWW9YrRqmjuDyrj .icon-shape .label{text-align:center;}#mermaid-svg-9PWW9YrRqmjuDyrj .node.clickable{cursor:pointer;}#mermaid-svg-9PWW9YrRqmjuDyrj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9PWW9YrRqmjuDyrj .arrowheadPath{fill:#333333;}#mermaid-svg-9PWW9YrRqmjuDyrj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9PWW9YrRqmjuDyrj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9PWW9YrRqmjuDyrj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9PWW9YrRqmjuDyrj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9PWW9YrRqmjuDyrj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9PWW9YrRqmjuDyrj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9PWW9YrRqmjuDyrj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9PWW9YrRqmjuDyrj .cluster text{fill:#333;}#mermaid-svg-9PWW9YrRqmjuDyrj .cluster span{color:#333;}#mermaid-svg-9PWW9YrRqmjuDyrj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9PWW9YrRqmjuDyrj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9PWW9YrRqmjuDyrj rect.text{fill:none;stroke-width:0;}#mermaid-svg-9PWW9YrRqmjuDyrj .icon-shape,#mermaid-svg-9PWW9YrRqmjuDyrj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9PWW9YrRqmjuDyrj .icon-shape p,#mermaid-svg-9PWW9YrRqmjuDyrj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9PWW9YrRqmjuDyrj .icon-shape .label rect,#mermaid-svg-9PWW9YrRqmjuDyrj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9PWW9YrRqmjuDyrj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9PWW9YrRqmjuDyrj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9PWW9YrRqmjuDyrj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 项目类型
小型/原型
OOP + Component + FSM
中型项目
Component + BehaviorTree + Event
大型3A
Behavior Tree 主要AI
ECS/Mass 性能层
Event Driven 通信
GAS/Ability System
技能系统
MVVM / MVP / 自研UI框架
DOP 热点优化
策略/模拟游戏
Utility AI + GOAP
综合对比表
| 架构 | 核心思想 | 适用场景 | 性能 | 复杂度 | 扩展性 |
|---|---|---|---|---|---|
| OOP | 继承+封装+多态 | 中小型、原型 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
| Component | 组合功能 | UE/Unity开发 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| DOP | 数据布局优化 | 大规模物理/粒子 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
| ECS | 数据-逻辑分离 | 大型游戏、万人同屏 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Ability System | 数据驱动技能 | MMO/ARPG/MOBA | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| MVC | 三层分离 | UI、编辑器 | ⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
| MVP | 接口解耦 | 可测试UI | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| MVVM | 数据绑定 | 复杂UI | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Observer/Event | 发布-订阅 | 跨系统通信 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| FSM | 状态+转换 | 局部控制、动画 | ⭐⭐⭐⭐⭐ | ⭐ | ⭐ |
| HFSM | 层次化状态 | 复杂状态切换 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| Behavior Tree | 树节点组合 | 主流AI | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| GOAP | 动态规划 | 开放世界NPC | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Utility AI | 评分决策 | 策略/模拟游戏 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
商业游戏AI使用案例
⚠️ 不同品类游戏AI架构差异极大,不存在统一排名。以下仅列出有公开技术资料确认的案例。
| 游戏 | AI架构 | 来源 |
|---|---|---|
| FEAR | GOAP | GDC演讲(经典案例) |
| 模拟人生 | Utility AI | 官方文档 |
| 最后生还者 | HFSM + Behavior Tree | GDC演讲 |
实际项目推荐
cpp
// 1. 独立游戏 / 原型
class Enemy
{
HealthComponent* health;
MovementComponent* movement;
FiniteStateMachine ai;
};
// 2. 中大型商业项目
class GameWorld
{
std::vector<Actor*> actors;
BehaviorTreeRunner ai;
EventBus eventBus;
AbilitySystemComponent abilitySystem;
UIManager ui;
};
// 3. 3A级大型游戏
class TripleAGame
{
EntityComponentSystem ecs;
DataOrientedPipeline dop;
GameplayAbilitySystem gas;
BehaviorTreeSystem behaviorAI;
GoapSystem goap;
UtilitySystem utility;
EventBus eventBus;
std::unique_ptr<IUISystem> ui;
};
16. Unreal Engine 架构映射
概述
以下将前文介绍的架构模式映射到Unreal Engine的对应系统,帮助UE开发者快速定位和使用。
架构映射表
| 概念 | UE对应 | 说明 |
|---|---|---|
| OOP | UObject 体系 |
UE的根基,所有对象继承UObject,支持GC、序列化、反射 |
| Component | ActorComponent / UActorComponent |
每个Actor可挂载多个Component实现功能组合 |
| Observer | Delegate / MulticastDelegate |
单播/多播委托,支持动态绑定 |
| Event | GameplayMessageSubsystem |
UE5的Gameplay消息系统,模块间解耦通信 |
| FSM | Animation Blueprint(状态机) |
动画蓝图本质上是FSM,每个状态对应一个动画混合 |
| HFSM | StateTree(UE5.1+) |
UE5引入的层次化状态机,面向AI行为编排 |
| Behavior Tree | Behavior Tree + Blackboard + EQS |
UE原生BT系统,搭配EQS做环境查询,是官方主力AI方案 |
| GOAP | 无官方实现 | UE社区有GOAP插件,或需自研(基于A*规划) |
| Utility AI | 无官方实现 | UE社区有Utility AI插件,或需自研(评分曲线系统) |
| ECS | Mass Entity |
UE5的Mass框架,面向超大规模实体(万人同屏) |
| DOP | Mass + Niagara |
Mass的数据布局(Archetype/Chunk)、Niagara的粒子计算 |
| Ability System | GameplayAbilitySystem(GAS) |
UE官方技能系统插件,支持技能/Buff/Tag/属性 |
| MVVM | MVVM Plugin(UE5.2 Experimental,UE5.3+逐渐成熟) |
UE官方MVVM框架,此前大量项目自建MVP风格 |
| MVC/MVP | Widget + Controller |
大量UE4/UE5项目的UI架构风格,非官方强制 |
Mass ECS 架构简图
#mermaid-svg-ArBcvoPoOzJIVRe7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ArBcvoPoOzJIVRe7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ArBcvoPoOzJIVRe7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ArBcvoPoOzJIVRe7 .error-icon{fill:#552222;}#mermaid-svg-ArBcvoPoOzJIVRe7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ArBcvoPoOzJIVRe7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ArBcvoPoOzJIVRe7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ArBcvoPoOzJIVRe7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ArBcvoPoOzJIVRe7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ArBcvoPoOzJIVRe7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ArBcvoPoOzJIVRe7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ArBcvoPoOzJIVRe7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ArBcvoPoOzJIVRe7 .marker.cross{stroke:#333333;}#mermaid-svg-ArBcvoPoOzJIVRe7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ArBcvoPoOzJIVRe7 p{margin:0;}#mermaid-svg-ArBcvoPoOzJIVRe7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ArBcvoPoOzJIVRe7 .cluster-label text{fill:#333;}#mermaid-svg-ArBcvoPoOzJIVRe7 .cluster-label span{color:#333;}#mermaid-svg-ArBcvoPoOzJIVRe7 .cluster-label span p{background-color:transparent;}#mermaid-svg-ArBcvoPoOzJIVRe7 .label text,#mermaid-svg-ArBcvoPoOzJIVRe7 span{fill:#333;color:#333;}#mermaid-svg-ArBcvoPoOzJIVRe7 .node rect,#mermaid-svg-ArBcvoPoOzJIVRe7 .node circle,#mermaid-svg-ArBcvoPoOzJIVRe7 .node ellipse,#mermaid-svg-ArBcvoPoOzJIVRe7 .node polygon,#mermaid-svg-ArBcvoPoOzJIVRe7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ArBcvoPoOzJIVRe7 .rough-node .label text,#mermaid-svg-ArBcvoPoOzJIVRe7 .node .label text,#mermaid-svg-ArBcvoPoOzJIVRe7 .image-shape .label,#mermaid-svg-ArBcvoPoOzJIVRe7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-ArBcvoPoOzJIVRe7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ArBcvoPoOzJIVRe7 .rough-node .label,#mermaid-svg-ArBcvoPoOzJIVRe7 .node .label,#mermaid-svg-ArBcvoPoOzJIVRe7 .image-shape .label,#mermaid-svg-ArBcvoPoOzJIVRe7 .icon-shape .label{text-align:center;}#mermaid-svg-ArBcvoPoOzJIVRe7 .node.clickable{cursor:pointer;}#mermaid-svg-ArBcvoPoOzJIVRe7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ArBcvoPoOzJIVRe7 .arrowheadPath{fill:#333333;}#mermaid-svg-ArBcvoPoOzJIVRe7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ArBcvoPoOzJIVRe7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ArBcvoPoOzJIVRe7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ArBcvoPoOzJIVRe7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ArBcvoPoOzJIVRe7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ArBcvoPoOzJIVRe7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ArBcvoPoOzJIVRe7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ArBcvoPoOzJIVRe7 .cluster text{fill:#333;}#mermaid-svg-ArBcvoPoOzJIVRe7 .cluster span{color:#333;}#mermaid-svg-ArBcvoPoOzJIVRe7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ArBcvoPoOzJIVRe7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ArBcvoPoOzJIVRe7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-ArBcvoPoOzJIVRe7 .icon-shape,#mermaid-svg-ArBcvoPoOzJIVRe7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ArBcvoPoOzJIVRe7 .icon-shape p,#mermaid-svg-ArBcvoPoOzJIVRe7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ArBcvoPoOzJIVRe7 .icon-shape .label rect,#mermaid-svg-ArBcvoPoOzJIVRe7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ArBcvoPoOzJIVRe7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ArBcvoPoOzJIVRe7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ArBcvoPoOzJIVRe7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 适用于超大规模Entity
传统Actor
Actor
SceneComponent
CharacterMovementComponent
Unreal Mass ECS
遍历Chunk
Chunk
连续内存
Position Array
Velocity Array
Archetype
Fragment: Position
Fragment: Velocity
Fragment: Health
Processor
MovementProcessor
DamageProcessor
Mass
Mass: 10000+ Entity
Actor: 数百~数千上限
GAS 架构简图
#mermaid-svg-Q46SF6CKkqX03Jf6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Q46SF6CKkqX03Jf6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Q46SF6CKkqX03Jf6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Q46SF6CKkqX03Jf6 .error-icon{fill:#552222;}#mermaid-svg-Q46SF6CKkqX03Jf6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Q46SF6CKkqX03Jf6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Q46SF6CKkqX03Jf6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Q46SF6CKkqX03Jf6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Q46SF6CKkqX03Jf6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Q46SF6CKkqX03Jf6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Q46SF6CKkqX03Jf6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Q46SF6CKkqX03Jf6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Q46SF6CKkqX03Jf6 .marker.cross{stroke:#333333;}#mermaid-svg-Q46SF6CKkqX03Jf6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Q46SF6CKkqX03Jf6 p{margin:0;}#mermaid-svg-Q46SF6CKkqX03Jf6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Q46SF6CKkqX03Jf6 .cluster-label text{fill:#333;}#mermaid-svg-Q46SF6CKkqX03Jf6 .cluster-label span{color:#333;}#mermaid-svg-Q46SF6CKkqX03Jf6 .cluster-label span p{background-color:transparent;}#mermaid-svg-Q46SF6CKkqX03Jf6 .label text,#mermaid-svg-Q46SF6CKkqX03Jf6 span{fill:#333;color:#333;}#mermaid-svg-Q46SF6CKkqX03Jf6 .node rect,#mermaid-svg-Q46SF6CKkqX03Jf6 .node circle,#mermaid-svg-Q46SF6CKkqX03Jf6 .node ellipse,#mermaid-svg-Q46SF6CKkqX03Jf6 .node polygon,#mermaid-svg-Q46SF6CKkqX03Jf6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Q46SF6CKkqX03Jf6 .rough-node .label text,#mermaid-svg-Q46SF6CKkqX03Jf6 .node .label text,#mermaid-svg-Q46SF6CKkqX03Jf6 .image-shape .label,#mermaid-svg-Q46SF6CKkqX03Jf6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Q46SF6CKkqX03Jf6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Q46SF6CKkqX03Jf6 .rough-node .label,#mermaid-svg-Q46SF6CKkqX03Jf6 .node .label,#mermaid-svg-Q46SF6CKkqX03Jf6 .image-shape .label,#mermaid-svg-Q46SF6CKkqX03Jf6 .icon-shape .label{text-align:center;}#mermaid-svg-Q46SF6CKkqX03Jf6 .node.clickable{cursor:pointer;}#mermaid-svg-Q46SF6CKkqX03Jf6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Q46SF6CKkqX03Jf6 .arrowheadPath{fill:#333333;}#mermaid-svg-Q46SF6CKkqX03Jf6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Q46SF6CKkqX03Jf6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Q46SF6CKkqX03Jf6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q46SF6CKkqX03Jf6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Q46SF6CKkqX03Jf6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q46SF6CKkqX03Jf6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Q46SF6CKkqX03Jf6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Q46SF6CKkqX03Jf6 .cluster text{fill:#333;}#mermaid-svg-Q46SF6CKkqX03Jf6 .cluster span{color:#333;}#mermaid-svg-Q46SF6CKkqX03Jf6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Q46SF6CKkqX03Jf6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Q46SF6CKkqX03Jf6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Q46SF6CKkqX03Jf6 .icon-shape,#mermaid-svg-Q46SF6CKkqX03Jf6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q46SF6CKkqX03Jf6 .icon-shape p,#mermaid-svg-Q46SF6CKkqX03Jf6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Q46SF6CKkqX03Jf6 .icon-shape .label rect,#mermaid-svg-Q46SF6CKkqX03Jf6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q46SF6CKkqX03Jf6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Q46SF6CKkqX03Jf6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Q46SF6CKkqX03Jf6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} UE GameplayAbilitySystem
拥有
追踪
管理
持有
应用
触发
修改
条件检查
Grant
AbilitySystemComponent
挂载在Character/Pawn上
GameplayAbility
技能蓝图/C++类
GameplayTag
标签层级系统
GameplayEffect
数据资产/Buff定义
AttributeSet
属性集定义
GameplayCue
通知类反馈
StateTree vs Behavior Tree 对比
| 维度 | Behavior Tree | StateTree(UE5.1+) |
|---|---|---|
| 本质 | 树节点组合(类似决策树) | 层次化状态机(HFSM) |
| 状态管理 | 隐式(节点Running/Success/Failure) | 显式(状态间转换) |
| 适用 | 复杂AI行为决策 | 角色行为切换、战斗状态管理 |
| 事件 | 每帧Tick驱动 | 事件驱动+Tick混用 |
| 并行能力 | 需Parallel节点 | 支持同状态下多Task协同执行 |
| UE版本 | UE4+/UE5全支持 | UE5.1+ |
在UE5项目中,Behavior Tree + StateTree 可组合使用:BT负责高层决策,StateTree负责底层状态切换。两者并非替代关系。
UE项目架构推荐
cpp
// 1. 常规UE5项目(中小型)
class AMyCharacter : public ACharacter
{
UHealthComponent* Health;
UInventoryComponent* Inventory;
UBehaviorTree* BehaviorTree;
UBlackboardComponent* Blackboard;
UAbilitySystemComponent* AbilitySystem;
UAnimInstance* AnimInstance;
};
// 2. UE5大型项目(万人同屏场景)
class AMassGameMode : public AGameModeBase
{
UMassEntitySubsystem* MassEntity;
UBehaviorTreeComponent* BehaviorTree;
UAbilitySystemComponent* AbilitySystem;
UMVVMViewModelCollectionObject* ViewModels;
UGameplayMessageSubsystem* MessageSystem;
};
结语
游戏架构设计没有银弹,每个模式都有其适用场景:
核心原则
- 简单优先:能用FSM解决的问题不要上GOAP
- 数据驱动:性能问题需要数据说话,过早优化是万恶之源
- 为扩展留空间:架构要有弹性,但不要过度设计
- 团队匹配:团队熟悉度 > 架构先进性
- 混合为王:实际项目往往是多种模式的组合
分层思维
基础层: OOP + Component + Event (地基)
性能层: DOP + ECS/Mass (加速)
能力系统层: Ability System / GAS (技能)
UI层: MVC + MVP + MVVM (交互)
AI层: BT + FSM + Utility (智能)
"架构不是一步到位的,而是在迭代中演化的。"