refactor(ecs): 将 ECS 系统重构为基于 Arch 的原生实现

- 将 MovementSystem 从继承 EcsSystemBase 改为继承 ArchSystemAdapter
- 更新 MovementSystem 的初始化和更新方法以适配 Arch 架构
- 移除测试代码中的 ECS 相关接口实现和抽象层
- 将测试用例从 GFramework ECS API 迁移到 Arch 原生 API
- 更新 ECS 测试类以使用 Arch World 和实体操作方法
- 重构 ECS 模块初始化流程以支持 Arch 系统注册和管理
This commit is contained in:
GeWuYou 2026-03-01 23:36:02 +08:00 committed by gewuyou
parent 40ba109671
commit 43dc5a4d11
6 changed files with 276 additions and 650 deletions

View File

@ -1,6 +1,5 @@
using GFramework.Core.Abstractions.architecture; using GFramework.Core.Abstractions.architecture;
using GFramework.Core.Abstractions.command; using GFramework.Core.Abstractions.command;
using GFramework.Core.Abstractions.ecs;
using GFramework.Core.Abstractions.environment; using GFramework.Core.Abstractions.environment;
using GFramework.Core.Abstractions.events; using GFramework.Core.Abstractions.events;
using GFramework.Core.Abstractions.ioc; using GFramework.Core.Abstractions.ioc;
@ -428,16 +427,6 @@ public class TestArchitectureContextV3 : IArchitectureContext
{ {
return _environment; return _environment;
} }
public IEcsWorld GetEcsWorld()
{
throw new NotImplementedException("ECS not implemented in test context");
}
public void RegisterEcsSystem<T>() where T : class, IEcsSystem
{
throw new NotImplementedException("ECS not implemented in test context");
}
} }
#endregion #endregion

View File

@ -1,6 +1,5 @@
using GFramework.Core.Abstractions.architecture; using GFramework.Core.Abstractions.architecture;
using GFramework.Core.Abstractions.command; using GFramework.Core.Abstractions.command;
using GFramework.Core.Abstractions.ecs;
using GFramework.Core.Abstractions.environment; using GFramework.Core.Abstractions.environment;
using GFramework.Core.Abstractions.events; using GFramework.Core.Abstractions.events;
using GFramework.Core.Abstractions.ioc; using GFramework.Core.Abstractions.ioc;
@ -452,14 +451,4 @@ public class TestArchitectureContext : IArchitectureContext
{ {
return Environment; return Environment;
} }
public IEcsWorld GetEcsWorld()
{
throw new NotImplementedException("ECS not implemented in test context");
}
public void RegisterEcsSystem<T>() where T : class, IEcsSystem
{
throw new NotImplementedException("ECS not implemented in test context");
}
} }

View File

