UE5集成Entt ECS框架:混合架构提升大规模实体性能 1. 项目概述为什么要在UE5里引入Entt如果你是一个用惯了UE5传统面向对象OOP模式的开发者第一次听到“在第三人称模板里集成Entt”这个想法可能会觉得有点“离经叛道”。毕竟虚幻引擎自己那套基于Actor和Component的Gameplay框架GAS是另一回事已经相当成熟了为什么还要费劲引入一个外部的ECSEntity-Component-System库呢我最初也是带着这个疑问开始的。在几个中型项目的性能优化攻坚期我们团队遇到了典型的“Actor膨胀”问题场景里一旦有上千个带有简单逻辑的实体比如大量需要寻路和状态更新的小怪、可交互的植被、动态环境粒子游戏线程GameThread的负担就会急剧上升。尽管UE5的Nanite和Lumen在渲染层面带来了革新但Gameplay逻辑的更新效率尤其是大量相似实体的批量处理依然是性能瓶颈。这时候ECS架构那种“数据导向设计”Data-Oriented Design的优势就凸显出来了——它通过将数据组件与逻辑系统分离并让数据在内存中连续排列极大地提升了CPU缓存利用率特别适合需要高频、批量更新大量同质化实体的场景。Entt是一个用现代C17/20编写的、头文件-only的ECS库以其极致的编译时速度、灵活的组件存储和令人惊叹的易用性在C社区闻名。它不像Unity的ECS或某些学术框架那样重而是更像一个“乐高积木”可以无缝嵌入到现有架构中。在UE5项目里集成Entt并不是要完全取代Actor而是寻求一种混合架构用Actor处理高层次的、复杂的、需要编辑器交互的对象比如主角、BOSS、关卡触发器而用Entt来高效驱动海量的、逻辑相对简单的“数据实体”比如弹幕、小兵、特效控制器。这个项目的核心目标就是在UE5官方的第三人称游戏模板这是大多数人的起点基础上搭建一个轻量、非侵入式的Entt集成框架。让你既能享受UE5强大的编辑器和渲染管线又能获得ECS带来的性能红利为项目应对大规模实体场景提前做好技术储备。2. 整体架构设计与思路拆解在动手写代码之前我们必须想清楚Entt和UE5原生框架如何共存。一个粗暴的“二选一”方案是行不通的我们需要一个清晰的边界和通信桥梁。2.1 核心设计哲学混合架构与职责划分我们的设计遵循一个核心原则“Actor管表现与交互Entt管数据与逻辑”。UE5 Actor (AActor)职责渲染表现StaticMesh/SkeletalMesh组件、物理碰撞Collision、动画蓝图AnimBlueprint、用户交互PlayerController、网络复制Replication以及所有需要在编辑器里直观摆放和配置的功能。优势与引擎深度集成可视化编辑功能全面。劣势内存开销相对较大一个空的AActor也有一定开销逻辑更新是分散的每个Actor的Tick不利于批量处理。Entt 实体 (entt::entity)职责纯数据存储位置、速度、生命值等状态和纯粹的计算逻辑移动系统、伤害系统、状态机系统。优势内存紧凑数据连续缓存友好系统可以一次性处理成千上万个实体性能极高。劣势没有直接的渲染、物理或网络支持需要与Actor桥接。那么它们如何关联呢我们引入一个**“代理人”AgentActor**的概念。例如一个“小兵”在游戏中会有一个可见的ACharacter或AActor负责渲染和受击反馈同时在Entt的世界里会有一个对应的entt::entity它拥有TransformComponent、HealthComponent、AIControllerComponent等数据。这个Agent Actor持有一个指向其对应Entt实体的句柄或ID并在每帧的Tick中从Entt系统获取最新的计算结果如位置然后应用到自己的SceneComponent上实现视觉同步。2.2 项目结构规划为了保持项目整洁我们建议在UE5项目的Source/YourProjectName/目录下创建如下结构Source/ └── YourProject/ ├── Private/ │ ├── ECS/ # Entt相关核心实现 │ │ ├── ECSWorld.cpp │ │ ├── Components/ # 所有组件定义 │ │ │ ├── TransformComp.cpp │ │ │ ├── HealthComp.cpp │ │ │ └── ... │ │ └── Systems/ # 所有系统定义 │ │ ├── MovementSystem.cpp │ │ ├── AISystem.cpp │ │ └── ... │ └── ... └── Public/ ├── ECS/ │ ├── ECSWorld.h │ ├── Components/ │ │ ├── TransformComp.h │ │ ├── HealthComp.h │ │ └── ... │ ├── Systems/ │ │ ├── MovementSystem.h │ │ ├── AISystem.h │ │ └── ... │ └── ECSAgentInterface.h # 代理Actor接口 └── ...关键文件说明ECSWorld.h/cppEntt注册表entt::registry的单例或全局管理器封装。负责实体的创建、销毁和所有系统的更新调度。Components/*.h/cpp定义所有Entt组件它们是纯数据的PODPlain Old Data结构体或类。Systems/*.h/cpp定义所有系统它们是包含Update函数的类在ECSWorld的驱动下遍历拥有特定组件组合的实体并执行逻辑。ECSAgentInterface.h一个UInterface让任何UE5 Actor都能声明自己是某个Entt实体的代理并提供获取/设置实体ID的方法。2.3 依赖管理与集成方式Entt是头文件库集成非常简单。有两种推荐方式Git Submodule推荐将Entt的官方仓库作为子模块添加到你的项目里。这样做的好处是版本清晰易于更新。# 在你的项目根目录执行 git submodule add https://github.com/skypjack/entt.git Source/ThirdParty/entt然后在项目的.Build.cs文件中将Source/ThirdParty/entt/src添加到包含路径PrivateIncludePaths或PublicIncludePaths。直接复制头文件如果项目结构要求简单可以直接下载Entt的single header版本entt.hpp放入项目的Source/YourProject/Public/下的某个目录如ThirdParty/entt并在需要的地方包含它。注意确保你的UE5项目设置中C语言标准至少设置为C17因为Entt大量使用了现代C特性。在YourProject.Build.cs中可以添加bEnableCpp17 true;对于更新的引擎版本可能是CppStandard CppStandardVersion.Cpp17;。3. 核心模块实现与代码解析理论说完了我们开始动手实现。我会从最核心的ECSWorld开始然后定义组件和系统最后实现与UE5 Actor的桥接。3.1 ECSWorld全局管理器与更新循环ECSWorld是我们整个ECS框架的发动机。它封装了entt::registry并管理所有系统的执行顺序。ECSWorld.h#pragma once #include CoreMinimal.h #include entt/entt.hpp #include ECSWorld.generated.h // 如果需要UCLASS但通常ECSWorld是纯C类 // 前向声明系统 class UECSSystemBase; /** * 全局ECS世界管理器。建议作为GameInstance的子对象或全局单例存在。 */ class YOURPROJECT_API FECSWorld { public: static FECSWorld Get(); // 初始化与销毁 void Initialize(); void Shutdown(); // 实体操作 entt::entity CreateEntity(); void DestroyEntity(entt::entity Entity); // 组件操作 templatetypename TComponent, typename... TArgs TComponent AddComponent(entt::entity Entity, TArgs... Args); templatetypename TComponent TComponent* GetComponent(entt::entity Entity); templatetypename TComponent void RemoveComponent(entt::entity Entity); // 系统注册与更新 void RegisterSystem(TSharedPtrUECSSystemBase System); void Update(float DeltaTime); // 获取底层注册表供高级用户使用 entt::registry GetRegistry() { return Registry; } private: FECSWorld(); ~FECSWorld(); entt::registry Registry; TArrayTSharedPtrUECSSystemBase Systems; static TUniquePtrFECSWorld Instance; };ECSWorld.cpp关键部分#include ECSWorld.h #include Systems/ECSSystemBase.h TUniquePtrFECSWorld FECSWorld::Instance nullptr; FECSWorld FECSWorld::Get() { if (!Instance.IsValid()) { Instance MakeUniqueFECSWorld(); } return *Instance; } void FECSWorld::Initialize() { // 可以在这里预分配实体池初始化默认组件等 Registry.reserve(1024); // 预分配实体空间 } void FECSWorld::Update(float DeltaTime) { // 按注册顺序更新所有系统 for (auto System : Systems) { if (System.IsValid() System-IsEnabled()) { System-Update(Registry, DeltaTime); } } // 可选处理在本帧被标记为“待销毁”的实体 // Registry.destroy(ToBeDestroyedEntities...); } entt::entity FECSWorld::CreateEntity() { return Registry.create(); } templatetypename TComponent, typename... TArgs TComponent FECSWorld::AddComponent(entt::entity Entity, TArgs... Args) { return Registry.emplaceTComponent(Entity, std::forwardTArgs(Args)...); } // 其他模板函数实现...如何接入UE5游戏循环我们通常在GameMode或一个专用的GameState的Tick中调用FECSWorld::Get().Update(DeltaTime)。为了更精细的控制可以创建一个UECSSubsystem继承自UTickableWorldSubsystem这样就能自动集成到引擎的更新循环中。3.2 组件定义纯数据容器组件必须是纯数据不包含逻辑。我们以TransformComponent和HealthComponent为例。TransformComp.h#pragma once #include CoreMinimal.h #include TransformComp.generated.h // 注意这里用USTRUCT方便蓝图访问如果需要 /** * 用于ECS的变换组件。与FTransform类似但更轻量。 */ USTRUCT(BlueprintType) struct YOURPROJECT_API FTransformComponent { GENERATED_BODY() public: FTransformComponent() : Location(FVector::ZeroVector), Rotation(FRotator::ZeroRotator), Scale(FVector::OneVector) {} UPROPERTY(BlueprintReadWrite, EditAnywhere) FVector Location; UPROPERTY(BlueprintReadWrite, EditAnywhere) FRotator Rotation; UPROPERTY(BlueprintReadWrite, EditAnywhere) FVector Scale; // 辅助函数 FTransform ToTransform() const { return FTransform(Rotation, Location, Scale); } void FromTransform(const FTransform Transform) { Location Transform.GetLocation(); Rotation Transform.GetRotation().Rotator(); Scale Transform.GetScale3D(); } };注意这里我们使用了USTRUCT和UPROPERTY。这并非必须但如果你想在蓝图中创建或调试这些组件数据这会非常有用。如果追求极致性能且无需蓝图交互可以定义为普通的C结构体。HealthComp.h#pragma once #include CoreMinimal.h /** * 生命值组件。普通结构体性能最优。 */ struct FHealthComponent { float CurrentHealth 100.0f; float MaxHealth 100.0f; bool IsAlive() const { return CurrentHealth 0.0f; } void TakeDamage(float Damage) { CurrentHealth FMath::Max(0.0f, CurrentHealth - Damage); } void Heal(float Amount) { CurrentHealth FMath::Min(MaxHealth, CurrentHealth Amount); } };3.3 系统实现逻辑处理器系统是逻辑所在。每个系统只关心拥有特定组件组合的实体。我们定义一个基类然后实现具体的系统。ECSSystemBase.h#pragma once #include CoreMinimal.h #include entt/entt.hpp /** * 所有ECS系统的基类。 */ class YOURPROJECT_API UECSSystemBase : public UObject { GENERATED_BODY() public: virtual void Update(entt::registry Registry, float DeltaTime) 0; virtual bool IsEnabled() const { return bEnabled; } void SetEnabled(bool bInEnabled) { bEnabled bInEnabled; } private: bool bEnabled true; };MovementSystem.cpp(实现文件)#include MovementSystem.h #include ../Components/TransformComp.h #include ../Components/VelocityComp.h // 假设有一个速度组件 void UMovementSystem::Update(entt::registry Registry, float DeltaTime) { // 关键使用entt的视图view来高效遍历所有同时拥有Transform和Velocity组件的实体。 // 这是一个只读视图如果不需要修改组件用const_view性能更好。 auto View Registry.viewFTransformComponent, const FVelocityComponent(); // 遍历所有符合条件的实体。这是数据导向的核心内存访问连续缓存命中率高。 for (auto [Entity, Transform, Velocity] : View.each()) { // 应用速度到位置 Transform.Location Velocity.LinearVelocity * DeltaTime; // 可以在这里添加简单的物理如重力 // Velocity.LinearVelocity FVector(0, 0, -980.0f) * DeltaTime; // 标记Transform组件为“已脏”如果需要通知代理Actor同步 // Registry.patchFTransformComponent(Entity); // 如果组件是普通结构体patch可以触发观察者 } }这里有一个非常重要的性能技巧Registry.viewCompA, CompB()会返回一个视图它遍历的是所有同时拥有CompA和CompB的实体。Entt内部会保证相同类型的组件在内存中连续存储所以这个循环的CPU缓存效率极高。这是ECS性能远超传统OOP中每个Actor独立Tick的关键。3.4 桥接层ECSAgentInterface与同步这是连接UE5世界和Entt世界的桥梁。ECSAgentInterface.h#pragma once #include CoreMinimal.h #include UObject/Interface.h #include ECSAgentInterface.generated.h UINTERFACE(MinimalAPI, Blueprintable) class UECSAgentInterface : public UInterface { GENERATED_BODY() }; /** * 任何希望作为Entt实体代理的UE5 Actor都应实现此接口。 */ class YOURPROJECT_API IECSAgentInterface { GENERATED_BODY() public: // 获取此Actor对应的Entt实体ID。如果尚未创建应返回entt::null。 virtual entt::entity GetECSEntity() const 0; // 设置实体ID。通常在Agent创建并注册到ECSWorld时调用。 virtual void SetECSEntity(entt::entity Entity) 0; // 每帧从ECS组件同步数据到Actor属性如位置、旋转。 UFUNCTION(BlueprintNativeEvent, Category ECS) void SyncFromECS(); // 每帧将Actor的属性同步到ECS组件如从控制器接收输入。 UFUNCTION(BlueprintNativeEvent, Category ECS) void SyncToECS(); };一个具体的代理Actor实现AECSAgentCharacter.h部分UCLASS() class YOURPROJECT_API AECSAgentCharacter : public ACharacter, public IECSAgentInterface { GENERATED_BODY() public: virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; virtual void Tick(float DeltaTime) override; // IECSAgentInterface 实现 virtual entt::entity GetECSEntity() const override { return MyECSEntity; } virtual void SetECSEntity(entt::entity Entity) override { MyECSEntity Entity; } virtual void SyncFromECS_Implementation() override; virtual void SyncToECS_Implementation() override; protected: entt::entity MyECSEntity entt::null; UPROPERTY(EditDefaultsOnly, Category ECS) bool bAutoRegisterWithECS true; };AECSAgentCharacter.cpp关键同步逻辑void AECSAgentCharacter::BeginPlay() { Super::BeginPlay(); if (bAutoRegisterWithECS HasAuthority()) // 通常只在服务器端创建ECS实体 { auto ECSWorld FECSWorld::Get(); MyECSEntity ECSWorld.CreateEntity(); // 1. 将Actor的初始状态同步到ECS组件 FTransformComponent TransformComp; TransformComp.FromTransform(GetActorTransform()); ECSWorld.AddComponentFTransformComponent(MyECSEntity, TransformComp); // 2. 添加其他必要组件如生命值 ECSWorld.AddComponentFHealthComponent(MyECSEntity); // 3. 可以添加一个标记组件用于系统识别这是“角色”实体 ECSWorld.AddComponentFAgentTagComponent(MyECSEntity, this); } } void AECSAgentCharacter::Tick(float DeltaTime) { Super::Tick(DeltaTime); if (MyECSEntity ! entt::null) { // 将ECS逻辑计算的结果同步回Actor例如位置更新 SyncFromECS(); // 将Actor当前的状态如输入同步到ECS组件 SyncToECS(); } } void AECSAgentCharacter::SyncFromECS_Implementation() { auto ECSWorld FECSWorld::Get(); if (auto* TransformComp ECSWorld.GetComponentFTransformComponent(MyECSEntity)) { // 将ECS中的位置应用到Actor。这里可以加入插值平滑避免视觉抖动。 SetActorTransform(TransformComp-ToTransform()); } if (auto* HealthComp ECSWorld.GetComponentFHealthComponent(MyECSEntity)) { // 同步生命值到Actor的UI或状态机 // UpdateHealthUI(HealthComp-CurrentHealth); if (!HealthComp-IsAlive()) { // 触发死亡逻辑 OnDeath(); } } } void AECSAgentCharacter::SyncToECS_Implementation() { // 例如将角色的移动输入同步到ECS的速度组件 auto ECSWorld FECSWorld::Get(); if (auto* VelocityComp ECSWorld.GetComponentFVelocityComponent(MyECSEntity)) { // 假设我们从PlayerController获取了输入向量 FVector InputVector GetPendingMovementInputVector(); VelocityComp-LinearVelocity InputVector * MoveSpeed; } } void AECSAgentCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (MyECSEntity ! entt::null) { FECSWorld::Get().DestroyEntity(MyECSEntity); MyECSEntity entt::null; } Super::EndPlay(EndPlayReason); }4. 在第三人称模板中的集成步骤现在我们将上述框架集成到UE5的第三人称模板中。假设你的项目叫MyECSGAME。4.1 第一步准备项目与引入Entt使用Epic Games Launcher创建一个基于**第三人称游戏模板C**的新项目MyECSGAME。按照上文“依赖管理”部分将Entt库作为子模块或头文件引入项目。修改MyECSGAME.Build.cs确保包含路径正确并启用C17。using UnrealBuildTool; public class MyECSGAME : ModuleRules { public MyECSGAME(ReadOnlyTargetRules Target) : base(Target) { PCHUsage PCHUsageMode.UseExplicitOrSharedPCHs; CppStandard CppStandardVersion.Cpp17; // 确保是C17或更高 PublicDependencyModuleNames.AddRange(new string[] { Core, CoreUObject, Engine, InputCore, EnhancedInput }); // 添加Entt头文件路径 string EnttPath Path.Combine(ModuleDirectory, ../ThirdParty/entt/src); PublicIncludePaths.Add(EnttPath); // 或者如果是复制单头文件 // PublicIncludePaths.Add(Path.Combine(ModuleDirectory, Public/ThirdParty)); } }在Source/MyECSGAME/目录下创建ECS文件夹结构Public/Private。4.2 第二步实现核心ECS模块按照第3节的内容依次创建并实现ECSWorld.h/cppComponents/下的几个基础组件TransformComp,HealthComp,VelocityCompSystems/下的基类ECSSystemBase和一个简单的MovementSystemECSAgentInterface.h在MyECSGAMEGameMode.h/cpp中初始化FECSWorld并驱动其更新。// MyECSGAMEGameMode.h public: virtual void Tick(float DeltaTime) override; // MyECSGAMEGameMode.cpp #include ECS/ECSWorld.h void AMyECSGAMEGameMode::Tick(float DeltaTime) { Super::Tick(DeltaTime); FECSWorld::Get().Update(DeltaTime); }4.3 第三步改造第三人称角色复制或修改模板生成的MyECSGAMECharacter通常继承自Character我们将其重命名为AECSAgentCharacter并使其继承自ACharacter和IECSAgentInterface。在AECSAgentCharacter中实现第3.4节中的桥接逻辑。关键修改注释或重写原角色中由CharacterMovementComponent驱动的移动逻辑。我们的移动将由ECS的MovementSystem计算然后在SyncFromECS中应用到角色位置。这意味着你需要将输入处理在SetupPlayerInputComponent或EnhancedInput回调中从直接调用AddMovementInput改为设置一个FVector变量这个变量会在SyncToECS中被同步到ECS的VelocityComponent。在AECSGAMECharacter::BeginPlay中确保ECS实体被创建并且初始位置被同步。4.4 第四步创建测试实体与系统创建一个简单的AISystem它遍历所有拥有TransformComponent和AITargetComponent包含一个目标位置的实体并计算朝向目标移动的速度。在游戏模式BeginPlay时通过FECSWorld::Get().RegisterSystem()注册你的MovementSystem和AISystem。在场景中放置几个AECSAgentCharacter作为玩家或AI并运行游戏。你应该能看到角色的移动逻辑已经从原生的CharacterMovementComponent转移到了ECS系统中。5. 性能优化、调试与常见问题集成完成后真正的挑战在于如何用好它。这里分享一些实战中的经验和坑。5.1 性能优化要点视图View的正确使用viewCompA, CompB()vsviewCompA(entt::excludeCompC)Entt的视图非常灵活。使用exclude可以排除拥有某些组件的实体性能开销极小。持久化视图如果一个系统每帧都要用相同的组件组合查询可以创建entt::basic_view成员变量并缓存它避免每帧重建视图的开销。只读与可写如果系统只读取某个组件使用const引用Registry.viewFTransformComponent, const FVelocityComponent()。这能给予编译器更多优化空间有时也能避免不必要的缓存失效。组件设计保持组件小巧组件应该是POD或简单类。避免在组件内存储指针尤其是UE4/UObject指针这会导致跨系统管理困难。如果需要引用存储entt::entity句柄或TWeakObjectPtr。使用标记组件对于只有存在与否意义的布尔状态如IsPlayer,IsDead可以创建一个空的struct TagPlayer {};组件。用registry.hasTagPlayer(entity)检查比在某个组件里加一个bool字段并进行过滤更符合ECS范式且查询效率高。内存与缓存实体池大量创建销毁实体会引发内存碎片。Entt的registry内部有对象池但你可以通过registry.reserveComponent(count)为特定组件预分配内存。分组Group对于固定组合且需要极致性能的场景可以使用entt::group。它能在创建实体时就确定其归属提供比视图更快的迭代速度但灵活性稍差。5.2 调试与可视化在UE5里调试纯C的ECS数据是个挑战。有几个实用技巧控制台命令创建自定义的Exec函数通过控制台输出ECS世界状态。// 在某个Manager类中 bool YourManager::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice Ar) { if (FParse::Command(Cmd, TEXT(ECS.DumpStats))) { auto Reg FECSWorld::Get().GetRegistry(); Ar.Logf(TEXT(Total Entities: %d), Reg.alive()); // 遍历并打印实体信息... return true; } return false; }在游戏中按“~”打开控制台输入ECS.DumpStats即可查看信息。蓝图调试节点通过ECSAgentInterface暴露一些只读属性到蓝图比如GetECSHealth这样你就可以在蓝图中看到同步过来的ECS生命值。ImGui集成如果你熟悉ImGui可以将其集成到UE5中创建一个实时的ECS实体/组件查看器窗口这是最强大的调试手段。5.3 常见问题与解决方案编译错误找不到entt头文件检查.Build.cs文件中的PublicIncludePaths或PrivateIncludePaths是否正确指向了包含entt.hpp的目录。检查源代码文件中#include entt/entt.hpp的路径是否正确。如果是单头文件可能是#include ThirdParty/entt.hpp。运行时崩溃访问无效的entt::entity原因实体已被销毁但代理Actor仍持有其旧ID并尝试访问。解决在IECSAgentInterface的SyncFromECS等函数中首先检查MyECSEntity ! entt::null Registry.valid(MyECSEntity)。entt::registry::valid()可以检查实体是否仍然有效。数据不同步Actor位置没有更新排查步骤确认ECS系统如MovementSystem的Update函数被正常调用加日志。在MovementSystem的循环中打印Transform.Location的变化看计算是否生效。在代理Actor的SyncFromECS函数中打印从ECS获取到的Transform数据。检查代理Actor的Tick是否被启用以及MyECSEntity是否正确赋值。性能提升不明显分析使用Unreal Insights的CPU Profiler查看MovementSystem.Update的耗时。如果实体数量少几十个ECS的优势可能被系统调度的固定开销抵消。验证尝试将实体数量增加到数千个例如生成大量只做简单运动的立方体对比纯Actor Tick方案和ECS方案的帧率。检查确保你的组件是平凡可复制的trivially copyable并且视图在遍历大量实体。如何与UE5的物理、动画、网络协同工作物理ECS负责计算期望的速度和位置然后将最终的位置通过SetActorTransform设置给Actor。Actor的RootComponent如果启用了物理模拟可能会与这个设置冲突。通常对于由ECS完全控制的实体我们禁用其Actor的物理模拟SetSimulatePhysics(false)或者使用Kinematic模式由ECS每帧设置其目标位置。动画动画状态如是否移动、是否攻击可以作为组件存储在ECS中如FAnimationStateComponent。动画蓝图通过代理Actor获取这些状态驱动状态机。网络复制这是最复杂的部分。一个可行的策略是只在服务器端运行完整的ECS逻辑。服务器端的ECS计算所有实体的状态然后将关键组件数据位置、生命值通过RPC或属性复制同步到客户端。客户端代理Actor接收到数据后应用并可能进行插值平滑。客户端的ECS可以只运行表现层的系统如粒子效果触发系统。集成Entt到UE5不是一个“一键替换”的过程而是一个渐进式的架构演进。从一个小模块比如弹幕系统、环境交互系统开始试点逐步积累经验再应用到更核心的游戏逻辑上是更稳妥和高效的做法。这套混合架构为你打开了另一扇门让你在面对大规模实体、复杂数据交互的游戏类型时手中多了一件性能利器。