从此

游戏开发引擎、3D建模、有限状态机(FSM)、ECS


综合/最新

Godot游戏开发 | 知名游戏作品 | 游戏开发文章

新:

游戏实战

状态:
  Idle(立)、Walk(走)、Run(跑)、Jump(跳)、Fall(落)、Attack(攻)、Defend(防)、Die(死)。
  关系:死与所有状态互斥;攻防互斥。
    Jump - 空格;跳起后直接通过离地和落地(无需MaxActiveDuration),来开始及结束状态,并回退至Idle。
    Attack - 鼠标左键;攻击需要MinActiveDuration和MaxActiveDuration来管理再入前摇和后摇,且不会回退。
    Defend - 鼠标右键;

  实务:
    动画播放时为true(临时解决连点时总处于首帧/或用动画完成回调): player.GetNode("AnimationTree").Get("parameters/OneShot/active").AsBool();

  退出游戏:
   public override void _UnhandledInput(InputEvent @event)
   {
    if (@event is InputEventKey eventKey)
    {
        if (eventKey.Pressed && eventKey.Keycode == Key.Escape) { GetTree().Quit(); }
    }
   }

状态机:
  枚举状态机 - 通过 enum + switch case 集中化管控,状态间具有排他性。
  有限状态机(Finite State Machine/FSM) - 分散为继承基类的子类状态对象。
    复合方式实现边走边攻击(缺点是状态较多):Idle、Walk、Attack、Walk_Attack

  并发状态机(Concurrent State Machine) - 状态间可并存;可换用行为树的 Parallel 节点。
    FSM 多实例方式(不优雅) - https://developer.unity.cn/projects/67399c19edbc2a001e3f7076
    多个 IState 字段方式 - https://info.congci.com/main/infomations/articles/8df94b5f-c8cd-11ee-904e-592f6ee49b9d#concurrent
    states 数组方式 - project-templates\programming\godot\projects\csharp\single-player\csm\csm-blend\
      https://www.cnblogs.com/gamedaybyday/p/18993996#t3
    通过代码添加跳转规则? fsm.AddTransition("State", "State2");

  层次状态机(Hierarchical State Machine) - 状态分组,子状态继承父状态的通用处理逻辑。
    CurrentState 存储最深子状态,可反查至根类状态,子状态不处理就向上执行,继承链上均为活跃状态。
    分层状态机原理 - https://zhuanlan.zhihu.com/p/558422986
    override 方式 - https://blog.csdn.net/weixin_42216813/article/details/146218878
    储存继承链方式 - https://github.com/olafvisker/hfsm/blob/main/HFSM.cs
      切换判定,可重写默认的 Finished() { return true; }
    Godot引用Unity HFSM库 - https://github.com/BangL/GodotHFSM-Samples/blob/master/GodotHFSM-Samples/GuardAI/PlayerController.cs
    Godot HFSM2 - https://github.com/Daylily-Zeleen/HFSM2/blob/main/addons/com.daylily_zeleen.hfsm2/CSharpWrappers/State.cs

  下推自动机(Pushdown Automata/PDA) - 堆栈式: https://github.com/AnAdisorn/Godot4-Push-Down-Automata-HFSM/blob/main/state_machine/StateMachine.cs

  分层并发有限状态机 Hierarchical concurrent finite state machine(HCFSM) - 结合HSM和CSM。

  状态机通用模板:
   public class StateMachine
   {
    private State currentState; // 并发状态机则应改为多项值 activeStates。
    public State CurrentState { get => currentState; }

    public void changeState(State state)
    {
        if (currentState != null)
            currentState.Exit(); // 老状态离开时触发。
        currentState = state;
        currentState.Enter(); // 新状态进入时触发。
    }

    public void Tick()
    {

        currentState.Tick(); // FSM 只此一行,而分层状态机则要增加前置或后置处理:

      /*
        // 层次状态机 - https://github.com/olafvisker/hfsm/blob/main/HFSM.cs
        var entry = currentState.GetFinalEntryState(); // 递归首个子状态,直至最末端作为入口状态。
        if (entry != null) changeState(entry);
        currentState.Tick();
        // [以下可选] 注册自动跳转条件 hfsm.To(idle, move, ()=>true); 根据Lambda返回值自动从idle跳至move状态,多项则全部为true才会跳转。
        var to = currentState.GetTransitionState();
        if (to != null) changeState(to);
      */
    }
   }
   /* 分层状态机用法:
        //刷新 hfsm.Tick(delta);
	var two = new Two();
        // 继承后 public class Three : Two { } 可触发基类 Update(double delta) { base.Update(delta); }
	var three = new Three();
	two.AddChildren(three);

	var two2 = new Two2();

	var one = new One();
	one.AddChildren(two, two2);
        //one.SetEntryState(two2); // 设置非首项入口状态。
	hfsm.changeState(one); // 必须是已存在实例。
	hfsm.changeState(two2);
  */

  Godot官方状态机实例(HSM+PDA)
    var states_stack := [] // 比如跳起普攻 [Attack, Jump ,Idle]
    普攻执行完成后会清除栈顶 finished.emit(PLAYER_STATE.previous),变为 [Jump ,Idle],跳完再执行 states_stack.pop_front() 清顶为 [Idle]。
    连招独立状态机(switch形式) - finite_state_machine/player/weapon/sword.gd
      连招超时通过动画树函数计步:起手set_attack_input_listening、播完set_ready_for_next_attack。


