feat(architecture): 集成Arch ECS框架并升级目标框架

- 在ArchitectureContext中添加ECS世界和系统调度器支持
- 添加IEcsWorld和IEcsSystem接口定义
- 实现EcsWorld、EcsSystemBase和EcsSystemRunner核心类
- 添加Position和Velocity示例组件及MovementSystem示例
- 创建ECS使用示例代码
- 将多个项目的TargetFramework从netstandard2.0升级到netstandard2.1
- 添加Arch和Arch.System包依赖到核心项目
- 在测试项目中添加ECS相关接口的模拟实现
This commit is contained in:
GeWuYou 2026-02-23 08:51:48 +08:00 committed by gewuyou
parent d653994ded
commit daff1fa12b
27 changed files with 501 additions and 14 deletions

View File

@ -1,7 +1,7 @@
<Project>
<Project>
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!--

View File

@ -17,7 +17,7 @@
<Using Include="GFramework.Core.Abstractions"/>
</ItemGroup>
<ItemGroup>
<PackageReference Update="Meziantou.Analyzer" Version="3.0.9">
<PackageReference Update="Meziantou.Analyzer" Version="3.0.12">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
@ -25,6 +25,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Arch" Version="2.1.0"/>
<PackageReference Include="Mediator.Abstractions" Version="3.0.1"/>
</ItemGroup>
</Project>

View File

@ -1,4 +1,5 @@
using GFramework.Core.Abstractions.command;
using GFramework.Core.Abstractions.ecs;
using GFramework.Core.Abstractions.environment;
using GFramework.Core.Abstractions.events;
using GFramework.Core.Abstractions.model;
@ -205,4 +206,18 @@ public interface IArchitectureContext
/// </summary>
/// <returns>环境对象实例</returns>
IEnvironment GetEnvironment();
// === ECS 支持 ===
/// <summary>
/// 获取ECS世界实例
/// </summary>
/// <returns>ECS世界实例</returns>
IEcsWorld GetEcsWorld();
/// <summary>
/// 注册ECS系统
/// </summary>
/// <typeparam name="T">ECS系统类型</typeparam>
void RegisterEcsSystem<T>() where T : class, IEcsSystem;
}

View File

@ -0,0 +1,20 @@
using GFramework.Core.Abstractions.system;
namespace GFramework.Core.Abstractions.ecs;
/// <summary>
/// ECS系统接口继承自ISystem以集成到现有架构
/// </summary>
public interface IEcsSystem : ISystem
{
/// <summary>
/// 系统优先级,数值越小越先执行
/// </summary>
int Priority { get; }
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="deltaTime">帧间隔时间(秒)</param>
void Update(float deltaTime);
}

View File

@ -0,0 +1,44 @@
using Arch.Core;
namespace GFramework.Core.Abstractions.ecs;
/// <summary>
/// ECS世界接口封装Arch的World实例
/// </summary>
public interface IEcsWorld : IDisposable
{
/// <summary>
/// 当前实体数量
/// </summary>
int EntityCount { get; }
/// <summary>
/// 获取内部的Arch World实例用于高级操作
/// </summary>
World InternalWorld { get; }
/// <summary>
/// 创建一个新实体
/// </summary>
/// <param name="types">组件类型数组</param>
/// <returns>创建的实体</returns>
Entity CreateEntity(params ComponentType[] types);
/// <summary>
/// 销毁指定实体
/// </summary>
/// <param name="entity">要销毁的实体</param>
void DestroyEntity(Entity entity);
/// <summary>
/// 检查实体是否存活
/// </summary>
/// <param name="entity">要检查的实体</param>
/// <returns>实体是否存活</returns>
bool IsAlive(Entity entity);
/// <summary>
/// 清空所有实体
/// </summary>
void Clear();
}

View File