@ -1,303 +1,158 @@
using System.Reflection; using System.Diagnostics.CodeAnalysis;
using Arch.Core; using Arch.Core;
using GFramework.Core.Abstractions.ecs;
using GFramework.Core.Abstractions.rule; using GFramework.Core.Abstractions.rule;
using GFramework.Core.architecture; using GFramework.Core.architecture;
using GFramework.Core.ecs; using GFramework.Core.ecs;
using GFramework.Core.ecs.components; using GFramework.Core.ecs.components;
using GFramework.Core.ecs.systems; using GFramework.Core.ecs.systems;
using GFramework.Core.ioc; using GFramework.Core.ioc;
using GFramework.Core.logging;
using NUnit.Framework; using NUnit.Framework;
namespace GFramework.Core.Tests.ecs; namespace GFramework.Core.Tests.ecs;
/// <summary>
/// ECS 高级功能测试类 - 使用 Arch 原生 API
/// </summary>
[TestFixture] [TestFixture]
[Experimental("GFrameworkECS")]
public class EcsAdvancedTests public class EcsAdvancedTests
{ {
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
_container = new MicrosoftDiContainer(); _container = new MicrosoftDiContainer();
var loggerField = typeof(MicrosoftDiContainer).GetField("_logger",
BindingFlags.NonPublic | BindingFlags.Instance);
loggerField?.SetValue(_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(EcsAdvancedTests)));
_context = new ArchitectureContext(_container); _context = new ArchitectureContext(_container);
} }
[TearDown] [TearDown]
public void TearDown() public void TearDown()
{ {
_ecsWorld?.Dispose(); if (_world != null)
_ecsWorld = null; {
World.Destroy(_world);
_world = null;
}
_container?.Clear(); _container?.Clear();
_context = null; _context = null;
_ecsModule = null;
} }
private MicrosoftDiContainer? _container; private MicrosoftDiContainer? _container;
private ArchitectureContext? _context; private ArchitectureContext? _context;
private EcsWorld? _ecsWorld; private World? _world;
private ArchEcsModule? _ecsModule;
private void InitializeEcsWithSystems(params Type[] systemTypes) private void InitializeEcsModule()
{ {
_ecsWorld = new EcsWorld();
_container!.Register(_ecsWorld);
_container.Register(_ecsWorld as IEcsWorld);
var systems = new List<IEcsSystem>();
foreach (var systemType in systemTypes)
{
var system = (IEcsSystem)Activator.CreateInstance(systemType)!;
((IContextAware)system).SetContext(_context!);
system.Initialize();
systems.Add(system);
_container.RegisterPlurality(system);
}
_container.Register(systems as IReadOnlyList<IEcsSystem>);
}
private EcsSystemRunner CreateRunner()
{
var runner = new EcsSystemRunner();
((IContextAware)runner).SetContext(_context!);
runner.Initialize();
return runner;
}
[Test]
public void EcsWorld_Dispose_Should_Be_Idempotent()
{
_ecsWorld = new EcsWorld();
_ecsWorld.CreateEntity(typeof(Position));
Assert.DoesNotThrow(() =>
{
_ecsWorld.Dispose();
_ecsWorld.Dispose();
});
_ecsWorld = null;
}
[Test]
public void EcsWorld_CreateEntity_WithNoComponents_Should_Work()
{
_ecsWorld = new EcsWorld();
var entity = _ecsWorld.CreateEntity();
Assert.That(_ecsWorld.EntityCount, Is.EqualTo(1));
Assert.That(_ecsWorld.IsAlive(entity), Is.True);
}
[Test]
public void EcsWorld_CreateEntity_WithMultipleComponents_Should_Work()
{
_ecsWorld = new EcsWorld();
var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity));
var world = _ecsWorld.InternalWorld;
Assert.That(world.Has<Position>(entity), Is.True);
Assert.That(world.Has<Velocity>(entity), Is.True);
}
[Test]
public void EcsWorld_IsAlive_AfterDestroy_Should_ReturnFalse()
{
_ecsWorld = new EcsWorld();
var entity = _ecsWorld.CreateEntity(typeof(Position));
Assert.That(_ecsWorld.IsAlive(entity), Is.True);
_ecsWorld.DestroyEntity(entity);
Assert.That(_ecsWorld.IsAlive(entity), Is.False);
}
[Test]
public void EcsSystemRunner_Update_WithoutStart_Should_NotUpdate()
{
InitializeEcsWithSystems(typeof(MovementSystem));
var entity = _ecsWorld!.CreateEntity(typeof(Position), typeof(Velocity));
var world = _ecsWorld.InternalWorld;
world.Set(entity, new Position(0, 0));
world.Set(entity, new Velocity(10, 5));
var runner = CreateRunner();
runner.Update(1.0f);
ref var pos = ref world.Get<Position>(entity);
Assert.That(pos.X, Is.EqualTo(0), "Position should not change without Start()");
Assert.That(pos.Y, Is.EqualTo(0), "Position should not change without Start()");
}
[Test]
public void EcsSystemRunner_StartStop_Should_ControlUpdates()
{
InitializeEcsWithSystems(typeof(MovementSystem));
var entity = _ecsWorld!.CreateEntity(typeof(Position), typeof(Velocity));
var world = _ecsWorld.InternalWorld;
world.Set(entity, new Position(0, 0));
world.Set(entity, new Velocity(10, 5));
var runner = CreateRunner();
runner.Start();
runner.Update(1.0f);
runner.Stop();
runner.Update(1.0f);
ref var pos = ref world.Get<Position>(entity);
Assert.That(pos.X, Is.EqualTo(10).Within(0.001f), "Only first update should apply");
Assert.That(pos.Y, Is.EqualTo(5).Within(0.001f), "Only first update should apply");
}
[Test]
public void EcsSystemRunner_WithNoSystems_Should_NotThrow()
{
_ecsWorld = new EcsWorld();
_container!.Register(_ecsWorld);
_container.Register(new List<IEcsSystem>() as IReadOnlyList<IEcsSystem>);
var runner = CreateRunner();
Assert.DoesNotThrow(() =>
{
runner.Start();
runner.Update(1.0f);
runner.Stop();
});
}
[Test]
public void EcsSystemRunner_OnDestroy_Should_ClearSystems()
{
InitializeEcsWithSystems(typeof(MovementSystem));
var entity = _ecsWorld!.CreateEntity(typeof(Position), typeof(Velocity));
var world = _ecsWorld.InternalWorld;
world.Set(entity, new Position(0, 0));
world.Set(entity, new Velocity(10, 5));
var runner = CreateRunner();
runner.Start();
// 销毁前先更新一次,记录初始位置
runner.Update(1.0f);
ref var posBeforeDestroy = ref world.Get<Position>(entity);
var xBefore = posBeforeDestroy.X;
var yBefore = posBeforeDestroy.Y;
runner.Destroy();
// 销毁后再更新,位置应该保持不变
runner.Update(1.0f);
ref var posAfterDestroy = ref world.Get<Position>(entity);
Assert.That(posAfterDestroy.X, Is.EqualTo(xBefore), "Position should not change after Destroy()");
Assert.That(posAfterDestroy.Y, Is.EqualTo(yBefore), "Position should not change after Destroy()");
}
[Test]
public void MultipleSystems_Should_ExecuteInPriorityOrder()
{
var executionOrder = new List<string>();
_ecsWorld = new EcsWorld();
_container!.Register(_ecsWorld);
var systemA = new OrderTrackingSystem("A", 10, executionOrder);
var systemB = new OrderTrackingSystem("B", -10, executionOrder);
var systemC = new OrderTrackingSystem("C", 0, executionOrder);
foreach (var system in new[] { systemA, systemB, systemC })
{
((IContextAware)system).SetContext(_context!);
system.Initialize();
_container.RegisterPlurality(system);
}
_container.Register(new List<IEcsSystem> { systemA, systemB, systemC } as IReadOnlyList<IEcsSystem>);
var runner = CreateRunner();
runner.Start();
runner.Update(1.0f);
Assert.That(executionOrder, Is.EqualTo(["B", "C", "A"]),
"Systems should execute in priority order (B=-10, C=0, A=10)");
}
[Test]
public void ChainedSystems_Should_PassDataBetweenSystems()
{
_ecsWorld = new EcsWorld();
_container!.Register(_ecsWorld);
var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity));
var world = _ecsWorld.InternalWorld;
world.Set(entity, new Position(0, 0));
world.Set(entity, new Velocity(10, 0));
var movementSystem = new MovementSystem(); var movementSystem = new MovementSystem();
((IContextAware)movementSystem).SetContext(_context!); ((IContextAware)movementSystem).SetContext(_context!);
movementSystem.Initialize(); _container!.RegisterPlurality(movementSystem);
_container.RegisterPlurality(movementSystem);
_container.Register(new List<IEcsSystem> { movementSystem } as IReadOnlyList<IEcsSystem>); _ecsModule = new ArchEcsModule(enabled: true);
_ecsModule.Register(_container);
_ecsModule.Initialize();
var runner = CreateRunner(); _world = _container.Get<World>();
runner.Start(); }
runner.Update(1.0f);
runner.Update(1.0f);
ref var pos = ref world.Get<Position>(entity); [Test]
public void World_Destroy_Should_Be_Safe()
{
InitializeEcsModule();
_world!.Create(new Position(0, 0));
Assert.DoesNotThrow(() =>
{
World.Destroy(_world);
_world = null;
});
}
[Test]
public void World_CreateEntity_WithNoComponents_Should_Work()
{
InitializeEcsModule();
var entity = _world!.Create();
Assert.That(_world.Size, Is.EqualTo(1));
Assert.That(_world.IsAlive(entity), Is.True);
}
[Test]
public void World_CreateEntity_WithMultipleComponents_Should_Work()
{
InitializeEcsModule();
var entity = _world!.Create(new Position(0, 0), new Velocity(1, 1));
Assert.That(_world.Has<Position>(entity), Is.True);
Assert.That(_world.Has<Velocity>(entity), Is.True);
}
[Test]
public void World_IsAlive_AfterDestroy_Should_ReturnFalse()
{
InitializeEcsModule();
var entity = _world!.Create(new Position(0, 0));
Assert.That(_world.IsAlive(entity), Is.True);
_world.Destroy(entity);
Assert.That(_world.IsAlive(entity), Is.False);
}
[Test]
public void ArchEcsModule_Update_Should_UpdateSystems()
{
InitializeEcsModule();
var entity = _world!.Create(new Position(0, 0), new Velocity(10, 5));
_ecsModule!.Update(1.0f);
ref var pos = ref _world.Get<Position>(entity);
Assert.That(pos.X, Is.EqualTo(10).Within(0.001f));
Assert.That(pos.Y, Is.EqualTo(5).Within(0.001f));
}
[Test]
public void ArchEcsModule_WithNoSystems_Should_NotThrow()
{
// 不注册任何系统
_ecsModule = new ArchEcsModule(enabled: true);
_ecsModule.Register(_container!);
_ecsModule.Initialize();
_world = _container!.Get<World>();
Assert.DoesNotThrow(() => { _ecsModule.Update(1.0f); });
}
[Test]
public void ChainedUpdates_Should_AccumulateChanges()
{
InitializeEcsModule();
var entity = _world!.Create(new Position(0, 0), new Velocity(10, 0));
_ecsModule!.Update(1.0f);
_ecsModule.Update(1.0f);
ref var pos = ref _world.Get<Position>(entity);
Assert.That(pos.X, Is.EqualTo(20).Within(0.001f), "Position should accumulate over multiple updates"); Assert.That(pos.X, Is.EqualTo(20).Within(0.001f), "Position should accumulate over multiple updates");
} }
[Test]
public void InitializeEcs_CalledTwice_Should_BeIdempotent()
{
_ecsWorld = new EcsWorld();
_container!.Register(_ecsWorld);
_container.Register<IEcsWorld>(_ecsWorld);
var ecsWorld1 = _context!.GetEcsWorld();
var ecsWorld2 = _context.GetEcsWorld();
Assert.That(ecsWorld2, Is.SameAs(ecsWorld1), "Should return same world instance");
}
[Test]
public void GetEcsWorld_Should_ReturnIEcsWorld()
{
_ecsWorld = new EcsWorld();
_container!.Register(_ecsWorld);
_container.Register<IEcsWorld>(_ecsWorld);
var ecsWorld = _context!.GetEcsWorld();
Assert.That(ecsWorld, Is.InstanceOf<IEcsWorld>());
Assert.That(ecsWorld, Is.InstanceOf<EcsWorld>());
}
[Test] [Test]
public void Component_AddAfterCreation_Should_Work() public void Component_AddAfterCreation_Should_Work()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
var entity = _ecsWorld.CreateEntity(Array.Empty<ComponentType>()); var entity = _world!.Create();
var world = _ecsWorld.InternalWorld;
world.Add(entity, new Position(5, 10)); _world.Add(entity, new Position(5, 10));
Assert.That(world.Has<Position>(entity), Is.True); Assert.That(_world.Has<Position>(entity), Is.True);
ref var pos = ref world.Get<Position>(entity); ref var pos = ref _world.Get<Position>(entity);
Assert.That(pos.X, Is.EqualTo(5)); Assert.That(pos.X, Is.EqualTo(5));
Assert.That(pos.Y, Is.EqualTo(10)); Assert.That(pos.Y, Is.EqualTo(10));
} }
@ -305,42 +160,57 @@ public class EcsAdvancedTests
[Test] [Test]
public void Component_Remove_Should_Work() public void Component_Remove_Should_Work()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); var entity = _world!.Create(new Position(0, 0), new Velocity(1, 1));
var world = _ecsWorld.InternalWorld;
world.Remove<Velocity>(entity); _world.Remove<Velocity>(entity);
Assert.That(world.Has<Position>(entity), Is.True); Assert.That(_world.Has<Position>(entity), Is.True);
Assert.That(world.Has<Velocity>(entity), Is.False); Assert.That(_world.Has<Velocity>(entity), Is.False);
} }
[Test] [Test]
public void Component_Replace_Should_Work() public void Component_Replace_Should_Work()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
var entity = _ecsWorld.CreateEntity(typeof(Position)); var entity = _world!.Create(new Position(1, 1));
var world = _ecsWorld.InternalWorld;
world.Set(entity, new Position(1, 1)); _world.Set(entity, new Position(100, 200));
world.Set(entity, new Position(100, 200));
ref var pos = ref world.Get<Position>(entity); ref var pos = ref _world.Get<Position>(entity);
Assert.That(pos.X, Is.EqualTo(100)); Assert.That(pos.X, Is.EqualTo(100));
Assert.That(pos.Y, Is.EqualTo(200)); Assert.That(pos.Y, Is.EqualTo(200));
} }
}
internal class OrderTrackingSystem(string name, int priority, List<string> executionOrder) : EcsSystemBase [Test]
{ public async Task ArchEcsModule_DestroyAsync_Should_CleanupResources()
public override int Priority { get; } = priority;
protected override void OnEcsInit()
{ {
InitializeEcsModule();
_world!.Create(new Position(0, 0));
Assert.That(_world.Size, Is.EqualTo(1));
await _ecsModule!.DestroyAsync();
// World 应该已经被销毁
_world = null;
} }
public override void Update(float deltaTime) [Test]
public void MultipleEntities_WithDifferentComponents_Should_CoExist()
{ {
executionOrder.Add(name); InitializeEcsModule();
var entity1 = _world!.Create(new Position(0, 0), new Velocity(1, 1));
var entity2 = _world.Create(new Position(10, 10));
var entity3 = _world.Create(new Velocity(5, 5));
Assert.That(_world.Size, Is.EqualTo(3));
Assert.That(_world.Has<Position>(entity1), Is.True);
Assert.That(_world.Has<Velocity>(entity1), Is.True);
Assert.That(_world.Has<Position>(entity2), Is.True);
Assert.That(_world.Has<Velocity>(entity2), Is.False);
Assert.That(_world.Has<Position>(entity3), Is.False);
Assert.That(_world.Has<Velocity>(entity3), Is.True);
} }
} }

