游戏开发核心架构指南

游戏开发核心架构指南

深入理解游戏开发中的核心架构模式:ECS、MVC、MVP、MVVM、OOP、DOP、FSM、HFSM、事件驱动、GOAP、Behavior Tree、Utility AI、Component、Observer、Ability System

目录

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

数据统计
... 更多级联...
⚠️ 事件链路过长

导致不可追踪

性能问题

事件驱动的主要性能问题不在于虚函数调用,而是:

  1. Cache Miss - 事件处理函数分散在不同内存位置
  2. 间接调用 - 函数指针/虚函数影响分支预测
  3. 事件风暴 - 单个事件触发链式反应,事件链路无限增长

优缺点

优点 缺点
系统间完全解耦 事件追踪困难
易于扩展新系统 循环事件风险
代码组织清晰 事件风暴(级联爆炸)
支持异步处理 全局状态管理复杂
灵活的事件组合 调试困难(调用链不透明)

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;
};

结语

游戏架构设计没有银弹,每个模式都有其适用场景:

核心原则

  1. 简单优先:能用FSM解决的问题不要上GOAP
  2. 数据驱动:性能问题需要数据说话,过早优化是万恶之源
  3. 为扩展留空间:架构要有弹性,但不要过度设计
  4. 团队匹配:团队熟悉度 > 架构先进性
  5. 混合为王:实际项目往往是多种模式的组合

分层思维

复制代码
基础层:    OOP + Component + Event    (地基)
性能层:    DOP + ECS/Mass              (加速)
能力系统层:  Ability System / GAS       (技能)
UI层:      MVC + MVP + MVVM           (交互)
AI层:      BT + FSM + Utility          (智能)

"架构不是一步到位的,而是在迭代中演化的。"

相关推荐
-凌凌漆-1 小时前
【Qt】C++中protected与private的区别
开发语言·c++·qt
娟宝宝萌萌哒1 小时前
Agent 应用工程架构:模块、挑战与传统工程迁移
人工智能·架构
草莓熊Lotso2 小时前
【Linux网络】深入理解 HTTP 协议(四):完善 C++ HTTP 服务器:从协议原理到生产级实现
linux·运维·服务器·c语言·网络·c++·http
牛油果子哥q2 小时前
【C++前置声明与头文件】C++前置声明与头文件深度精讲:重复包含、循环依赖、重复定义报错、工程编译架构与实战解决方案
开发语言·c++
少司府2 小时前
C++进阶:map和set的使用
开发语言·数据结构·c++·容器·stl·set·map
程序喵大人2 小时前
C++ 程序员转型 AI Infra 学习路线
c++·人工智能·学习·ai infra
梦梦代码精2 小时前
TP8+Vue3+UniApp:LikeShop架构受青睐!
架构·uni-app
cpp_25012 小时前
P11375 [GESP202412 六级] 树上游走
数据结构·c++·算法·题解·洛谷·树形结构·gesp六级
Whoami!2 小时前
03-【高校】多校区链路加解密架构
网络安全·架构·链路加解密