ECS)
打開Godot4引擎之后在任意的目錄下新建一個(gè)C#腳本此時(shí)在項(xiàng)目根目錄下會(huì)自動(dòng)生成一個(gè).sln文件可以打開文件資源管理器進(jìn)入到項(xiàng)目根目錄然后雙擊打開.sln會(huì)自動(dòng)打開VisualStudio2026它能自動(dòng)識(shí)別為解決方案項(xiàng)目。打開cmd到項(xiàng)目根目錄并且確認(rèn)已經(jīng)安裝donet-sdk8.0。安裝ECS框架和擴(kuò)展# 安裝核心Arch ECS包 dotnet add package Arch # 可選配套包推薦一起裝#Arch.System 系統(tǒng)基類dotnet add package Arch.System#Arch.System.SourceGenerator 源代碼生成器減少反射大幅提速dotnet add package Arch.System.SourceGenerator#Arch-Events 事件擴(kuò)展dotnet add package Arch-Events然后要引入Arch包到項(xiàng)目中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/!-- 保護(hù)Arch程序集防止裁剪Arch內(nèi)部泛型、組件元數(shù)據(jù) --TrimmerRootAssemblyIncludeArchRootModeAll//ItemGroup/Project可以在場(chǎng)景中添加一個(gè)太陽(yáng)、天空環(huán)境5.然后必須創(chuàng)建一個(gè)相機(jī)才可以看得見5. 調(diào)整相機(jī)角度到合適的位置6. 編寫一個(gè)ECS腳本代碼創(chuàng)建25萬(wàn)個(gè)物體給物體繞著任意旋轉(zhuǎn)軸旋轉(zhuǎn)新建一個(gè)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;// 構(gòu)造變換存入transforms數(shù)組_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();}}然后把腳本拖拽到根節(jié)點(diǎn)8. 要選擇一個(gè)網(wǎng)格最后構(gòu)建項(xiàng)目運(yùn)行項(xiàng)目25萬(wàn)個(gè)物體同時(shí)旋轉(zhuǎn)耗時(shí)9 ~ 10毫秒左右如果打包成Release包可以進(jìn)一步加快速度到6 ~ 7毫秒左右的耗時(shí)。使用傳統(tǒng)的節(jié)點(diǎn)對(duì)象創(chuàng)建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(請(qǐng)?jiān)跈z視面板給 DonutMesh 賦值 Mesh 資源);SetProcess(false);return;}_rotationAxesnewVector3[InstanceCount];_rotationSpeedsnewfloat[InstanceCount];_rotationAnglesnewfloat[InstanceCount];_transformBuffernewfloat[InstanceCount*TransformStride];// 格式必須在設(shè)置 InstanceCount分配實(shí)例緩沖區(qū)之前指定。_multiMeshnewMultiMesh{TransformFormatMultiMesh.TransformFormatEnum.Transform3D,MeshDonutMesh,InstanceCountInstanceCount};// 預(yù)設(shè)能包圍所有旋轉(zhuǎn)姿態(tài)的邊界避免逐幀重新計(jì)算 25 萬(wàn)個(gè)實(shí)例的 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;// 每個(gè) 3D 變換占 12 個(gè) float按 3 行旋轉(zhuǎn)矩陣 平移存儲(chǔ)。WriteBasis(offset,Basis.Identity);_transformBuffer[offset3]x*Spacing-250.0f;_transformBuffer[offset7]0.0f;_transformBuffer[offset11]z*Spacing-250.0f;// 隨機(jī)三維單位旋轉(zhuǎn)軸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 實(shí)例數(shù)量{InstanceCount}使用 1 個(gè) MultiMeshInstance3D);}publicoverridevoid_Process(doubledelta){if(_multiMeshnull)return;ulongt1Time.GetTicksMsec();floatdt(float)delta;for(inti0;iInstanceCount;i){// 從累計(jì)角度重建旋轉(zhuǎn)避免持續(xù)乘矩陣產(chǎn)生縮放/正交誤差。_rotationAngles[i]Mathf.PosMod(_rotationAngles[i]_rotationSpeeds[i]*dt,Mathf.Tau);WriteBasis(i*TransformStride,newBasis(_rotationAxes[i],_rotationAngles[i]));}// 每幀一次批量提交不再逐個(gè)調(diào)用 Rotate 或 SetInstanceTransform。_multiMesh.Buffer_transformBuffer;ulongt2Time.GetTicksMsec();GD.Print(t2-t1);}privatevoidWriteBasis(intoffset,Basisbasis){// Basis.X/Y/Z 是列向量MultiMesh 緩沖區(qū)需要按行寫入。_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;}}經(jīng)過運(yùn)行發(fā)現(xiàn)每一幀率耗時(shí)高達(dá)100毫秒左右。從視覺上對(duì)比會(huì)發(fā)現(xiàn)差距非常大。幀率對(duì)比采用Mono實(shí)體同時(shí)旋轉(zhuǎn)25萬(wàn)個(gè)Mesh網(wǎng)格FPS低到可憐的9幀/秒采用ECS框架同樣的網(wǎng)格數(shù)量同時(shí)旋轉(zhuǎn)高達(dá)79幀/秒ECS框架適合超多的實(shí)體的場(chǎng)景。