View File

@ -1,229 +1,161 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Arch.Core; using Arch.Core;
using GFramework.Core.Abstractions.ecs; using GFramework.Core.Abstractions.rule;
using GFramework.Core.architecture; using GFramework.Core.architecture;
using GFramework.Core.ecs; using GFramework.Core.ecs;
using GFramework.Core.ecs.components; using GFramework.Core.ecs.components;
using GFramework.Core.ecs.systems; using GFramework.Core.ecs.systems;
using GFramework.Core.ioc; using GFramework.Core.ioc;
using GFramework.Core.logging;
using NUnit.Framework; using NUnit.Framework;
namespace GFramework.Core.Tests.ecs; namespace GFramework.Core.Tests.ecs;
/// <summary> /// <summary>
/// ECS基础功能测试类用于验证ECS系统的核心功能。 /// ECS 基础功能测试类 - 使用 Arch 原生 API
/// 包括实体创建、组件设置、系统更新、实体销毁等基本操作。
/// </summary> /// </summary>
[TestFixture] [TestFixture]
[Experimental("GFrameworkECS")] [Experimental("GFrameworkECS")]
public class EcsBasicTests public class EcsBasicTests
{ {
/// <summary>
/// 测试初始化方法,在每个测试方法执行前运行。
/// 负责初始化日志工厂、依赖注入容器和架构上下文。
/// </summary>
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
_container = new MicrosoftDiContainer(); _container = new MicrosoftDiContainer();
var loggerField = typeof(MicrosoftDiContainer).GetField("_logger",
BindingFlags.NonPublic | BindingFlags.Instance);
loggerField?.SetValue(_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(EcsBasicTests)));
_context = new ArchitectureContext(_container); _context = new ArchitectureContext(_container);
} }
/// <summary>
/// 测试清理方法,在每个测试方法执行后运行。
/// 负责释放ECS世界资源并清空容器和上下文。
/// </summary>
[TearDown] [TearDown]
public void TearDown() public void TearDown()
{ {
_ecsWorld?.Dispose(); if (_world != null)
_ecsWorld = null; {
World.Destroy(_world);
_world = null;
}
_container?.Clear(); _container?.Clear();
_context = null; _context = null;
_ecsModule = null;
} }
private MicrosoftDiContainer? _container; private MicrosoftDiContainer? _container;
private ArchitectureContext? _context; private ArchitectureContext? _context;
private EcsWorld? _ecsWorld; private World? _world;
private ArchEcsModule? _ecsModule;
/// <summary> /// <summary>
/// 初始化ECS系统并注册指定类型的系统实例。 /// 初始化 ECS 模块
/// </summary> /// </summary>
/// <param name="systemTypes">需要注册的系统类型数组</param> private void InitializeEcsModule()
private void InitializeEcsWithSystems(params Type[] systemTypes)
{ {
_ecsWorld = new EcsWorld(); // 注册系统(直接继承 ArchSystemAdapter它继承自 AbstractSystem
_container!.Register(_ecsWorld); var movementSystem = new MovementSystem();
_container.Register(_ecsWorld as IEcsWorld); ((IContextAware)movementSystem).SetContext(_context!);
_container!.RegisterPlurality(movementSystem);
var systems = new List<IEcsSystem>(); // 创建并注册 ArchEcsModule
foreach (var systemType in systemTypes) _ecsModule = new ArchEcsModule(enabled: true);
{ _ecsModule.Register(_container);
var system = (IEcsSystem)Activator.CreateInstance(systemType)!; _ecsModule.Initialize();
system.SetContext(_context!);
system.Initialize();
systems.Add(system);
_container.RegisterPlurality(system);
}
_container.Register(systems as IReadOnlyList<IEcsSystem>); // 获取 World
_world = _container.Get<World>();
} }
/// <summary>
/// 测试ECS初始化功能验证是否能正确创建EcsWorld实例。
/// </summary>
[Test] [Test]
[Experimental("GFrameworkECS")] public void Test_01_InitializeEcs_Should_Create_World()
public void Test_01_InitializeEcs_Should_Create_EcsWorld()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
_container!.Register(_ecsWorld);
_container.Register<IEcsWorld>(_ecsWorld);
var ecsWorld = _context!.GetEcsWorld(); Assert.That(_world, Is.Not.Null, "World should be created");
Assert.That(_world!.Size, Is.EqualTo(0), "Initial entity count should be 0");
Assert.That(ecsWorld, Is.Not.Null, "EcsWorld should be created");
Assert.That(ecsWorld.EntityCount, Is.EqualTo(0), "Initial entity count should be 0");
} }
/// <summary>
/// 测试实体创建功能,验证能否成功创建带有指定组件的实体。
/// </summary>
[Test] [Test]
public void Test_02_CreateEntity_Should_Work() public void Test_02_CreateEntity_Should_Work()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity));
Assert.That(_ecsWorld.EntityCount, Is.EqualTo(1), "Entity count should be 1"); var entity = _world!.Create(new Position(0, 0), new Velocity(1, 1));
Assert.That(_ecsWorld.IsAlive(entity), Is.True, "Entity should be alive");
Assert.That(_world.Size, Is.EqualTo(1), "Entity count should be 1");
Assert.That(_world.IsAlive(entity), Is.True, "Entity should be alive");
} }
/// <summary>
/// 测试组件设置功能,验证能否正确存储和获取组件数据。
/// </summary>
[Test] [Test]
public void Test_03_SetComponent_Should_Store_Data() public void Test_03_SetComponent_Should_Store_Data()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
var entity = _ecsWorld.CreateEntity(typeof(Position));
var world = _ecsWorld.InternalWorld;
world.Set(entity, new Position(10, 20)); var entity = _world!.Create(new Position(10, 20));
Assert.That(world.Has<Position>(entity), Is.True, "Entity should have Position component"); Assert.That(_world.Has<Position>(entity), Is.True, "Entity should have Position component");
ref var pos = ref world.Get<Position>(entity); ref var pos = ref _world.Get<Position>(entity);
Assert.That(pos.X, Is.EqualTo(10), "Position.X should be 10"); Assert.That(pos.X, Is.EqualTo(10), "Position.X should be 10");
Assert.That(pos.Y, Is.EqualTo(20), "Position.Y should be 20"); Assert.That(pos.Y, Is.EqualTo(20), "Position.Y should be 20");
} }
/// <summary>
/// 测试移动系统功能,验证系统能否正确更新实体位置。
/// </summary>
[Test] [Test]
public void Test_04_MovementSystem_Should_Update_Position() public void Test_04_MovementSystem_Should_Update_Position()
{ {
InitializeEcsWithSystems(typeof(MovementSystem)); InitializeEcsModule();
var entity = _ecsWorld!.CreateEntity(typeof(Position), typeof(Velocity)); var entity = _world!.Create(new Position(0, 0), new Velocity(10, 5));
var world = _ecsWorld.InternalWorld; // 更新系统
world.Set(entity, new Position(0, 0)); _ecsModule!.Update(1.0f);
world.Set(entity, new Velocity(10, 5));
var systems = _container!.Get<IReadOnlyList<IEcsSystem>>(); ref var pos = ref _world.Get<Position>(entity);
Assert.That(systems, Is.Not.Null);
Assert.That(systems!.Count, Is.GreaterThan(0));
var movementSystem = systems.First(s => s is MovementSystem) as MovementSystem;
Assert.That(movementSystem, Is.Not.Null);
movementSystem!.Update(1.0f);
ref var pos = ref world.Get<Position>(entity);
Assert.That(pos.X, Is.EqualTo(10).Within(0.001f), "X position should be 10"); Assert.That(pos.X, Is.EqualTo(10).Within(0.001f), "X position should be 10");
Assert.That(pos.Y, Is.EqualTo(5).Within(0.001f), "Y position should be 5"); Assert.That(pos.Y, Is.EqualTo(5).Within(0.001f), "Y position should be 5");
} }
/// <summary>
/// 测试实体销毁功能,验证能否正确销毁实体并更新实体计数。
/// </summary>
[Test] [Test]
public void Test_05_DestroyEntity_Should_Work() public void Test_05_DestroyEntity_Should_Work()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
var entity = _ecsWorld.CreateEntity(typeof(Position));
_ecsWorld.DestroyEntity(entity); var entity = _world!.Create(new Position(0, 0));
_world.Destroy(entity);
Assert.That(_ecsWorld.EntityCount, Is.EqualTo(0), "Entity count should be 0"); Assert.That(_world.Size, Is.EqualTo(0), "Entity count should be 0");
Assert.That(_ecsWorld.IsAlive(entity), Is.False, "Entity should not be alive"); Assert.That(_world.IsAlive(entity), Is.False, "Entity should not be alive");
} }
/// <summary>
/// 测试世界清理功能,验证能否清除所有实体。
/// </summary>
[Test] [Test]
public void Test_06_ClearWorld_Should_Remove_All_Entities() public void Test_06_ClearWorld_Should_Remove_All_Entities()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
_ecsWorld.CreateEntity(typeof(Position)); _world!.Create(new Position(0, 0));
} }
_ecsWorld.Clear(); _world!.Clear();
Assert.That(_ecsWorld.EntityCount, Is.EqualTo(0), "Entity count should be 0 after clear"); Assert.That(_world.Size, Is.EqualTo(0), "Entity count should be 0 after clear");
} }
/// <summary>
/// 测试多个实体的批量更新功能,验证系统能否正确处理多个实体的更新。
/// </summary>
[Test] [Test]
public void Test_07_Multiple_Entities_Should_Update_Correctly() public void Test_07_Multiple_Entities_Should_Update_Correctly()
{ {
InitializeEcsWithSystems(typeof(MovementSystem)); InitializeEcsModule();
var world = _ecsWorld!.InternalWorld;
var entities = new Entity[10]; var entities = new Entity[10];
for (var i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
entities[i] = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); entities[i] = _world!.Create(new Position(0, 0), new Velocity(i, i * 2));
world.Set(entities[i], new Position(0, 0));
world.Set(entities[i], new Velocity(i, i * 2));
} }
var systems = _container!.Get<IReadOnlyList<IEcsSystem>>(); // 更新系统
var movementSystem = systems!.First(s => s is MovementSystem) as MovementSystem; _ecsModule!.Update(1.0f);
movementSystem!.Update(1.0f);
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
ref var pos = ref world.Get<Position>(entities[i]); ref var pos = ref _world!.Get<Position>(entities[i]);
Assert.That(pos.X, Is.EqualTo(i).Within(0.001f), $"Entity {i} X position should be {i}"); Assert.That(pos.X, Is.EqualTo(i).Within(0.001f), $"Entity {i} X position should be {i}");
Assert.That(pos.Y, Is.EqualTo(i * 2).Within(0.001f), $"Entity {i} Y position should be {i * 2}"); Assert.That(pos.Y, Is.EqualTo(i * 2).Within(0.001f), $"Entity {i} Y position should be {i * 2}");
} }
} }
/// <summary>
/// 测试未初始化情况下获取ECS世界的异常处理。
/// </summary>
[Test]
public void Test_08_GetEcsWorld_Without_Initialize_Should_Throw()
{
Assert.Throws<InvalidOperationException>(() => { _context!.GetEcsWorld(); },
"ECS World not initialized. Enable ECS in configuration.");
}
} }