@ -1,5 +1,6 @@
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.Abstractions.command;
using GFramework.Core.Abstractions.ecs;
using GFramework.Core.Abstractions.environment;
using GFramework.Core.Abstractions.events;
using GFramework.Core.Abstractions.ioc;
@ -356,6 +357,16 @@ public class TestArchitectureContextV3 : IArchitectureContext
{
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

View File

@ -1,5 +1,6 @@
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.Abstractions.command;
using GFramework.Core.Abstractions.ecs;
using GFramework.Core.Abstractions.environment;
using GFramework.Core.Abstractions.events;
using GFramework.Core.Abstractions.ioc;
@ -451,4 +452,14 @@ public class TestArchitectureContext : IArchitectureContext
{
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

@ -14,5 +14,7 @@
<PackageReference Include="LanguageExt.Core" Version="4.4.9"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3"/>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0"/>
<PackageReference Include="Arch" Version="2.1.0"/>
<PackageReference Include="Arch.System" Version="1.1.0"/>
</ItemGroup>
</Project>

View File

@ -1,5 +1,6 @@
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.Abstractions.command;
using GFramework.Core.Abstractions.ecs;
using GFramework.Core.Abstractions.environment;
using GFramework.Core.Abstractions.events;
using GFramework.Core.Abstractions.ioc;
@ -7,6 +8,7 @@ using GFramework.Core.Abstractions.model;
using GFramework.Core.Abstractions.query;
using GFramework.Core.Abstractions.system;
using GFramework.Core.Abstractions.utility;
using GFramework.Core.ecs;
using Mediator;
using ICommand = GFramework.Core.Abstractions.command.ICommand;
@ -19,6 +21,8 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
{
private readonly IIocContainer _container = container ?? throw new ArgumentNullException(nameof(container));
private readonly Dictionary<Type, object> _serviceCache = new();
private EcsSystemRunner? _ecsRunner;
private EcsWorld? _ecsWorld;
#region Mediator Integration ()
@ -412,4 +416,59 @@ public class ArchitectureContext(IIocContainer container) : IArchitectureContext
}
#endregion
#region ECS Support
/// <summary>
/// 获取ECS世界实例
/// </summary>
/// <returns>ECS世界实例</returns>
public IEcsWorld GetEcsWorld()
{
return _ecsWorld ??
throw new InvalidOperationException("ECS World not initialized. Call InitializeEcs() first.");
}
/// <summary>
/// 注册ECS系统
/// </summary>
/// <typeparam name="T">ECS系统类型</typeparam>
public void RegisterEcsSystem<T>() where T : class, IEcsSystem
{
// 使用RegisterPlurality注册到所有接口
_container.RegisterPlurality<T>();
}
/// <summary>
/// 初始化ECS在架构初始化时调用
/// </summary>
public void InitializeEcs()
{
if (_ecsWorld != null) return;
// 创建ECS世界
_ecsWorld = new EcsWorld();
_container.Register(_ecsWorld);
// 注册系统调度器
_container.RegisterPlurality<EcsSystemRunner>();
_ecsRunner = _container.Get<EcsSystemRunner>();
}
/// <summary>
/// 获取ECS系统调度器
/// </summary>
internal EcsSystemRunner? GetEcsRunner() => _ecsRunner;
/// <summary>
/// 销毁ECS资源
/// </summary>
private void DisposeEcs()
{
_ecsWorld?.Dispose();
_ecsWorld = null;
_ecsRunner = null;
}
#endregion
}

View File

@ -0,0 +1,62 @@
using Arch.Core;
using GFramework.Core.Abstractions.ecs;
using GFramework.Core.system;
namespace GFramework.Core.ecs;
/// <summary>
/// ECS系统基类继承自AbstractSystem以集成到现有架构
/// </summary>
public abstract class EcsSystemBase : AbstractSystem, IEcsSystem
{
/// <summary>
/// ECS世界实例
/// </summary>
protected EcsWorld EcsWorld { get; private set; } = null!;
/// <summary>
/// 快捷访问内部World
/// </summary>
protected World World => EcsWorld.InternalWorld;
/// <summary>
/// 系统优先级默认为0
/// </summary>
public virtual int Priority => 0;
/// <summary>
/// 每帧更新(子类实现)
/// </summary>
public abstract void Update(float deltaTime);
/// <summary>
/// 系统初始化
/// </summary>
protected override void OnInit()
{
EcsWorld = Context.GetService<EcsWorld>() ?? throw new InvalidOperationException(
"EcsWorld not found in context. Make sure ECS is properly initialized.");
OnEcsInit();
}
/// <summary>
/// 系统销毁
/// </summary>
protected override void OnDestroy()
{
OnEcsDestroy();
}
/// <summary>
/// ECS系统初始化子类实现
/// </summary>
protected abstract void OnEcsInit();
/// <summary>
/// ECS系统销毁子类可选实现
/// </summary>
protected virtual void OnEcsDestroy()
{
}
}

View File

@ -0,0 +1,66 @@
using GFramework.Core.Abstractions.ecs;
using GFramework.Core.system;
namespace GFramework.Core.ecs;
/// <summary>
/// ECS系统调度器负责管理和更新所有ECS系统
/// </summary>
public sealed class EcsSystemRunner : AbstractSystem
{
private readonly List<IEcsSystem> _systems = new();
private bool _isRunning;
/// <summary>
/// 初始化调度器从DI容器获取所有ECS系统
/// </summary>
protected override void OnInit()
{
// 从容器获取所有已注册的ECS系统
var systemsList = Context.GetService<IReadOnlyList<IEcsSystem>>();
if (systemsList != null && systemsList.Count > 0)
{
// 按优先级排序
_systems.AddRange(systemsList.OrderBy(s => s.Priority));
}
}
/// <summary>
/// 更新所有ECS系统
/// </summary>
/// <param name="deltaTime">帧间隔时间</param>
public void Update(float deltaTime)
{
if (!_isRunning) return;
foreach (var system in _systems)
{
system.Update(deltaTime);
}
}
/// <summary>
/// 启动调度器
/// </summary>
public void Start()
{
_isRunning = true;
}
/// <summary>
/// 停止调度器
/// </summary>
public void Stop()
{
_isRunning = false;
}
/// <summary>
/// 销毁调度器
/// </summary>
protected override void OnDestroy()
{
Stop();
_systems.Clear();
}
}

View File

@ -0,0 +1,66 @@
using Arch.Core;
using GFramework.Core.Abstractions.ecs;
namespace GFramework.Core.ecs;
/// <summary>
/// ECS世界实现封装Arch的World实例
/// </summary>
public sealed class EcsWorld : IEcsWorld
{
private readonly World _world = World.Create();
private bool _disposed;
/// <summary>
/// 获取内部的Arch World实例
/// </summary>
public World InternalWorld => _world;
/// <summary>
/// 当前实体数量
/// </summary>
public int EntityCount => _world.Size;
/// <summary>
/// 创建一个新实体
/// </summary>
public Entity CreateEntity(params ComponentType[] types)
{
return _world.Create(types);
}
/// <summary>
/// 销毁指定实体
/// </summary>
public void DestroyEntity(Entity entity)
{
_world.Destroy(entity);
}
/// <summary>
/// 检查实体是否存活
/// </summary>
public bool IsAlive(Entity entity)
{
return _world.IsAlive(entity);
}
/// <summary>
/// 清空所有实体
/// </summary>
public void Clear()
{
_world.Clear();
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
if (_disposed) return;
World.Destroy(_world);
_disposed = true;
}
}

View File

@ -0,0 +1,16 @@
namespace GFramework.Core.ecs.components;
/// <summary>
/// 位置组件
/// </summary>
public struct Position
{
public float X;
public float Y;
public Position(float x, float y)
{
X = x;
Y = y;
}
}

View File

@ -0,0 +1,16 @@
namespace GFramework.Core.ecs.components;
/// <summary>
/// 速度组件
/// </summary>
public struct Velocity
{
public float X;
public float Y;
public Velocity(float x, float y)
{
X = x;
Y = y;
}
}

View File

@ -0,0 +1,67 @@
using Arch.Core;
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.ecs.components;
namespace GFramework.Core.ecs.examples;
/// <summary>
/// ECS使用示例 - 演示如何创建和管理实体
/// </summary>
public static class EcsUsageExample
{
/// <summary>
/// 示例1: 创建移动的敌人实体
/// </summary>
public static void CreateMovingEnemies(IArchitectureContext context, int count)
{
var ecsWorld = context.GetEcsWorld();
var world = ecsWorld.InternalWorld;
for (int i = 0; i < count; i++)
{
// 创建实体
var entity = ecsWorld.CreateEntity(
new ComponentType[]
{
typeof(Position),
typeof(Velocity)
}
);
// 设置初始位置和速度
world.Set(entity, new Position(i * 10, 0));
var random = new Random();
world.Set(entity, new Velocity(
(float)(random.NextDouble() * 100 - 50),
(float)(random.NextDouble() * 100 - 50)
));
}
}
/// <summary>
/// 示例2: 批量清理实体
/// </summary>
public static void ClearAllEntities(IArchitectureContext context)
{
var ecsWorld = context.GetEcsWorld();
ecsWorld.Clear();
}
/// <summary>
/// 示例3: 查询特定实体
/// </summary>
public static int CountMovingEntities(IArchitectureContext context)
{
var ecsWorld = context.GetEcsWorld();
var world = ecsWorld.InternalWorld;
int count = 0;
var query = new QueryDescription()
.WithAll<Position, Velocity>();
world.Query(in query, (Entity entity) => { count++; });
return count;
}
}

View File

@ -0,0 +1,31 @@
using Arch.Core;
using GFramework.Core.ecs.components;
namespace GFramework.Core.ecs.systems;
/// <summary>
/// 移动系统示例 - 根据速度更新位置
/// </summary>
public class MovementSystem : EcsSystemBase
{
private QueryDescription _query;
public override int Priority => 0;
protected override void OnEcsInit()
{
// 创建查询查找所有同时拥有Position和Velocity组件的实体
_query = new QueryDescription()
.WithAll<Position, Velocity>();
}
public override void Update(float deltaTime)
{
// 查询并更新所有符合条件的实体
World.Query(in _query, (ref Position pos, ref Velocity vel) =>
{
pos.X += vel.X * deltaTime;
pos.Y += vel.Y * deltaTime;
});
}
}

View File

@ -1,7 +1,7 @@
<Project>
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!--

View File

@ -19,7 +19,7 @@
<Using Include="GFramework.Game.Abstractions"/>
</ItemGroup>
<ItemGroup>
<PackageReference Update="Meziantou.Analyzer" Version="3.0.9">
<PackageReference Update="Meziantou.Analyzer" Version="3.0.12">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

View File

@ -2,7 +2,7 @@
// This type is required to support init-only setters and record types
// when targeting netstandard2.0 or older frameworks.
#if NETSTANDARD2_0 || NETFRAMEWORK || NETCOREAPP2_0
#if NETSTANDARD2_1 || NETFRAMEWORK || NETCOREAPP2_1
using System.ComponentModel;
namespace System.Runtime.CompilerServices;

View File

@ -1,7 +1,7 @@
<Project>
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!--

View File

@ -19,7 +19,7 @@
<Folder Include="logging\"/>
</ItemGroup>
<ItemGroup>
<PackageReference Update="Meziantou.Analyzer" Version="3.0.9">
<PackageReference Update="Meziantou.Analyzer" Version="3.0.12">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<!-- 这是 Analyzer不是运行时库 -->
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>

View File

@ -1,7 +1,7 @@
<Project>
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!--

View File

@ -18,7 +18,7 @@
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
<PackageReference Update="Meziantou.Analyzer" Version="3.0.9">
<PackageReference Update="Meziantou.Analyzer" Version="3.0.12">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

View File

@ -1,7 +1,7 @@
<Project>
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!--

View File

@ -22,7 +22,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all"/>
<PackageReference Update="Meziantou.Analyzer" Version="3.0.9">
<PackageReference Update="Meziantou.Analyzer" Version="3.0.12">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<!-- 这是 Analyzer不是运行时库 -->
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>