行为树

C#行为树插件+类库暂未用上的行为树插件 <ItemGroup><PackageReference Include="GroveGames.BehaviourTree" Version="0.4.10" /></ItemGroup> 解压插件 behaviour-tree-0.4.10-godot-addon.zip 编辑器Debugger选项卡Behaviour Tree Debugger启用:运行 -> 远程 -> 选中 GodotBehaviourTree 节点 -> 勾上 Debuggable;后续估计会在 项目设置 里添加该插件选项。 简述之 - 逐帧执行 GodotBehaviourTree 类 SetupTree() 链式添加的各种节点: Sequence(整体成功方Success、首个失败即短路Failure),用于严格匹配。 Selector(整体失败方Failure、首个成功即短路Success),用于宽松回退。 Parallel(非线性/全部执行并符合传入ParallelPolicy),解决Sequence当前节点返回Running时,下一节点执行不到的场景。 可取代并发状态机! Decorator(纯条件判定节点/子类Conditional等)。 bt.Abort() - 中断Running状态节点。 var rs = Root.Selector(); // 直接节点用 rs.Conditional(() => true)...,其他节点类型可链式构造: var sr = rs.Sequence().Conditional(() => true).Cooldown(1f).Repeater(RepeatMode.UntilSuccess); sr.Attach(new Attack(sr)); // 成功则执行相应动作:通常为 Execute()、Evaluate(float delta) 方法名。 // 或显式包装 seq.Attach(new HasEnemy(_enemy, _entity, seq)); var seq = rs.Sequence(); seq = seq.Attach(new Conditional(seq, () => true)).Cooldown(1f)... 关键行 - public override void _Process(double delta) => bt.Tick((float)delta); // 牵扯物理计算可换至 _PhysicsProcess。 上下文 - Blackboard.SetValue("target_pos_enemey", entity.GetParent().GetNode("entity")); // 存储玩家角色位置供NPC敌人走近。 实战图 - 行为参考 https://blog.csdn.net/weixin_42216813/article/details/146218878#3_HFSM__44 Selector Selector - 要么走、要么攻击。 Walk、Attack Idle - 不走不攻击则回退至站立。

架构