View File

@ -1,358 +1,205 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Arch.Core; using Arch.Core;
using GFramework.Core.Abstractions.ecs; using GFramework.Core.Abstractions.rule;
using GFramework.Core.architecture; using GFramework.Core.architecture;
using GFramework.Core.ecs; using GFramework.Core.ecs;
using GFramework.Core.ecs.components; using GFramework.Core.ecs.components;
using GFramework.Core.ecs.systems; using GFramework.Core.ecs.systems;
using GFramework.Core.ioc; using GFramework.Core.ioc;
using GFramework.Core.logging;
using NUnit.Framework; using NUnit.Framework;
namespace GFramework.Core.Tests.ecs; namespace GFramework.Core.Tests.ecs;
/// <summary> /// <summary>
/// ECS集成测试类用于验证ECS系统的整体功能和性能表现。 /// ECS 集成测试类 - 使用 Arch 原生 API
/// 包括实体管理、组件操作、系统调度、优先级控制以及性能基准测试。
/// </summary> /// </summary>
[TestFixture] [TestFixture]
[Experimental("GFrameworkECS")] [Experimental("GFrameworkECS")]
public class EcsIntegrationTests public class EcsIntegrationTests
{ {
/// <summary>
/// 测试初始化方法,在每个测试方法执行前运行。
/// 负责初始化日志工厂、依赖注入容器和架构上下文。
/// </summary>
[SetUp] [SetUp]
public void Setup() public void Setup()
{ {
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
_container = new MicrosoftDiContainer(); _container = new MicrosoftDiContainer();
var loggerField = typeof(MicrosoftDiContainer).GetField("_logger",
BindingFlags.NonPublic | BindingFlags.Instance);
loggerField?.SetValue(_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(EcsIntegrationTests)));
_context = new ArchitectureContext(_container); _context = new ArchitectureContext(_container);
} }
/// <summary>
/// 测试清理方法,在每个测试方法执行后运行。
/// 负责释放ECS世界资源并清空容器和上下文。
/// </summary>
[TearDown] [TearDown]
public void TearDown() public void TearDown()
{ {
_ecsWorld?.Dispose(); if (_world != null)
_ecsWorld = null; {
World.Destroy(_world);
_world = null;
}
_container?.Clear(); _container?.Clear();
_context = null; _context = null;
_ecsModule = null;
} }
private MicrosoftDiContainer? _container; private MicrosoftDiContainer? _container;
private ArchitectureContext? _context; private ArchitectureContext? _context;
private EcsWorld? _ecsWorld; private World? _world;
private ArchEcsModule? _ecsModule;
/// <summary> /// <summary>
/// 初始化ECS系统并注册指定类型的系统实例。 /// 初始化 ECS 模块
/// </summary> /// </summary>
/// <param name="systemTypes">需要注册的系统类型数组</param> private void InitializeEcsModule()
private void InitializeEcsWithSystems(params Type[] systemTypes)
{ {
_ecsWorld = new EcsWorld(); // 注册系统
_container!.Register(_ecsWorld); var movementSystem = new MovementSystem();
_container.Register(_ecsWorld as IEcsWorld); ((IContextAware)movementSystem).SetContext(_context!);
_container!.RegisterPlurality(movementSystem);
var systems = new List<IEcsSystem>(); // 创建并注册 ArchEcsModule
foreach (var systemType in systemTypes) _ecsModule = new ArchEcsModule(enabled: true);
{ _ecsModule.Register(_container);
var system = (IEcsSystem)Activator.CreateInstance(systemType)!; _ecsModule.Initialize();
system.SetContext(_context!);
system.Initialize();
systems.Add(system);
_container.RegisterPlurality(system);
}
_container.Register(systems as IReadOnlyList<IEcsSystem>); // 获取 World
_world = _container.Get<World>();
} }
/// <summary>
/// 测试ECS初始化功能验证是否能正确创建EcsWorld实例。
/// </summary>
[Test] [Test]
public void InitializeEcs_Should_Create_EcsWorld() public void InitializeEcs_Should_Create_World()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
_container!.Register(_ecsWorld);
_container.Register<IEcsWorld>(_ecsWorld);
var ecsWorld = _context!.GetEcsWorld(); Assert.That(_world, Is.Not.Null);
Assert.That(_world!.Size, Is.EqualTo(0));
Assert.That(ecsWorld, Is.Not.Null);
Assert.That(ecsWorld.EntityCount, Is.EqualTo(0));
} }
/// <summary>
/// 测试实体创建功能,验证创建实体后实体计数是否正确增加。
/// </summary>
[Test] [Test]
public void CreateEntity_Should_Increase_EntityCount() public void CreateEntity_Should_Increase_EntityCount()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity));
Assert.That(_ecsWorld.EntityCount, Is.EqualTo(1)); var entity = _world!.Create(new Position(0, 0), new Velocity(1, 1));
Assert.That(_ecsWorld.IsAlive(entity), Is.True);
Assert.That(_world.Size, Is.EqualTo(1));
Assert.That(_world.IsAlive(entity), Is.True);
} }
/// <summary>
/// 测试实体销毁功能,验证销毁实体后实体计数是否正确减少。
/// </summary>
[Test] [Test]
public void DestroyEntity_Should_Decrease_EntityCount() public void DestroyEntity_Should_Decrease_EntityCount()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
var entity = _ecsWorld.CreateEntity(typeof(Position));
_ecsWorld.DestroyEntity(entity); var entity = _world!.Create(new Position(0, 0));
_world.Destroy(entity);
Assert.That(_ecsWorld.EntityCount, Is.EqualTo(0)); Assert.That(_world.Size, Is.EqualTo(0));
Assert.That(_ecsWorld.IsAlive(entity), Is.False); Assert.That(_world.IsAlive(entity), Is.False);
} }
/// <summary>
/// 测试组件设置功能,验证能否正确存储和获取组件数据。
/// </summary>
[Test] [Test]
public void SetComponent_Should_Store_ComponentData() public void SetComponent_Should_Store_ComponentData()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
var entity = _ecsWorld.CreateEntity(typeof(Position));
var world = _ecsWorld.InternalWorld; var entity = _world!.Create(new Position(10, 20));
world.Set(entity, new Position(10, 20));
Assert.That(world.Has<Position>(entity), Is.True); Assert.That(_world.Has<Position>(entity), Is.True);
ref var pos = ref world.Get<Position>(entity); ref var pos = ref _world.Get<Position>(entity);
Assert.That(pos.X, Is.EqualTo(10)); Assert.That(pos.X, Is.EqualTo(10));
Assert.That(pos.Y, Is.EqualTo(20)); Assert.That(pos.Y, Is.EqualTo(20));
} }
/// <summary>
/// 测试世界清理功能,验证能否清除所有实体。
/// </summary>
[Test] [Test]
public void ClearWorld_Should_Remove_All_Entities() public void ClearWorld_Should_Remove_All_Entities()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
_ecsWorld.CreateEntity(typeof(Position)); _world!.Create(new Position(0, 0));
} }
_ecsWorld.Clear(); _world!.Clear();
Assert.That(_ecsWorld.EntityCount, Is.EqualTo(0)); Assert.That(_world.Size, Is.EqualTo(0));
} }
/// <summary>
/// 测试ECS系统注册功能验证系统能否正确添加到运行器中。
/// </summary>
[Test] [Test]
public void RegisterEcsSystem_Should_Add_System_To_Runner() public void RegisterEcsSystem_Should_Add_System_To_Module()
{ {
InitializeEcsWithSystems(typeof(MovementSystem)); InitializeEcsModule();
var systems = _container!.Get<IReadOnlyList<IEcsSystem>>(); var adapters = _container!.GetAll<ArchSystemAdapter<float>>();
Assert.That(systems, Is.Not.Null); Assert.That(adapters, Is.Not.Null);
Assert.That(systems!.Count, Is.EqualTo(1)); Assert.That(adapters.Count, Is.EqualTo(1));
Assert.That(systems[0], Is.InstanceOf<MovementSystem>()); Assert.That(adapters[0], Is.InstanceOf<MovementSystem>());
} }
/// <summary>
/// 测试移动系统功能,验证系统能否正确更新单个实体的位置。
/// </summary>
[Test] [Test]
public void MovementSystem_Should_Update_Position() public void MovementSystem_Should_Update_Position()
{ {
InitializeEcsWithSystems(typeof(MovementSystem)); InitializeEcsModule();
var entity = _ecsWorld!.CreateEntity(typeof(Position), typeof(Velocity)); var entity = _world!.Create(new Position(0, 0), new Velocity(10, 5));
var world = _ecsWorld.InternalWorld; _ecsModule!.Update(1.0f);
world.Set(entity, new Position(0, 0));
world.Set(entity, new Velocity(10, 5));
var systems = _container!.Get<IReadOnlyList<IEcsSystem>>(); ref var pos = ref _world.Get<Position>(entity);
var movementSystem = systems!.First(s => s is MovementSystem) as MovementSystem;
movementSystem!.Update(1.0f);
ref var pos = ref world.Get<Position>(entity);
Assert.That(pos.X, Is.EqualTo(10).Within(0.001f)); Assert.That(pos.X, Is.EqualTo(10).Within(0.001f));
Assert.That(pos.Y, Is.EqualTo(5).Within(0.001f)); Assert.That(pos.Y, Is.EqualTo(5).Within(0.001f));
} }
/// <summary>
/// 测试移动系统功能,验证系统能否正确批量更新多个实体的位置。
/// </summary>
[Test] [Test]
public void MovementSystem_Should_Update_Multiple_Entities() public void MovementSystem_Should_Update_Multiple_Entities()
{ {
InitializeEcsWithSystems(typeof(MovementSystem)); InitializeEcsModule();
var world = _ecsWorld!.InternalWorld;
var entities = new Entity[100]; var entities = new Entity[100];
for (var i = 0; i < 100; i++) for (var i = 0; i < 100; i++)
{ {
entities[i] = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); entities[i] = _world!.Create(new Position(0, 0), new Velocity(i, i * 2));
world.Set(entities[i], new Position(0, 0));
world.Set(entities[i], new Velocity(i, i * 2));
} }
var systems = _container!.Get<IReadOnlyList<IEcsSystem>>(); _ecsModule!.Update(0.5f);
var movementSystem = systems!.First(s => s is MovementSystem) as MovementSystem;
movementSystem!.Update(0.5f);
for (var i = 0; i < 100; i++) for (var i = 0; i < 100; i++)
{ {
ref var pos = ref world.Get<Position>(entities[i]); ref var pos = ref _world!.Get<Position>(entities[i]);
Assert.That(pos.X, Is.EqualTo(i * 0.5f).Within(0.001f)); Assert.That(pos.X, Is.EqualTo(i * 0.5f).Within(0.001f));
Assert.That(pos.Y, Is.EqualTo(i * 2 * 0.5f).Within(0.001f)); Assert.That(pos.Y, Is.EqualTo(i * 2 * 0.5f).Within(0.001f));
} }
} }
/// <summary>
/// 测试ECS系统运行器的优先级调度功能验证系统是否按优先级顺序执行。
/// </summary>
[Test]
public void EcsSystemRunner_Should_Respect_Priority()
{
InitializeEcsWithSystems(typeof(LowPrioritySystem), typeof(HighPrioritySystem));
var systems = _container!.Get<IReadOnlyList<IEcsSystem>>();
Assert.That(systems, Is.Not.Null);
Assert.That(systems!.Count, Is.EqualTo(2));
var sortedSystems = systems.OrderBy(s => s.Priority).ToList();
Assert.That(sortedSystems[0], Is.InstanceOf<HighPrioritySystem>());
Assert.That(sortedSystems[1], Is.InstanceOf<LowPrioritySystem>());
}
/// <summary>
/// 测试未初始化情况下获取ECS世界的异常处理。
/// </summary>
[Test]
public void GetEcsWorld_Without_Initialize_Should_Throw()
{
Assert.Throws<InvalidOperationException>(() => { _context!.GetEcsWorld(); },
"ECS World not initialized. Enable ECS in configuration.");
}
/// <summary>
/// 性能基准测试验证更新10000个实体的性能表现。
/// </summary>
[Test] [Test]
public void Performance_Test_10000_Entities() public void Performance_Test_10000_Entities()
{ {
InitializeEcsWithSystems(typeof(MovementSystem)); InitializeEcsModule();
var world = _ecsWorld!.InternalWorld;
for (int i = 0; i < 10000; i++) for (int i = 0; i < 10000; i++)
{ {
var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); _world!.Create(new Position(0, 0), new Velocity(1, 1));
world.Set(entity, new Position(0, 0));
world.Set(entity, new Velocity(1, 1));
} }
var systems = _container!.Get<IReadOnlyList<IEcsSystem>>();
var movementSystem = systems!.First(s => s is MovementSystem) as MovementSystem;
var startTime = DateTime.UtcNow; var startTime = DateTime.UtcNow;
movementSystem!.Update(0.016f); _ecsModule!.Update(0.016f);
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
Assert.That(_ecsWorld.EntityCount, Is.EqualTo(10000)); Assert.That(_world!.Size, Is.EqualTo(10000));
Assert.That(elapsed, Is.LessThan(100), $"Updating 10000 entities took: {elapsed}ms"); Assert.That(elapsed, Is.LessThan(100), $"Updating 10000 entities took: {elapsed}ms");
} }
/// <summary>
/// 性能基准测试验证创建1000个实体的性能表现。
/// </summary>
[Test] [Test]
public void Performance_Test_1000_Entities_Creation() public void Performance_Test_1000_Entities_Creation()
{ {
_ecsWorld = new EcsWorld(); InitializeEcsModule();
var world = _ecsWorld.InternalWorld;
var startTime = DateTime.UtcNow; var startTime = DateTime.UtcNow;
for (int i = 0; i < 1000; i++) for (int i = 0; i < 1000; i++)
{ {
var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); _world!.Create(new Position(0, 0), new Velocity(1, 1));
world.Set(entity, new Position(0, 0));
world.Set(entity, new Velocity(1, 1));
} }
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
Assert.That(_ecsWorld.EntityCount, Is.EqualTo(1000)); Assert.That(_world!.Size, Is.EqualTo(1000));
Assert.That(elapsed, Is.LessThan(50), $"Creating 1000 entities took: {elapsed}ms"); Assert.That(elapsed, Is.LessThan(50), $"Creating 1000 entities took: {elapsed}ms");
} }
}
/// <summary>
/// 高优先级系统示例,用于测试系统调度优先级功能。
/// </summary>
public class HighPrioritySystem : EcsSystemBase
{
/// <summary>
/// 获取系统优先级,数值越小优先级越高。
/// </summary>
public override int Priority => -100;
/// <summary>
/// ECS初始化回调方法。
/// </summary>
protected override void OnEcsInit()
{
}
/// <summary>
/// 系统更新方法。
/// </summary>
/// <param name="deltaTime">帧间隔时间</param>
public override void Update(float deltaTime)
{
}
}
/// <summary>
/// 低优先级系统示例,用于测试系统调度优先级功能。
/// </summary>
public class LowPrioritySystem : EcsSystemBase
{
/// <summary>
/// 获取系统优先级,数值越大优先级越低。
/// </summary>
public override int Priority => 100;
/// <summary>
/// ECS初始化回调方法。
/// </summary>
protected override void OnEcsInit()
{
}
/// <summary>
/// 系统更新方法。
/// </summary>
/// <param name="deltaTime">帧间隔时间</param>
public override void Update(float deltaTime)
{
}
} }

