
打开Godot4引擎之后在任意的目录下新建一个C#脚本此时在项目根目录下会自动生成一个.sln文件可以打开文件资源管理器进入到项目根目录然后双击打开.sln会自动打开VisualStudio2026它能自动识别为解决方案项目。打开cmd到项目根目录并且确认已经安装donet-sdk8.0。安装ECS框架和扩展# 安装核心Arch ECS包 dotnet add package Arch # 可选配套包推荐一起装#Arch.System 系统基类dotnet add package Arch.System#Arch.System.SourceGenerator 源代码生成器减少反射大幅提速dotnet add package Arch.System.SourceGenerator#Arch-Events 事件扩展dotnet add package Arch-Events然后要引入Arch包到项目中ProjectSdkGodot.NET.Sdk/4.7.0PropertyGroupTargetFrameworknet8.0/TargetFrameworkEnableDynamicLoadingtrue/EnableDynamicLoadingPublishReadyToRuntrue/PublishReadyToRunSelfContainedtrue/SelfContainedAllowUnsafeBlocksTrue/AllowUnsafeBlocks/PropertyGroupItemGroupPackageReferenceIncludeArchVersion2.1.0/PackageReferenceIncludeArch-EventsVersion2.1.0/PackageReferenceIncludeArch.SystemVersion1.1.0/PackageReferenceIncludeArch.System.SourceGeneratorVersion2.1.0/TrimmerRootAssemblyIncludeGodotSharp/TrimmerRootAssemblyInclude$(TargetName)//ItemGroupItemGroupCondition$(Configuration)ReleaseTrimmerRootAssemblyInclude$(AssemblyName)RootModeAll/!-- 保护Arch程序集防止裁剪Arch内部泛型、组件元数据 --TrimmerRootAssemblyIncludeArchRootModeAll//ItemGroup/Project可以在场景中添加一个太阳、天空环境5.然后必须创建一个相机才可以看得见5. 调整相机角度到合适的位置6. 编写一个ECS脚本代码创建25万个物体给物体绕着任意旋转轴旋转新建一个CreateMultiEcsEntity.cs脚本usingArch;usingArch.Core;usingGodot;usingSystem;usingSystem.Diagnostics;usingSystem.Runtime.CompilerServices;usingSystem.Runtime.InteropServices;usingSystem.Threading.Tasks;// ECS data is rendered by one MultiMesh; no per-entity Node3D is created.publicpartialclassCreateMultiEcsEntity:Node3D{[Export]publicMeshTorusPrefab{get;set;}[Export(PropertyHint.Range,1,200000,1)]publicintGridSize{get;set;}500;[Export]publicfloatSpacing{get;set;}2.2f;privateWorld_ecsWorld;privateMultiMesh_multiMesh;privateEntityState[]_statesArray.EmptyEntityState();// Filled in parallel; submitted through Godots main-thread-only MultiMesh API.privateTransform3D[]_transformsArray.EmptyTransform3D();publicstructRotateComp{publicintIndex;publicVector3Axis;publicfloatSpeed;}ParallelOptionsparallelOpt;[StructLayout(LayoutKind.Sequential,Pack1)]privateunsafestructEntityState{publicVector3Position;publicVector3Axis;publicfloatSpeed;publicQuaternionRotation;//public fixed byte Padding[20];}publicoverridevoid_Ready(){varsizeUnsafeUnsafe.SizeOfEntityState();parallelOptnewParallelOptions{MaxDegreeOfParallelismSystem.Environment.ProcessorCount};if(TorusPrefabnull){GD.PrintErr(TorusPrefab must reference a Mesh.);return;}_ecsWorldWorld.Create();_multiMeshnewMultiMesh{TransformFormatMultiMesh.TransformFormatEnum.Transform3D,MeshTorusPrefab};AddChild(newMultiMeshInstance3D{Multimesh_multiMesh});SpawnTorusEcsEntities(Math.Max(1,GridSize));}publicvoidSpawnTorusEcsEntities(intgridSize){intcountchecked(gridSize*gridSize);_statesnewEntityState[count];_transformsnewTransform3D[count];_multiMesh.InstanceCountcount;for(intx0;xgridSize;x){for(intz0;zgridSize;z){intindexx*gridSizez;Vector3axisnewVector3((float)GD.RandRange(1.0,2.0),(float)GD.RandRange(2.0,3.0),(float)GD.RandRange(1.0,2.0)).Normalized();floatspeed(float)GD.RandRange(0.4,1.5);Vector3positionnewVector3((x-(gridSize-1)/2.0f)*Spacing,0,(z-(gridSize-1)/2.0f)*Spacing);_states[index]newEntityState{Positionposition,Axisaxis,Speedspeed,RotationQuaternion.Identity};_transforms[index]newTransform3D(Basis.Identity,position);_ecsWorld.Create(newRotateComp{Indexindex,Axisaxis,Speedspeed});}}SubmitTransforms();}publicoverridevoid_Process(doubledelta){if(_states.Length0)return;ulongt1Time.GetTicksMsec();floatdt(float)delta;// CPU-side ECS rotation and Transform3D construction run in parallel.Parallel.For(0,_states.Length,parallelOpt,i{refEntityStatestateref_states[i];floatangleDeltastate.Speed*dt;QuaternionrotStepnewQuaternion(state.Axis,angleDelta);state.RotationrotStep*state.Rotation;// 构造变换存入transforms数组_transforms[i]newTransform3D(newBasis(state.Rotation),state.Position);});// MultiMesh is a Godot resource and must only be modified from the main thread.SubmitTransforms();ulongt2Time.GetTicksMsec();GD.Print(t2-t1);}privatevoidSubmitTransforms(){for(inti0;i_transforms.Length;i){_multiMesh.SetInstanceTransform(i,_transforms[i]);}}publicoverridevoid_ExitTree(){_ecsWorld?.Dispose();}}然后把脚本拖拽到根节点8. 要选择一个网格最后构建项目运行项目25万个物体同时旋转耗时9 ~ 10毫秒左右如果打包成Release包可以进一步加快速度到6 ~ 7毫秒左右的耗时。使用传统的节点对象创建CreateMultiMonoObj.cs脚本usingGodot;usingSystem;publicpartialclassCreateMultiMonoObj:Node3D{privateconstintGridSize500;privateconstintInstanceCountGridSize*GridSize;privateconstintTransformStride12;[Export]publicMeshDonutMesh{get;set;}null;[Export]publicfloatSpacing2.5f;[Export]publicfloatRotateSpeedMin5.0f;[Export]publicfloatRotateSpeedMax8.0f;privateMultiMesh_multiMesh;privateVector3[]_rotationAxesArray.EmptyVector3();privatefloat[]_rotationSpeedsArray.Emptyfloat();privatefloat[]_rotationAnglesArray.Emptyfloat();privatefloat[]_transformBufferArray.Emptyfloat();publicoverridevoid_Ready(){if(DonutMeshnull){GD.PushWarning(请在检视面板给 DonutMesh 赋值 Mesh 资源);SetProcess(false);return;}_rotationAxesnewVector3[InstanceCount];_rotationSpeedsnewfloat[InstanceCount];_rotationAnglesnewfloat[InstanceCount];_transformBuffernewfloat[InstanceCount*TransformStride];// 格式必须在设置 InstanceCount分配实例缓冲区之前指定。_multiMeshnewMultiMesh{TransformFormatMultiMesh.TransformFormatEnum.Transform3D,MeshDonutMesh,InstanceCountInstanceCount};// 预设能包围所有旋转姿态的边界避免逐帧重新计算 25 万个实例的 AABB。AabbmeshBoundsDonutMesh.GetAabb();floatradiusmeshBounds.Position.Abs().Max(meshBounds.End.Abs()).Length();floatgridEnd(GridSize-1)*Spacing-250.0f;floatminMathf.Min(-250.0f,gridEnd)-radius;floatmaxMathf.Max(-250.0f,gridEnd)radius;_multiMesh.CustomAabbnewAabb(newVector3(min,-radius,min),newVector3(max-min,radius*2.0f,max-min));for(intx0;xGridSize;x){for(intz0;zGridSize;z){intindexx*GridSizez;intoffsetindex*TransformStride;// 每个 3D 变换占 12 个 float按 3 行旋转矩阵 平移存储。WriteBasis(offset,Basis.Identity);_transformBuffer[offset3]x*Spacing-250.0f;_transformBuffer[offset7]0.0f;_transformBuffer[offset11]z*Spacing-250.0f;// 随机三维单位旋转轴Vector3randomAxisnewVector3(GD.Randf()*2.0f-1.0f,GD.Randf()*2.0f-1.0f,GD.Randf()*2.0f-1.0f);_rotationAxes[index]randomAxis.IsZeroApprox()?Vector3.Up:randomAxis.Normalized();_rotationSpeeds[index]Mathf.Lerp(RotateSpeedMin,RotateSpeedMax,GD.Randf());}}_multiMesh.Buffer_transformBuffer;AddChild(newMultiMeshInstance3D{NameDonutMultiMesh,Multimesh_multiMesh});GD.Print($生成 MultiMesh 实例数量{InstanceCount}使用 1 个 MultiMeshInstance3D);}publicoverridevoid_Process(doubledelta){if(_multiMeshnull)return;ulongt1Time.GetTicksMsec();floatdt(float)delta;for(inti0;iInstanceCount;i){// 从累计角度重建旋转避免持续乘矩阵产生缩放/正交误差。_rotationAngles[i]Mathf.PosMod(_rotationAngles[i]_rotationSpeeds[i]*dt,Mathf.Tau);WriteBasis(i*TransformStride,newBasis(_rotationAxes[i],_rotationAngles[i]));}// 每帧一次批量提交不再逐个调用 Rotate 或 SetInstanceTransform。_multiMesh.Buffer_transformBuffer;ulongt2Time.GetTicksMsec();GD.Print(t2-t1);}privatevoidWriteBasis(intoffset,Basisbasis){// Basis.X/Y/Z 是列向量MultiMesh 缓冲区需要按行写入。_transformBuffer[offset]basis.X.X;_transformBuffer[offset1]basis.Y.X;_transformBuffer[offset2]basis.Z.X;_transformBuffer[offset4]basis.X.Y;_transformBuffer[offset5]basis.Y.Y;_transformBuffer[offset6]basis.Z.Y;_transformBuffer[offset8]basis.X.Z;_transformBuffer[offset9]basis.Y.Z;_transformBuffer[offset10]basis.Z.Z;}}经过运行发现每一帧率耗时高达100毫秒左右。从视觉上对比会发现差距非常大。帧率对比采用Mono实体同时旋转25万个Mesh网格FPS低到可怜的9帧/秒采用ECS框架同样的网格数量同时旋转高达79帧/秒ECS框架适合超多的实体的场景。