ECS: Entity component system
  目的 - 使行为能add/remove,提升灵活度,分解复杂度,而CPU L1\L2内存连续的读取性能提升只是附带。
  场景 - OOP对象同时提供数据和行为,整体载入会拖累性能,因此ECS采用了分拆组合的方式,将数据和行为进行了分离,甚至对两者按业务再次划分,使各自关注点的噪音更小。
    实体是游戏对象的容器,它没有任何行为或属性,只是一个标识符。组件是游戏对象的属性或行为,例如位置、速度、生命值等。系统是游戏对象的行为逻辑,例如移动、攻击、碰撞检测等。
  内存连续原理 - https://blog.csdn.net/ylmbtm/article/details/121430868
  ECS结合FSM、行为树用法 - https://pixelmatic.github.io/articles/2020/05/13/ecs-and-ai.html
  C# ECS框架(支持Godot) - https://github.com/genaray/Arch/wiki/Integration-Guides#godot
    用法 - https://github.com/Neerti/Arch-ECS-Godot-Demo/blob/main/Demo/EntityRenderer.cs
      
      创建Entity: World.Create(new Position {Vec2 = new Vector2(GD.Randf() * GetViewportRect().Size.X, GD.Randf() * GetViewportRect().Size.Y)},
        new Velocity {Vec2 = new Vector2((float)GD.RandRange(-1.0f, 1.0f) * 200f, (float)GD.RandRange(-1.0f, 1.0f) * 200f)},
        new Sprite {SpriteColor = new Color(GD.Randf(), GD.Randf(), GD.Randf())});
      [清除Entities] World.Clear();
      var sys = new MovementSystem(World.Create(), new Rect2I(0, 0, GetTree().Root.Size));
      _PhysicsProcess 调用 sys.Update(delta); sys2.Update(delta);

  GDS ECS框架 - https://godotengine.org/asset-library/asset/3481

  运行逻辑 - 某个业务系统筛选出拥有这个业务系统相关组件的实体,对这些实体的相关组件进行处理更新。www.cnblogs.com/hggzhang/p/17161722.html
  即 - sys.add(comp); systems.add(sys); while (true) { for systems.run() }

  C#默认堆不便实现内存连续,故要自行处理,可参考Arch ECS框架实现:
    https://github.com/genaray/Arch/blob/master/src/Arch/Core/Chunk.cs

  ●Entity: 表示一个游戏对象,它通常只包含一个表示对象ID的唯一标识符。它拥有一个Component的集合,这些不同类型的Component集合构成一个特定类型的游戏对象。此外,Entity 也可用来作为属性间通信的枢纽。
  ●Component: 表示一个属性的数据部分,例如HealthComponent 表示一个血量属性的数据。通常Component是不包含实例方法的,除了一些方便的get/set,或者内部数据处理方法。
  ●System: 表示一个属性的行为部分,例如HealthSystem表示血量属性的行为,它会检测HealthComponent的值,如果其血量小于或等于0时则向Entity发出死亡通知事件。System 通常由游戏循环驱动来修改游戏对象的状态,典型的System 包含一个update (更新)方法。

1. ECS 结合 FSM 的实现方式
FSM 在 ECS 中通常有两种主要的实现方法,它们都遵循 ECS 的原则:数据驱动逻辑。

方法一:状态作为 Component Tag(最符合 ECS 理念)
状态(State) 被定义为一个 空 Component (Tag Component) 或一个只包含少量数据的 Component。

转换(Transition) 由专门的 System 来处理。

例如,要让一个实体进入“攻击”状态,只需给它添加一个 AttackStateComponent,并移除旧的 IdleStateComponent。

逻辑(Behavior) 分散在不同的 System 中。

IdleSystem 只查询拥有 IdleStateComponent 的实体,并执行空闲逻辑(如巡逻)。

AttackSystem 只查询拥有 AttackStateComponent 的实体,并执行攻击逻辑。

优点: 性能高,数据紧凑。System 的查询(Query)直接筛选出了处于特定状态的所有实体,非常适合并行计算。

缺点: 状态过多时会导致 System 和 Component 数量爆炸,以及 Archetype(数据块)的碎片化。


方法二:状态作为 Component 字段(更像传统 FSM)
状态(State) 被存储在实体的一个 AIStateComponent 的枚举(Enum)字段中。

FSM System 负责所有实体的状态转换,根据实体的数据和事件来改变这个枚举值。

行为逻辑 仍然分散在不同的 System 中,但这些 System 需要在内部检查状态枚举值。

AISystem 查询所有拥有 AIStateComponent 的实体,然后内部用 switch/case 语句根据状态枚举来调用不同的逻辑函数。

优点: 易于理解和实现,更像传统的 FSM,状态管理集中。

缺点: 破坏了 ECS 的**“最小化数据”**原则,System 必须处理不相关的数据(即在 switch 中跳过很多状态),不利于最极致的并行优化。

游戏引擎

UPBGE (Uchronia Project Blender Game Engine)  - Blender Game Engine (BGE) 分支,与 Blender 深度集成,通过 Python 编写游戏的 3D 游戏引擎。
Godot 布娃娃:
  选中Skeleton3D -> Skeleton -> Create Physical Skeleton:将创建PhysicalBoneSimulator3D和自动生成子节点们PhysicalBone3D。
  执行瘫软姿势:PhysicalBoneSimulator3D.physical_bones_start_simulation();

其他

只能鼠标点击,不响应Enter等确认键:Button.FocusMode = FocusModeEnum.None;

Blender 为动画批量增加根骨骼的 plugin - https://github.com/snougo/MixamoTool

行为树实例 -   
  https://github.com/grovegs/BehaviourTree/blob/main/sandbox/GodotApplication/node_3d.tscn
TestSceneController 类:
  多处 - Node 改为 BehaviourNode
  一处 - Tree 改为 GodotBehaviourTree、Root 改为 BehaviourRoot
GodotBehaviourTree.Debug 类:
  底部 Nodes.Node.Empty 改为 Nodes.BehaviourNode.Empty