View File

@ -4,7 +4,7 @@ using GFramework.Core.ecs.components;
namespace GFramework.Core.ecs.systems; namespace GFramework.Core.ecs.systems;
/// <summary> /// <summary>
/// 移动系统 - Arch 原生实现 /// 移动系统 - 继承 ArchSystemAdapter
/// 负责更新具有位置和速度组件的实体的位置 /// 负责更新具有位置和速度组件的实体的位置
/// </summary> /// </summary>
public sealed class MovementSystem : ArchSystemAdapter<float> public sealed class MovementSystem : ArchSystemAdapter<float>
@ -12,28 +12,27 @@ public sealed class MovementSystem : ArchSystemAdapter<float>
private QueryDescription _query; private QueryDescription _query;
/// <summary> /// <summary>
/// 初始化系统 /// Arch 系统初始化
/// </summary> /// </summary>
public void Initialize(World world) protected override void OnArchInitialize()
{ {
// 创建查询查找所有同时拥有Position和Velocity组件的实体 // 创建查询查找所有同时拥有Position和Velocity组件的实体
_query = new QueryDescription() _query = new QueryDescription()
.WithAll<Position, Velocity>(); .WithAll<Position, Velocity>();
} }
/// <summary> /// <summary>
/// 系统更新方法,每帧调用一次 /// 系统更新方法,每帧调用一次
/// </summary> /// </summary>
/// <param name="world">ECS 世界</param>
/// <param name="deltaTime">帧间隔时间</param> /// <param name="deltaTime">帧间隔时间</param>
public void Update(World world, float deltaTime) protected override void OnUpdate(in float deltaTime)
{ {
// 查询并更新所有符合条件的实体 // 查询并更新所有符合条件的实体
world.Query(in _query, (ref Position pos, ref Velocity vel) => var f = deltaTime;
World.Query(in _query, (ref Position pos, ref Velocity vel) =>
{ {
pos.X += vel.X * deltaTime; pos.X += vel.X * f;
pos.Y += vel.Y * deltaTime; pos.Y += vel.Y * f;
}); });
} }
} }