diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 628753b..8ec0bef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,6 +125,15 @@ jobs: --no-build \ --logger "trx;LogFileName=sg-$RANDOM.trx" \ --results-directory TestResults + + - name: Test - GFramework.Ecs.Arch.Tests + run: | + dotnet test GFramework.Ecs.Arch.Tests \ + -c Release \ + --no-build \ + --logger "trx;LogFileName=ecs-arch-$RANDOM.trx" \ + --results-directory TestResults + - name: Generate CTRF report run: | mkdir -p ctrf diff --git a/GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs b/GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs new file mode 100644 index 0000000..ab5a7c3 --- /dev/null +++ b/GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs @@ -0,0 +1,42 @@ +using System.Collections.Concurrent; + +namespace GFramework.Core.Abstractions.architecture; + +/// +/// 架构模块注册表 - 用于外部模块的自动注册 +/// +public static class ArchitectureModuleRegistry +{ + private static readonly ConcurrentDictionary> _factories = new(); + + /// + /// 注册模块工厂(幂等操作,相同模块名只会注册一次) + /// + /// 模块工厂函数 + public static void Register(Func factory) + { + // 创建临时实例以获取模块名(用于幂等性检查) + var tempModule = factory(); + var moduleName = tempModule.ModuleName; + + // 幂等注册:相同模块名只注册一次 + _factories.TryAdd(moduleName, factory); + } + + /// + /// 创建所有已注册的模块实例 + /// + /// 模块实例集合 + public static IEnumerable CreateModules() + { + return _factories.Values.Select(f => f()); + } + + /// + /// 清空注册表(主要用于测试) + /// + public static void Clear() + { + _factories.Clear(); + } +} \ No newline at end of file diff --git a/GFramework.Core.Abstractions/architecture/IServiceModuleManager.cs b/GFramework.Core.Abstractions/architecture/IServiceModuleManager.cs index 20b7d52..8c7face 100644 --- a/GFramework.Core.Abstractions/architecture/IServiceModuleManager.cs +++ b/GFramework.Core.Abstractions/architecture/IServiceModuleManager.cs @@ -1,5 +1,4 @@ using GFramework.Core.Abstractions.ioc; -using GFramework.Core.Abstractions.properties; namespace GFramework.Core.Abstractions.architecture; @@ -18,8 +17,7 @@ public interface IServiceModuleManager /// 注册内置的服务模块。 /// /// IoC容器实例,用于解析依赖。 - /// 架构属性配置,用于模块初始化。 - void RegisterBuiltInModules(IIocContainer container, ArchitectureProperties properties); + void RegisterBuiltInModules(IIocContainer container); /// /// 获取所有已注册的服务模块。 diff --git a/GFramework.Core.Abstractions/properties/ArchitectureProperties.cs b/GFramework.Core.Abstractions/properties/ArchitectureProperties.cs index 1915685..f1a5eca 100644 --- a/GFramework.Core.Abstractions/properties/ArchitectureProperties.cs +++ b/GFramework.Core.Abstractions/properties/ArchitectureProperties.cs @@ -17,10 +17,4 @@ public sealed class ArchitectureProperties /// 默认值为 false,表示不启用严格验证。 /// public bool StrictPhaseValidation { get; set; } - - /// - /// 启用 ECS(Entity Component System)功能的开关。 - /// 当设置为 true 时,架构将启用 ECS 相关功能。 - /// - public bool EnableEcs { get; set; } } \ No newline at end of file diff --git a/GFramework.Core.Tests/GFramework.Core.Tests.csproj b/GFramework.Core.Tests/GFramework.Core.Tests.csproj index eed0620..f394be1 100644 --- a/GFramework.Core.Tests/GFramework.Core.Tests.csproj +++ b/GFramework.Core.Tests/GFramework.Core.Tests.csproj @@ -1,9 +1,11 @@  + net8.0;net10.0 disable enable - net10.0;net8.0 + false + true diff --git a/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs b/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs index efe5aad..5621b33 100644 --- a/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs +++ b/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs @@ -4,7 +4,6 @@ using GFramework.Core.Abstractions.environment; using GFramework.Core.Abstractions.events; using GFramework.Core.Abstractions.ioc; using GFramework.Core.Abstractions.model; -using GFramework.Core.Abstractions.properties; using GFramework.Core.Abstractions.query; using GFramework.Core.Abstractions.system; using GFramework.Core.Abstractions.utility; @@ -15,7 +14,6 @@ using GFramework.Core.events; using GFramework.Core.ioc; using GFramework.Core.query; using Mediator; -using NUnit.Framework; using ICommand = GFramework.Core.Abstractions.command.ICommand; namespace GFramework.Core.Tests.architecture; @@ -48,8 +46,7 @@ public class ArchitectureServicesTests private void RegisterBuiltInServices() { - var properties = new ArchitectureProperties(); - _services!.ModuleManager.RegisterBuiltInModules(_services.Container, properties); + _services!.ModuleManager.RegisterBuiltInModules(_services.Container); } /// @@ -216,13 +213,11 @@ public class ArchitectureServicesTests [Test] public void Multiple_Instances_Should_Have_Independent_EventBus() { - var properties = new ArchitectureProperties(); - var services1 = new ArchitectureServices(); - services1.ModuleManager.RegisterBuiltInModules(services1.Container, properties); + services1.ModuleManager.RegisterBuiltInModules(services1.Container); var services2 = new ArchitectureServices(); - services2.ModuleManager.RegisterBuiltInModules(services2.Container, properties); + services2.ModuleManager.RegisterBuiltInModules(services2.Container); Assert.That(services1.EventBus, Is.Not.SameAs(services2.EventBus)); } @@ -233,13 +228,11 @@ public class ArchitectureServicesTests [Test] public void Multiple_Instances_Should_Have_Independent_CommandBus() { - var properties = new ArchitectureProperties(); - var services1 = new ArchitectureServices(); - services1.ModuleManager.RegisterBuiltInModules(services1.Container, properties); + services1.ModuleManager.RegisterBuiltInModules(services1.Container); var services2 = new ArchitectureServices(); - services2.ModuleManager.RegisterBuiltInModules(services2.Container, properties); + services2.ModuleManager.RegisterBuiltInModules(services2.Container); Assert.That(services1.CommandExecutor, Is.Not.SameAs(services2.CommandExecutor)); } @@ -250,13 +243,11 @@ public class ArchitectureServicesTests [Test] public void Multiple_Instances_Should_Have_Independent_QueryBus() { - var properties = new ArchitectureProperties(); - var services1 = new ArchitectureServices(); - services1.ModuleManager.RegisterBuiltInModules(services1.Container, properties); + services1.ModuleManager.RegisterBuiltInModules(services1.Container); var services2 = new ArchitectureServices(); - services2.ModuleManager.RegisterBuiltInModules(services2.Container, properties); + services2.ModuleManager.RegisterBuiltInModules(services2.Container); Assert.That(services1.QueryExecutor, Is.Not.SameAs(services2.QueryExecutor)); } @@ -269,27 +260,6 @@ public class ArchitectureServicesTests { Assert.That(_services!.ModuleManager, Is.Not.Null); } - - /// - /// 测试EnableEcs配置开关 - /// - [Test] - public void EnableEcs_Should_Control_Ecs_Module_Registration() - { - var propertiesWithEcs = new ArchitectureProperties { EnableEcs = true }; - var propertiesWithoutEcs = new ArchitectureProperties { EnableEcs = false }; - - var servicesWithEcs = new ArchitectureServices(); - servicesWithEcs.ModuleManager.RegisterBuiltInModules(servicesWithEcs.Container, propertiesWithEcs); - - var servicesWithoutEcs = new ArchitectureServices(); - servicesWithoutEcs.ModuleManager.RegisterBuiltInModules(servicesWithoutEcs.Container, propertiesWithoutEcs); - - var modulesWithEcs = servicesWithEcs.ModuleManager.GetModules(); - var modulesWithoutEcs = servicesWithoutEcs.ModuleManager.GetModules(); - - Assert.That(modulesWithEcs.Count, Is.GreaterThan(modulesWithoutEcs.Count)); - } } #region Test Classes diff --git a/GFramework.Core/GFramework.Core.csproj b/GFramework.Core/GFramework.Core.csproj index 05baa84..41e4b38 100644 --- a/GFramework.Core/GFramework.Core.csproj +++ b/GFramework.Core/GFramework.Core.csproj @@ -13,7 +13,5 @@ - - diff --git a/GFramework.Core/architecture/Architecture.cs b/GFramework.Core/architecture/Architecture.cs index 717c17e..0ebdff6 100644 --- a/GFramework.Core/architecture/Architecture.cs +++ b/GFramework.Core/architecture/Architecture.cs @@ -641,8 +641,8 @@ public abstract class Architecture( _logger = LoggerFactoryResolver.Provider.CreateLogger(GetType().Name); Environment.Initialize(); - // 注册内置服务模块(根据配置) - Services.ModuleManager.RegisterBuiltInModules(Container, Configuration.ArchitectureProperties); + // 注册内置服务模块 + Services.ModuleManager.RegisterBuiltInModules(Container); // 将 Environment 注册到容器(如果尚未注册) if (!Container.Contains()) diff --git a/GFramework.Core/services/ServiceModuleManager.cs b/GFramework.Core/services/ServiceModuleManager.cs index 0f16073..9ed0aac 100644 --- a/GFramework.Core/services/ServiceModuleManager.cs +++ b/GFramework.Core/services/ServiceModuleManager.cs @@ -2,8 +2,6 @@ using GFramework.Core.Abstractions.architecture; using GFramework.Core.Abstractions.ioc; using GFramework.Core.Abstractions.lifecycle; using GFramework.Core.Abstractions.logging; -using GFramework.Core.Abstractions.properties; -using GFramework.Core.ecs; using GFramework.Core.logging; using GFramework.Core.services.modules; @@ -44,12 +42,11 @@ public sealed class ServiceModuleManager : IServiceModuleManager /// /// 注册内置服务模块,并根据优先级排序后完成服务注册。 - /// 内置模块包括事件总线、命令执行器、查询执行器等核心模块, - /// 并根据配置决定是否启用ECS模块。 + /// 内置模块包括事件总线、命令执行器、查询执行器等核心模块。 + /// 同时注册通过 ArchitectureModuleRegistry 自动注册的外部模块。 /// /// IoC容器实例,用于模块服务注册。 - /// 架构属性配置,用于判断是否启用ECS模块。 - public void RegisterBuiltInModules(IIocContainer container, ArchitectureProperties properties) + public void RegisterBuiltInModules(IIocContainer container) { if (_builtInModulesRegistered) { @@ -57,21 +54,25 @@ public sealed class ServiceModuleManager : IServiceModuleManager return; } + // 注册内置模块 RegisterModule(new EventBusModule()); RegisterModule(new CommandExecutorModule()); RegisterModule(new QueryExecutorModule()); RegisterModule(new AsyncQueryExecutorModule()); - if (properties.EnableEcs) + // 注册外部模块(通过 ArchitectureModuleRegistry 自动注册) + foreach (var module in ArchitectureModuleRegistry.CreateModules()) { - RegisterModule(new ArchEcsModule(enabled: true)); - _logger.Info("ECS module enabled via configuration"); + RegisterModule(module); + _logger.Info($"External module registered: {module.ModuleName}"); } + // 按优先级排序 var sortedModules = _modules.OrderBy(m => m.Priority).ToList(); _modules.Clear(); _modules.AddRange(sortedModules); + // 注册服务 foreach (var module in _modules.Where(module => module.IsEnabled)) { _logger.Debug($"Registering services for module: {module.ModuleName}"); @@ -79,7 +80,7 @@ public sealed class ServiceModuleManager : IServiceModuleManager } _builtInModulesRegistered = true; - _logger.Info($"Registered {_modules.Count} built-in service modules"); + _logger.Info($"Registered {_modules.Count} service modules"); } /// diff --git a/GFramework.Ecs.Arch.Abstractions/ArchOptions.cs b/GFramework.Ecs.Arch.Abstractions/ArchOptions.cs new file mode 100644 index 0000000..926f877 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/ArchOptions.cs @@ -0,0 +1,22 @@ +namespace GFramework.Ecs.Arch.Abstractions; + +/// +/// Arch ECS 配置选项 +/// +public sealed class ArchOptions +{ + /// + /// World 初始容量 + /// + public int WorldCapacity { get; set; } = 1000; + + /// + /// 是否启用统计信息 + /// + public bool EnableStatistics { get; set; } + + /// + /// 模块优先级 + /// + public int Priority { get; set; } = 50; +} \ No newline at end of file diff --git a/GFramework.Ecs.Arch.Abstractions/Directory.Build.props b/GFramework.Ecs.Arch.Abstractions/Directory.Build.props new file mode 100644 index 0000000..8febdc4 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/Directory.Build.props @@ -0,0 +1,18 @@ + + + netstandard2.1 + true + true + preview + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + diff --git a/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj b/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj new file mode 100644 index 0000000..b937843 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj @@ -0,0 +1,13 @@ + + + + GeWuYou.$(AssemblyName) + true + enable + + + + + + + diff --git a/GFramework.Ecs.Arch.Abstractions/GlobalUsings.cs b/GFramework.Ecs.Arch.Abstractions/GlobalUsings.cs new file mode 100644 index 0000000..9d7f7e7 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using System; +global using System.Collections.Generic; +global using System.Threading.Tasks; \ No newline at end of file diff --git a/GFramework.Ecs.Arch.Abstractions/IArchEcsModule.cs b/GFramework.Ecs.Arch.Abstractions/IArchEcsModule.cs new file mode 100644 index 0000000..6dcc631 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/IArchEcsModule.cs @@ -0,0 +1,15 @@ +using GFramework.Core.Abstractions.architecture; + +namespace GFramework.Ecs.Arch.Abstractions; + +/// +/// Arch ECS 模块接口 - 定义 ECS 模块的核心契约 +/// +public interface IArchEcsModule : IServiceModule +{ + /// + /// 更新所有 ECS 系统 + /// + /// 帧间隔时间 + void Update(float deltaTime); +} \ No newline at end of file diff --git a/GFramework.Ecs.Arch.Abstractions/IArchSystemAdapter.cs b/GFramework.Ecs.Arch.Abstractions/IArchSystemAdapter.cs new file mode 100644 index 0000000..59c42d6 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/IArchSystemAdapter.cs @@ -0,0 +1,16 @@ +using GFramework.Core.Abstractions.system; + +namespace GFramework.Ecs.Arch.Abstractions; + +/// +/// Arch 系统适配器接口 - 桥接 Arch.System.ISystem<T> 到框架上下文 +/// +/// 系统数据类型(通常是 float 表示 deltaTime) +public interface IArchSystemAdapter : ISystem +{ + /// + /// 更新系统 + /// + /// 系统数据参数(通常是 deltaTime) + void Update(in T t); +} \ No newline at end of file diff --git a/GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj b/GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj new file mode 100644 index 0000000..1d5346f --- /dev/null +++ b/GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj @@ -0,0 +1,25 @@ + + + + net8.0;net10.0 + disable + enable + false + true + false + + + + + + + + + + + + + + + + diff --git a/GFramework.Ecs.Arch.Tests/GlobalUsings.cs b/GFramework.Ecs.Arch.Tests/GlobalUsings.cs new file mode 100644 index 0000000..170ef5b --- /dev/null +++ b/GFramework.Ecs.Arch.Tests/GlobalUsings.cs @@ -0,0 +1,5 @@ +global using System; +global using System.Collections.Generic; +global using System.Threading.Tasks; +global using NUnit.Framework; +global using GFramework.Ecs.Arch; \ No newline at end of file diff --git a/GFramework.Core.Tests/ecs/EcsAdvancedTests.cs b/GFramework.Ecs.Arch.Tests/ecs/EcsAdvancedTests.cs similarity index 97% rename from GFramework.Core.Tests/ecs/EcsAdvancedTests.cs rename to GFramework.Ecs.Arch.Tests/ecs/EcsAdvancedTests.cs index 3831138..3ef8f07 100644 --- a/GFramework.Core.Tests/ecs/EcsAdvancedTests.cs +++ b/GFramework.Ecs.Arch.Tests/ecs/EcsAdvancedTests.cs @@ -2,13 +2,11 @@ using System.Diagnostics.CodeAnalysis; using Arch.Core; using GFramework.Core.Abstractions.rule; using GFramework.Core.architecture; -using GFramework.Core.ecs; -using GFramework.Core.ecs.components; -using GFramework.Core.ecs.systems; using GFramework.Core.ioc; -using NUnit.Framework; +using GFramework.Ecs.Arch.components; +using GFramework.Ecs.Arch.systems; -namespace GFramework.Core.Tests.ecs; +namespace GFramework.Ecs.Arch.Tests.ecs; /// /// ECS 高级功能测试类 - 使用 Arch 原生 API diff --git a/GFramework.Core.Tests/ecs/EcsBasicTests.cs b/GFramework.Ecs.Arch.Tests/ecs/EcsBasicTests.cs similarity index 96% rename from GFramework.Core.Tests/ecs/EcsBasicTests.cs rename to GFramework.Ecs.Arch.Tests/ecs/EcsBasicTests.cs index c1d9f52..896789a 100644 --- a/GFramework.Core.Tests/ecs/EcsBasicTests.cs +++ b/GFramework.Ecs.Arch.Tests/ecs/EcsBasicTests.cs @@ -2,13 +2,11 @@ using System.Diagnostics.CodeAnalysis; using Arch.Core; using GFramework.Core.Abstractions.rule; using GFramework.Core.architecture; -using GFramework.Core.ecs; -using GFramework.Core.ecs.components; -using GFramework.Core.ecs.systems; using GFramework.Core.ioc; -using NUnit.Framework; +using GFramework.Ecs.Arch.components; +using GFramework.Ecs.Arch.systems; -namespace GFramework.Core.Tests.ecs; +namespace GFramework.Ecs.Arch.Tests.ecs; /// /// ECS 基础功能测试类 - 使用 Arch 原生 API diff --git a/GFramework.Core.Tests/ecs/EcsIntegrationTests.cs b/GFramework.Ecs.Arch.Tests/ecs/EcsIntegrationTests.cs similarity index 97% rename from GFramework.Core.Tests/ecs/EcsIntegrationTests.cs rename to GFramework.Ecs.Arch.Tests/ecs/EcsIntegrationTests.cs index 55287a9..db9c290 100644 --- a/GFramework.Core.Tests/ecs/EcsIntegrationTests.cs +++ b/GFramework.Ecs.Arch.Tests/ecs/EcsIntegrationTests.cs @@ -2,13 +2,11 @@ using System.Diagnostics.CodeAnalysis; using Arch.Core; using GFramework.Core.Abstractions.rule; using GFramework.Core.architecture; -using GFramework.Core.ecs; -using GFramework.Core.ecs.components; -using GFramework.Core.ecs.systems; using GFramework.Core.ioc; -using NUnit.Framework; +using GFramework.Ecs.Arch.components; +using GFramework.Ecs.Arch.systems; -namespace GFramework.Core.Tests.ecs; +namespace GFramework.Ecs.Arch.Tests.ecs; /// /// ECS 集成测试类 - 使用 Arch 原生 API diff --git a/GFramework.Ecs.Arch.Tests/integration/ExplicitRegistrationTests.cs b/GFramework.Ecs.Arch.Tests/integration/ExplicitRegistrationTests.cs new file mode 100644 index 0000000..515e5c9 --- /dev/null +++ b/GFramework.Ecs.Arch.Tests/integration/ExplicitRegistrationTests.cs @@ -0,0 +1,135 @@ +using Arch.Core; +using GFramework.Core.Abstractions.architecture; +using GFramework.Core.architecture; +using GFramework.Core.ioc; +using GFramework.Ecs.Arch.Abstractions; +using GFramework.Ecs.Arch.extensions; + +namespace GFramework.Ecs.Arch.Tests.integration; + +/// +/// 显式注册集成测试 +/// +[TestFixture] +public class ExplicitRegistrationTests +{ + [SetUp] + public void Setup() + { + _container = new MicrosoftDiContainer(); + _context = new ArchitectureContext(_container); + + // 清空注册表,确保测试隔离 + ArchitectureModuleRegistry.Clear(); + } + + [TearDown] + public void TearDown() + { + _container?.Clear(); + _context = null; + ArchitectureModuleRegistry.Clear(); + GameContext.Clear(); + } + + private MicrosoftDiContainer? _container; + private ArchitectureContext? _context; + + /// + /// 测试 Arch ECS 模块显式注册 + /// + [Test] + public void ArchEcsModule_Should_Be_Explicitly_Registered() + { + // Arrange + var architecture = new TestArchitecture(); + + // Act - 显式注册 + architecture.UseArch(); + architecture.Initialize(); + + // Assert - 验证 World 已注册(由 ArchEcsModule 注册) + var world = architecture.Context.GetService(); + Assert.That(world, Is.Not.Null, "World should be registered by ArchEcsModule"); + + // 验证 IArchEcsModule 已注册 + var ecsModule = architecture.Context.GetService(); + Assert.That(ecsModule, Is.Not.Null, "IArchEcsModule should be registered"); + } + + /// + /// 测试 World 是否正确注册到容器 + /// + [Test] + public void World_Should_Be_Registered_In_Container() + { + // Arrange + var architecture = new TestArchitecture(); + + // Act - 显式注册 + architecture.UseArch(); + architecture.Initialize(); + + // Assert + var world = architecture.Context.GetService(); + Assert.That(world, Is.Not.Null, "World should be registered in container"); + } + + /// + /// 测试带配置的注册 + /// + [Test] + public void UseArch_Should_Accept_Configuration() + { + // Arrange + var architecture = new TestArchitecture(); + var configCalled = false; + + // Act + architecture.UseArch(options => + { + options.WorldCapacity = 2000; + options.EnableStatistics = true; + configCalled = true; + }); + + // Assert + Assert.That(configCalled, Is.True, "Configuration delegate should be called"); + } + + /// + /// 测试链式调用 + /// + [Test] + public void UseArch_Should_Support_Chaining() + { + // Arrange & Act + var architecture = new TestArchitecture() + .UseArch() + .UseArch(options => options.WorldCapacity = 2000); + + architecture.Initialize(); + + // Assert - 验证模块已注册 + var world = architecture.Context.GetService(); + Assert.That(world, Is.Not.Null, "World should be registered"); + + var ecsModule = architecture.Context.GetService(); + Assert.That(ecsModule, Is.Not.Null, "IArchEcsModule should be registered"); + } + + /// + /// 测试架构类,用于测试 + /// + private class TestArchitecture : Architecture + { + public TestArchitecture() : base(new ArchitectureConfiguration()) + { + } + + protected override void OnInitialize() + { + // 测试架构,无需额外初始化 + } + } +} \ No newline at end of file diff --git a/GFramework.Core/ecs/ArchEcsModule.cs b/GFramework.Ecs.Arch/ArchEcsModule.cs similarity index 72% rename from GFramework.Core/ecs/ArchEcsModule.cs rename to GFramework.Ecs.Arch/ArchEcsModule.cs index 784b822..31ab8e9 100644 --- a/GFramework.Core/ecs/ArchEcsModule.cs +++ b/GFramework.Ecs.Arch/ArchEcsModule.cs @@ -1,25 +1,27 @@ using Arch.Core; -using GFramework.Core.Abstractions.architecture; using GFramework.Core.Abstractions.ioc; -namespace GFramework.Core.ecs; +namespace GFramework.Ecs.Arch; /// /// Arch ECS 模块 - 核心适配器,桥接 Arch 到框架生命周期 /// -public sealed class ArchEcsModule : IServiceModule +public sealed class ArchEcsModule : IArchEcsModule { - private readonly List> _systems = []; + private readonly ArchOptions _options; private IIocContainer? _container; private bool _isInitialized; + private IReadOnlyList> _systems = []; private World? _world; /// /// 构造函数 /// + /// 配置选项 /// 是否启用模块 - public ArchEcsModule(bool enabled = true) + public ArchEcsModule(ArchOptions? options = null, bool enabled = true) { + _options = options ?? new ArchOptions(); IsEnabled = enabled; } @@ -31,7 +33,7 @@ public sealed class ArchEcsModule : IServiceModule /// /// 模块优先级 /// - public int Priority => 50; + public int Priority => _options.Priority; /// /// 是否启用 @@ -47,8 +49,11 @@ public sealed class ArchEcsModule : IServiceModule _container = container; - // 创建并注册 World - _world = World.Create(); + // 注册模块自身 + container.RegisterPlurality(this); + + // 创建并注册 World(使用配置的容量) + _world = World.Create(_options.WorldCapacity); container.Register(_world); } @@ -65,17 +70,13 @@ public sealed class ArchEcsModule : IServiceModule return; } - // 从容器获取所有适配器 - var adapters = _container.GetAll>(); - if (adapters.Count > 0) - { - _systems.AddRange(adapters); + // 从容器按优先级获取所有适配器 + _systems = _container.GetAllByPriority>(); - // 初始化所有系统(会调用 Arch 系统的 Initialize) - foreach (var system in _systems) - { - system.Initialize(); - } + // 初始化所有系统(会调用 Arch 系统的 Initialize) + foreach (var system in _systems) + { + system.Initialize(); } _isInitialized = true; @@ -97,7 +98,7 @@ public sealed class ArchEcsModule : IServiceModule system.Destroy(); } - _systems.Clear(); + _systems = []; // 销毁 World if (_world != null) diff --git a/GFramework.Ecs.Arch/ArchOptions.cs b/GFramework.Ecs.Arch/ArchOptions.cs new file mode 100644 index 0000000..adc17e4 --- /dev/null +++ b/GFramework.Ecs.Arch/ArchOptions.cs @@ -0,0 +1,22 @@ +namespace GFramework.Ecs.Arch; + +/// +/// Arch ECS 模块配置选项 +/// +public sealed class ArchOptions +{ + /// + /// World 初始容量(默认:1000) + /// + public int WorldCapacity { get; set; } = 1000; + + /// + /// 是否启用统计信息(默认:false) + /// + public bool EnableStatistics { get; set; } = false; + + /// + /// 模块优先级(默认:50) + /// + public int Priority { get; set; } = 50; +} \ No newline at end of file diff --git a/GFramework.Core/ecs/ArchSystemAdapter.cs b/GFramework.Ecs.Arch/ArchSystemAdapter.cs similarity index 96% rename from GFramework.Core/ecs/ArchSystemAdapter.cs rename to GFramework.Ecs.Arch/ArchSystemAdapter.cs index 8a86c98..6f7e99f 100644 --- a/GFramework.Core/ecs/ArchSystemAdapter.cs +++ b/GFramework.Ecs.Arch/ArchSystemAdapter.cs @@ -3,19 +3,29 @@ using GFramework.Core.extensions; using GFramework.Core.system; using ArchSys = Arch.System; -namespace GFramework.Core.ecs; +namespace GFramework.Ecs.Arch; /// /// Arch 系统适配器 - 桥接 Arch.System.ISystem<T> 到框架上下文 /// /// 系统数据类型(通常是 float 表示 deltaTime) -public abstract class ArchSystemAdapter : AbstractSystem, ArchSys.ISystem +public abstract class ArchSystemAdapter : AbstractSystem, IArchSystemAdapter, ArchSys.ISystem { /// /// 获取或设置 Arch ECS 世界的实例 /// public World World { get; private set; } = null!; + /// + /// 显式实现 Arch.System.ISystem<T> 的主更新方法 + /// 调用受保护的虚方法 OnUpdate 以强制子类实现核心更新逻辑 + /// + /// 系统数据参数(通常是 deltaTime) + public void Update(in T t) + { + OnUpdate(in t); + } + // ===== Arch 显式接口实现 ===== /// @@ -37,16 +47,6 @@ public abstract class ArchSystemAdapter : AbstractSystem, ArchSys.ISystem OnBeforeUpdate(in t); } - /// - /// 显式实现 Arch.System.ISystem<T> 的主更新方法 - /// 调用受保护的虚方法 OnUpdate 以强制子类实现核心更新逻辑 - /// - /// 系统数据参数(通常是 deltaTime) - public void Update(in T t) - { - OnUpdate(in t); - } - /// /// 显式实现 Arch.System.ISystem<T> 的更新后回调方法 /// 调用受保护的虚方法 OnAfterUpdate 以允许子类自定义后处理逻辑 diff --git a/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj b/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj new file mode 100644 index 0000000..2b58a0e --- /dev/null +++ b/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj @@ -0,0 +1,23 @@ + + + + GeWuYou.$(AssemblyName) + net8.0;net9.0;net10.0 + disable + enable + true + true + + + + + + + + + + + + + diff --git a/GFramework.Ecs.Arch/GlobalUsings.cs b/GFramework.Ecs.Arch/GlobalUsings.cs new file mode 100644 index 0000000..50bd209 --- /dev/null +++ b/GFramework.Ecs.Arch/GlobalUsings.cs @@ -0,0 +1,5 @@ +global using System; +global using System.Collections.Generic; +global using System.Threading.Tasks; +global using GFramework.Core.Abstractions; +global using GFramework.Ecs.Arch.Abstractions; \ No newline at end of file diff --git a/GFramework.Ecs.Arch/README.md b/GFramework.Ecs.Arch/README.md new file mode 100644 index 0000000..96501af --- /dev/null +++ b/GFramework.Ecs.Arch/README.md @@ -0,0 +1,192 @@ +# GFramework.Ecs.Arch + +GFramework 的 Arch ECS 集成包,提供开箱即用的 ECS(Entity Component System)支持。 + +## 特性 + +- 🎯 **显式集成** - 符合 .NET 生态习惯的显式注册方式 +- 🔌 **零依赖** - 不使用时,Core 包无 Arch 依赖 +- 🎯 **类型安全** - 完整的类型系统和编译时检查 +- ⚡ **高性能** - 基于 Arch ECS 的高性能实现 +- 🔧 **易扩展** - 简单的系统适配器模式 + +## 快速开始 + +### 1. 安装包 + +```bash +dotnet add package GeWuYou.GFramework.Ecs.Arch +``` + +### 2. 注册 ECS 模块 + +```csharp +// 在架构初始化时添加 Arch ECS 支持 +var architecture = new GameArchitecture(config) + .UseArch(); // 添加 ECS 支持 + +architecture.Initialize(); +``` + +### 3. 带配置的注册 + +```csharp +var architecture = new GameArchitecture(config) + .UseArch(options => + { + options.WorldCapacity = 2000; + options.EnableStatistics = true; + options.Priority = 50; + }); + +architecture.Initialize(); +``` + +```csharp +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Sequential)] +public struct Position(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +[StructLayout(LayoutKind.Sequential)] +public struct Velocity(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} +``` + +### 5. 创建系统 + +```csharp +using Arch.Core; +using GFramework.Ecs.Arch; + +public sealed class MovementSystem : ArchSystemAdapter +{ + private QueryDescription _query; + + protected override void OnArchInitialize() + { + _query = new QueryDescription() + .WithAll(); + } + + protected override void OnUpdate(in float deltaTime) + { + World.Query(in _query, (ref Position pos, ref Velocity vel) => + { + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; + }); + } +} +``` + +### 6. 注册系统 + +```csharp +public class MyArchitecture : Architecture +{ + protected override void OnRegisterSystem(IIocContainer container) + { + container.Register(); + } +} +``` + +### 7. 创建实体 + +```csharp +var world = this.GetService(); +var entity = world.Create( + new Position(0, 0), + new Velocity(1, 1) +); +``` + +### 8. 更新系统 + +```csharp +var ecsModule = this.GetService(); +ecsModule.Update(deltaTime); +``` + +## 配置选项 + +### 代码配置 + +```csharp +var architecture = new GameArchitecture(config) + .UseArch(options => + { + options.WorldCapacity = 2000; + options.EnableStatistics = true; + options.Priority = 50; + }); +``` + +### 配置说明 + +- `WorldCapacity` - World 初始容量(默认:1000) +- `EnableStatistics` - 是否启用统计信息(默认:false) +- `Priority` - 模块优先级(默认:50) + +## 架构说明 + +### 显式注册模式 + +本包采用 .NET 生态标准的显式注册模式,基于架构实例: + +**优点:** + +- ✅ 符合 .NET 生态习惯 +- ✅ 显式、可控 +- ✅ 易于测试和调试 +- ✅ 支持配置 +- ✅ 支持链式调用 +- ✅ 避免"魔法"行为 + +**使用方式:** +```csharp +// 在架构初始化时添加 +var architecture = new GameArchitecture(config) + .UseArch(); // 显式注册 + +architecture.Initialize(); +``` + +详见:[INTEGRATION_PATTERN.md](INTEGRATION_PATTERN.md) + +### 系统适配器 + +`ArchSystemAdapter` 桥接 Arch.System.ISystem 到 GFramework 架构: + +- 自动获取 World 实例 +- 集成到框架生命周期 +- 支持上下文感知(Context-Aware) + +### 生命周期 + +1. **注册阶段** - 模块自动注册到架构 +2. **初始化阶段** - 创建 World,初始化系统 +3. **运行阶段** - 每帧调用 Update +4. **销毁阶段** - 清理资源,销毁 World + +## 示例 + +完整示例请参考 `GFramework.Ecs.Arch.Tests` 项目。 + +## 依赖 + +- GFramework.Core >= 1.0.0 +- Arch >= 2.1.0 +- Arch.System >= 1.1.0 + +## 许可证 + +MIT License diff --git a/GFramework.Core/ecs/components/Position.cs b/GFramework.Ecs.Arch/components/Position.cs similarity index 92% rename from GFramework.Core/ecs/components/Position.cs rename to GFramework.Ecs.Arch/components/Position.cs index 204f66d..fe625d0 100644 --- a/GFramework.Core/ecs/components/Position.cs +++ b/GFramework.Ecs.Arch/components/Position.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace GFramework.Core.ecs.components; +namespace GFramework.Ecs.Arch.components; /// /// 位置组件,用于表示实体在二维空间中的坐标位置。 diff --git a/GFramework.Core/ecs/components/Velocity.cs b/GFramework.Ecs.Arch/components/Velocity.cs similarity index 93% rename from GFramework.Core/ecs/components/Velocity.cs rename to GFramework.Ecs.Arch/components/Velocity.cs index 26465b2..dab5eb6 100644 --- a/GFramework.Core/ecs/components/Velocity.cs +++ b/GFramework.Ecs.Arch/components/Velocity.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace GFramework.Core.ecs.components; +namespace GFramework.Ecs.Arch.components; /// /// 速度结构体,用于表示二维空间中实体的瞬时速度向量 diff --git a/GFramework.Ecs.Arch/extensions/ArchExtensions.cs b/GFramework.Ecs.Arch/extensions/ArchExtensions.cs new file mode 100644 index 0000000..dda51b1 --- /dev/null +++ b/GFramework.Ecs.Arch/extensions/ArchExtensions.cs @@ -0,0 +1,31 @@ +using GFramework.Core.Abstractions.architecture; + +namespace GFramework.Ecs.Arch.extensions; + +/// +/// Arch ECS 扩展方法 +/// +public static class ArchExtensions +{ + /// + /// 添加 Arch ECS 支持到架构中 + /// + /// 架构类型 + /// 架构实例 + /// 可选的配置委托 + /// 架构实例,支持链式调用 + public static TArchitecture UseArch( + this TArchitecture architecture, + Action? configure = null) + where TArchitecture : IArchitecture + { + // 配置选项 + var options = new ArchOptions(); + configure?.Invoke(options); + + // 注册模块(传递配置选项) + ArchitectureModuleRegistry.Register(() => new ArchEcsModule(options, enabled: true)); + + return architecture; + } +} \ No newline at end of file diff --git a/GFramework.Core/ecs/systems/MovementSystem.cs b/GFramework.Ecs.Arch/systems/MovementSystem.cs similarity index 92% rename from GFramework.Core/ecs/systems/MovementSystem.cs rename to GFramework.Ecs.Arch/systems/MovementSystem.cs index 897ca8b..3f3b923 100644 --- a/GFramework.Core/ecs/systems/MovementSystem.cs +++ b/GFramework.Ecs.Arch/systems/MovementSystem.cs @@ -1,7 +1,7 @@ using Arch.Core; -using GFramework.Core.ecs.components; +using GFramework.Ecs.Arch.components; -namespace GFramework.Core.ecs.systems; +namespace GFramework.Ecs.Arch.systems; /// /// 移动系统 - 继承 ArchSystemAdapter diff --git a/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj b/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj index 3a8c827..4941dcf 100644 --- a/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj +++ b/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj @@ -1,9 +1,11 @@  + net8.0;net10.0 disable enable - net10.0;net8.0 + false + true diff --git a/GFramework.csproj b/GFramework.csproj index 09f1d65..ae91b88 100644 --- a/GFramework.csproj +++ b/GFramework.csproj @@ -47,6 +47,9 @@ + + + @@ -82,6 +85,9 @@ + + + @@ -103,6 +109,9 @@ + + + diff --git a/GFramework.sln b/GFramework.sln index 3eba55b..04b1d8f 100644 --- a/GFramework.sln +++ b/GFramework.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework ", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.SourceGenerators", "GFramework.SourceGenerators\GFramework.SourceGenerators.csproj", "{E9D51809-0351-4B83-B85B-B5F469AAB3B8}" EndProject @@ -26,63 +26,216 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Game.Abstraction EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core.Tests", "GFramework.Core.Tests\GFramework.Core.Tests.csproj", "{759BCD95-A9D9-4D8F-9255-A9F1B661DF74}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Ecs.Arch.Abstractions", "GFramework.Ecs.Arch.Abstractions\GFramework.Ecs.Arch.Abstractions.csproj", "{5E1488B2-6554-408D-83FB-EB2FFFABF545}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Ecs.Arch", "GFramework.Ecs.Arch\GFramework.Ecs.Arch.csproj", "{E9B49EE9-25BC-47C1-83CE-92F636B9D491}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Ecs.Arch.Tests", "GFramework.Ecs.Arch.Tests\GFramework.Ecs.Arch.Tests.csproj", "{112CF413-4596-4AA3-B3FE-65532802FDD6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|x64.ActiveCfg = Debug|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|x64.Build.0 = Debug|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|x86.ActiveCfg = Debug|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|x86.Build.0 = Debug|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.ActiveCfg = Release|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.Build.0 = Release|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x64.ActiveCfg = Release|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x64.Build.0 = Release|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x86.ActiveCfg = Release|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x86.Build.0 = Release|Any CPU {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x64.ActiveCfg = Debug|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x64.Build.0 = Debug|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x86.ActiveCfg = Debug|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x86.Build.0 = Debug|Any CPU {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.Build.0 = Release|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x64.ActiveCfg = Release|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x64.Build.0 = Release|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x86.ActiveCfg = Release|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x86.Build.0 = Release|Any CPU {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x64.ActiveCfg = Debug|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x64.Build.0 = Debug|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x86.ActiveCfg = Debug|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x86.Build.0 = Debug|Any CPU {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.ActiveCfg = Release|Any CPU {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.Build.0 = Release|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x64.ActiveCfg = Release|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x64.Build.0 = Release|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x86.ActiveCfg = Release|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x86.Build.0 = Release|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x64.ActiveCfg = Debug|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x64.Build.0 = Debug|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x86.ActiveCfg = Debug|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x86.Build.0 = Debug|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|Any CPU.Build.0 = Release|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|x64.ActiveCfg = Release|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|x64.Build.0 = Release|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|x86.ActiveCfg = Release|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|x86.Build.0 = Release|Any CPU {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|x64.ActiveCfg = Debug|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|x64.Build.0 = Debug|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|x86.ActiveCfg = Debug|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|x86.Build.0 = Debug|Any CPU {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|Any CPU.Build.0 = Release|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|x64.ActiveCfg = Release|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|x64.Build.0 = Release|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|x86.ActiveCfg = Release|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|x86.Build.0 = Release|Any CPU {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|x64.ActiveCfg = Debug|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|x64.Build.0 = Debug|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|x86.ActiveCfg = Debug|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|x86.Build.0 = Debug|Any CPU {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|Any CPU.ActiveCfg = Release|Any CPU {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|Any CPU.Build.0 = Release|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|x64.ActiveCfg = Release|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|x64.Build.0 = Release|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|x86.ActiveCfg = Release|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|x86.Build.0 = Release|Any CPU {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|x64.ActiveCfg = Debug|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|x64.Build.0 = Debug|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|x86.ActiveCfg = Debug|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|x86.Build.0 = Debug|Any CPU {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|x64.ActiveCfg = Release|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|x64.Build.0 = Release|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|x86.ActiveCfg = Release|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|x86.Build.0 = Release|Any CPU {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|x64.ActiveCfg = Debug|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|x64.Build.0 = Debug|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|x86.ActiveCfg = Debug|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|x86.Build.0 = Debug|Any CPU {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|Any CPU.Build.0 = Release|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|x64.ActiveCfg = Release|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|x64.Build.0 = Release|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|x86.ActiveCfg = Release|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|x86.Build.0 = Release|Any CPU {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|x64.ActiveCfg = Debug|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|x64.Build.0 = Debug|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|x86.ActiveCfg = Debug|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|x86.Build.0 = Debug|Any CPU {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|Any CPU.Build.0 = Release|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|x64.ActiveCfg = Release|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|x64.Build.0 = Release|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|x86.ActiveCfg = Release|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|x86.Build.0 = Release|Any CPU {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|x64.ActiveCfg = Debug|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|x64.Build.0 = Debug|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|x86.ActiveCfg = Debug|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|x86.Build.0 = Debug|Any CPU {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|Any CPU.ActiveCfg = Release|Any CPU {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|Any CPU.Build.0 = Release|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|x64.ActiveCfg = Release|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|x64.Build.0 = Release|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|x86.ActiveCfg = Release|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|x86.Build.0 = Release|Any CPU {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|x64.ActiveCfg = Debug|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|x64.Build.0 = Debug|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|x86.ActiveCfg = Debug|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|x86.Build.0 = Debug|Any CPU {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|Any CPU.Build.0 = Release|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|x64.ActiveCfg = Release|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|x64.Build.0 = Release|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|x86.ActiveCfg = Release|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|x86.Build.0 = Release|Any CPU {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|x64.ActiveCfg = Debug|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|x64.Build.0 = Debug|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|x86.ActiveCfg = Debug|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|x86.Build.0 = Debug|Any CPU {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|Any CPU.ActiveCfg = Release|Any CPU {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|Any CPU.Build.0 = Release|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|x64.ActiveCfg = Release|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|x64.Build.0 = Release|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|x86.ActiveCfg = Release|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|x86.Build.0 = Release|Any CPU {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|x64.ActiveCfg = Debug|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|x64.Build.0 = Debug|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|x86.ActiveCfg = Debug|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|x86.Build.0 = Debug|Any CPU {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|Any CPU.ActiveCfg = Release|Any CPU {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|Any CPU.Build.0 = Release|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|x64.ActiveCfg = Release|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|x64.Build.0 = Release|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|x86.ActiveCfg = Release|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|x86.Build.0 = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|x64.ActiveCfg = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|x64.Build.0 = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|x86.ActiveCfg = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|x86.Build.0 = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|Any CPU.Build.0 = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|x64.ActiveCfg = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|x64.Build.0 = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|x86.ActiveCfg = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|x86.Build.0 = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|x64.ActiveCfg = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|x64.Build.0 = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|x86.ActiveCfg = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|x86.Build.0 = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|Any CPU.Build.0 = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|x64.ActiveCfg = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|x64.Build.0 = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|x86.ActiveCfg = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|x86.Build.0 = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|x64.ActiveCfg = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|x64.Build.0 = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|x86.ActiveCfg = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|x86.Build.0 = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|Any CPU.Build.0 = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x64.ActiveCfg = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x64.Build.0 = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x86.ActiveCfg = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index a6aaef0..cc889fe 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -47,6 +47,7 @@ export default defineConfig({ { text: '首页', link: '/zh-CN/' }, { text: '入门指南', link: '/zh-CN/getting-started' }, { text: 'Core', link: '/zh-CN/core/' }, + { text: 'ECS', link: '/zh-CN/ecs/' }, { text: 'Game', link: '/zh-CN/game/' }, { text: 'Godot', link: '/zh-CN/godot/' }, { text: '源码生成器', link: '/zh-CN/source-generators' }, @@ -91,7 +92,6 @@ export default defineConfig({ { text: '事件系统', link: '/zh-CN/core/events' }, { text: '属性系统', link: '/zh-CN/core/property' }, { text: 'IoC容器', link: '/zh-CN/core/ioc' }, - { text: 'ECS 系统集成', link: '/zh-CN/core/ecs' }, { text: '协程系统', link: '/zh-CN/core/coroutine' }, { text: '状态机', link: '/zh-CN/core/state-machine' }, { text: '暂停系统', link: '/zh-CN/core/pause' }, @@ -110,6 +110,16 @@ export default defineConfig({ } ], + '/zh-CN/ecs/': [ + { + text: 'ECS 系统集成', + items: [ + { text: 'ECS 概述', link: '/zh-CN/ecs/' }, + { text: 'Arch ECS 集成', link: '/zh-CN/ecs/arch' } + ] + } + ], + '/zh-CN/game/': [ { text: 'Game 游戏模块', diff --git a/docs/zh-CN/core/ecs.md b/docs/zh-CN/core/ecs.md deleted file mode 100644 index 527fa6a..0000000 --- a/docs/zh-CN/core/ecs.md +++ /dev/null @@ -1,1006 +0,0 @@ ---- -title: ECS 系统集成 -description: ECS(Entity Component System)系统集成指南,基于 Arch.Core 实现高性能的实体组件系统。 ---- - -# ECS 系统集成 - -## 概述 - -GFramework 集成了 [Arch.Core](https://github.com/genaray/Arch) ECS 框架,提供高性能的实体组件系统(Entity Component -System)架构。通过 ECS 模式,你可以构建数据驱动、高度可扩展的游戏系统。 - -**主要特性**: - -- 基于 Arch.Core 的高性能 ECS 实现 -- 与 GFramework 架构无缝集成 -- 支持组件查询和批量处理 -- 零 GC 分配的组件访问 -- 灵活的系统生命周期管理 -- 支持多线程并行处理(Arch 原生支持) - -**性能特点**: - -- 10,000 个实体更新 < 100ms -- 1,000 个实体创建 < 50ms -- 基于 Archetype 的高效内存布局 -- 支持 SIMD 优化 - -## 核心概念 - -### Entity(实体) - -实体是游戏世界中的基本对象,本质上是一个唯一标识符(ID)。实体本身不包含数据或逻辑,只是组件的容器。 - -```csharp -using Arch.Core; - -// 创建实体 -var entity = world.Create(); - -// 创建带组件的实体 -var entity = world.Create(new Position(0, 0), new Velocity(1, 1)); -``` - -### Component(组件) - -组件是纯数据结构,用于存储实体的状态。组件应该是简单的值类型(struct),不包含逻辑。 - -```csharp -using System.Runtime.InteropServices; - -/// -/// 位置组件 -/// -[StructLayout(LayoutKind.Sequential)] -public struct Position(float x, float y) -{ - public float X { get; set; } = x; - public float Y { get; set; } = y; -} - -/// -/// 速度组件 -/// -[StructLayout(LayoutKind.Sequential)] -public struct Velocity(float x, float y) -{ - public float X { get; set; } = x; - public float Y { get; set; } = y; -} -``` - -**组件设计原则**: - -- 使用 `struct` 而不是 `class` -- 只包含数据,不包含逻辑 -- 使用 `[StructLayout(LayoutKind.Sequential)]` 优化内存布局 -- 保持组件小而专注 - -### System(系统) - -系统包含游戏逻辑,负责处理具有特定组件组合的实体。在 GFramework 中,系统通过继承 `ArchSystemAdapter<T>` 来实现。 - -```csharp -using Arch.Core; -using GFramework.Core.ecs; - -/// -/// 移动系统 - 更新实体位置 -/// -public sealed class MovementSystem : ArchSystemAdapter -{ - private QueryDescription _query; - - protected override void OnArchInitialize() - { - // 创建查询:查找所有同时拥有 Position 和 Velocity 组件的实体 - _query = new QueryDescription() - .WithAll(); - } - - protected override void OnUpdate(in float deltaTime) - { - // 查询并更新所有符合条件的实体 - World.Query(in _query, (ref Position pos, ref Velocity vel) => - { - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - }); - } -} -``` - -### World(世界) - -World 是 ECS 的核心容器,管理所有实体和组件。GFramework 通过 `ArchEcsModule` 自动创建和管理 World。 - -```csharp -// World 由 ArchEcsModule 自动创建和注册到 IoC 容器 -// 在系统中可以直接访问 -public class MySystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 访问 World - var entityCount = World.Size; - } -} -``` - -### Arch.Core 集成 - -GFramework 通过以下组件桥接 Arch.Core 到框架生命周期: - -- **ArchEcsModule**:ECS 模块,管理 World 和系统生命周期 -- **ArchSystemAdapter<T>**:系统适配器,桥接 Arch 系统到 GFramework - -## 基本用法 - -### 1. 定义组件 - -```csharp -using System.Runtime.InteropServices; - -namespace MyGame.Components; - -[StructLayout(LayoutKind.Sequential)] -public struct Health(float current, float max) -{ - public float Current { get; set; } = current; - public float Max { get; set; } = max; -} - -[StructLayout(LayoutKind.Sequential)] -public struct Damage(float value) -{ - public float Value { get; set; } = value; -} - -[StructLayout(LayoutKind.Sequential)] -public struct PlayerTag -{ - // 标签组件,不需要数据 -} -``` - -### 2. 创建系统 - -```csharp -using Arch.Core; -using GFramework.Core.ecs; -using MyGame.Components; - -namespace MyGame.Systems; - -/// -/// 伤害系统 - 处理伤害逻辑 -/// -public sealed class DamageSystem : ArchSystemAdapter -{ - private QueryDescription _query; - - protected override void OnArchInitialize() - { - // 查询所有具有 Health 和 Damage 组件的实体 - _query = new QueryDescription() - .WithAll(); - } - - protected override void OnUpdate(in float deltaTime) - { - // 处理伤害 - World.Query(in _query, (Entity entity, ref Health health, ref Damage damage) => - { - health.Current -= damage.Value * deltaTime; - - // 如果生命值耗尽,移除伤害组件 - if (health.Current <= 0) - { - health.Current = 0; - World.Remove(entity); - } - }); - } -} -``` - -### 3. 注册 ECS 模块 - -```csharp -using GFramework.Core.architecture; -using GFramework.Core.ecs; -using MyGame.Systems; - -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册 ECS 系统 - RegisterSystem(new MovementSystem()); - RegisterSystem(new DamageSystem()); - - // 安装 ECS 模块 - InstallModule(new ArchEcsModule(enabled: true)); - } -} -``` - -### 4. 创建和管理实体 - -```csharp -using Arch.Core; -using GFramework.Core.Abstractions.controller; -using MyGame.Components; -using GFramework.Core.Abstractions.controller; -using GFramework.SourceGenerators.Abstractions.rule; - -[ContextAware] -public partial class GameController : IController -{ - private World _world; - - public void Start() - { - // 获取 World - _world = this.GetService(); - - // 创建玩家实体 - var player = _world.Create( - new Position(0, 0), - new Velocity(0, 0), - new Health(100, 100), - new PlayerTag() - ); - - // 创建敌人实体 - var enemy = _world.Create( - new Position(10, 10), - new Velocity(-1, 0), - new Health(50, 50) - ); - } - - public void ApplyDamage(Entity entity, float damageValue) - { - // 添加伤害组件 - if (_world.Has(entity)) - { - _world.Add(entity, new Damage(damageValue)); - } - } -} -``` - -### 5. 更新 ECS 系统 - -```csharp -// 在游戏主循环中更新 ECS -public class GameLoop -{ - private ArchEcsModule _ecsModule; - - public void Update(float deltaTime) - { - // 更新所有 ECS 系统 - _ecsModule.Update(deltaTime); - } -} -``` - -## 高级用法 - -### 查询实体 - -Arch 提供了强大的查询 API,支持多种过滤条件: - -```csharp -using Arch.Core; - -public class QueryExampleSystem : ArchSystemAdapter -{ - private QueryDescription _query1; - private QueryDescription _query2; - private QueryDescription _query3; - - protected override void OnArchInitialize() - { - // 查询:必须有 Position 和 Velocity - _query1 = new QueryDescription() - .WithAll(); - - // 查询:必须有 Health,但不能有 Damage - _query2 = new QueryDescription() - .WithAll() - .WithNone(); - - // 查询:必须有 Position,可选 Velocity - _query3 = new QueryDescription() - .WithAll() - .WithAny(); - } - - protected override void OnUpdate(in float deltaTime) - { - // 使用查询 1 - World.Query(in _query1, (ref Position pos, ref Velocity vel) => - { - // 处理逻辑 - }); - - // 使用查询 2 - World.Query(in _query2, (Entity entity, ref Health health) => - { - // 处理逻辑 - }); - } -} -``` - -### 系统生命周期钩子 - -`ArchSystemAdapter<T>` 提供了多个生命周期钩子: - -```csharp -public class LifecycleExampleSystem : ArchSystemAdapter -{ - protected override void OnArchInitialize() - { - // Arch 系统初始化 - // 在这里创建查询、初始化资源 - } - - protected override void OnBeforeUpdate(in float deltaTime) - { - // 更新前调用 - // 可用于预处理逻辑 - } - - protected override void OnUpdate(in float deltaTime) - { - // 主更新逻辑 - } - - protected override void OnAfterUpdate(in float deltaTime) - { - // 更新后调用 - // 可用于后处理逻辑 - } - - protected override void OnArchDispose() - { - // 资源清理 - } -} -``` - -### 组件操作 - -```csharp -using Arch.Core; - -public class ComponentOperations -{ - private World _world; - - public void Examples() - { - var entity = _world.Create(); - - // 添加组件 - _world.Add(entity, new Position(0, 0)); - _world.Add(entity, new Velocity(1, 1)); - - // 检查组件 - if (_world.Has(entity)) - { - // 获取组件引用(零 GC 分配) - ref var pos = ref _world.Get(entity); - pos.X += 10; - } - - // 设置组件(替换现有值) - _world.Set(entity, new Position(100, 100)); - - // 移除组件 - _world.Remove(entity); - - // 销毁实体 - _world.Destroy(entity); - } -} -``` - -### 批量操作 - -```csharp -public class BatchOperations -{ - private World _world; - - public void CreateMultipleEntities() - { - // 批量创建实体 - for (int i = 0; i < 1000; i++) - { - _world.Create( - new Position(i, i), - new Velocity(1, 1) - ); - } - } - - public void ClearAllEntities() - { - // 清空所有实体 - _world.Clear(); - } -} -``` - -### 实体查询和迭代 - -```csharp -public class EntityIterationSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 方式 1:使用查询和 Lambda - var query = new QueryDescription().WithAll(); - World.Query(in query, (ref Position pos) => - { - // 处理每个实体 - }); - - // 方式 2:获取实体引用 - World.Query(in query, (Entity entity, ref Position pos) => - { - // 可以访问实体 ID - if (pos.X > 100) - { - World.Destroy(entity); - } - }); - - // 方式 3:多组件查询 - var multiQuery = new QueryDescription() - .WithAll(); - - World.Query(in multiQuery, ( - Entity entity, - ref Position pos, - ref Velocity vel, - ref Health health) => - { - // 处理多个组件 - }); - } -} -``` - -## 性能优化 - -### 1. 使用 struct 组件 - -```csharp -// ✅ 推荐:使用 struct -[StructLayout(LayoutKind.Sequential)] -public struct Position(float x, float y) -{ - public float X { get; set; } = x; - public float Y { get; set; } = y; -} - -// ❌ 不推荐:使用 class -public class Position -{ - public float X { get; set; } - public float Y { get; set; } -} -``` - -### 2. 缓存查询 - -```csharp -public class OptimizedSystem : ArchSystemAdapter -{ - // ✅ 推荐:缓存查询 - private QueryDescription _cachedQuery; - - protected override void OnArchInitialize() - { - _cachedQuery = new QueryDescription() - .WithAll(); - } - - protected override void OnUpdate(in float deltaTime) - { - // 使用缓存的查询 - World.Query(in _cachedQuery, (ref Position pos, ref Velocity vel) => - { - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - }); - } -} - -// ❌ 不推荐:每次创建新查询 -public class UnoptimizedSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 每帧创建新查询(性能差) - var query = new QueryDescription().WithAll(); - World.Query(in query, (ref Position pos, ref Velocity vel) => - { - // ... - }); - } -} -``` - -### 3. 使用 ref 访问组件 - -```csharp -// ✅ 推荐:使用 ref 避免复制 -World.Query(in query, (ref Position pos, ref Velocity vel) => -{ - pos.X += vel.X; // 直接修改,零 GC -}); - -// ❌ 不推荐:不使用 ref -World.Query(in query, (Position pos, Velocity vel) => -{ - pos.X += vel.X; // 复制值,修改不会生效 -}); -``` - -### 4. 组件大小优化 - -```csharp -// ✅ 推荐:小而专注的组件 -public struct Position(float x, float y) -{ - public float X { get; set; } = x; - public float Y { get; set; } = y; -} - -public struct Velocity(float x, float y) -{ - public float X { get; set; } = x; - public float Y { get; set; } = y; -} - -// ❌ 不推荐:大而全的组件 -public struct Transform -{ - public float X, Y, Z; - public float RotationX, RotationY, RotationZ; - public float ScaleX, ScaleY, ScaleZ; - public float VelocityX, VelocityY, VelocityZ; - // ... 太多数据 -} -``` - -### 5. 批量处理 - -```csharp -public class BatchProcessingSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // ✅ 推荐:批量处理 - var query = new QueryDescription().WithAll(); - World.Query(in query, (ref Position pos, ref Velocity vel) => - { - // 一次查询处理所有实体 - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - }); - } -} - -// ❌ 不推荐:逐个处理 -public class IndividualProcessingSystem : ArchSystemAdapter -{ - private List _entities = new(); - - protected override void OnUpdate(in float deltaTime) - { - foreach (var entity in _entities) - { - ref var pos = ref World.Get(entity); - ref var vel = ref World.Get(entity); - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - } - } -} -``` - -## 最佳实践 - -### 1. ECS 设计模式 - -**组件组合优于继承**: - -```csharp -// ✅ 推荐:使用组件组合 -var player = world.Create( - new Position(0, 0), - new Velocity(0, 0), - new Health(100, 100), - new PlayerTag(), - new Controllable() -); - -var enemy = world.Create( - new Position(10, 10), - new Velocity(-1, 0), - new Health(50, 50), - new EnemyTag(), - new AI() -); - -// ❌ 不推荐:使用继承 -public class Player : Entity { } -public class Enemy : Entity { } -``` - -**单一职责系统**: - -```csharp -// ✅ 推荐:每个系统只负责一件事 -public class MovementSystem : ArchSystemAdapter -{ - // 只负责移动 -} - -public class CollisionSystem : ArchSystemAdapter -{ - // 只负责碰撞检测 -} - -public class DamageSystem : ArchSystemAdapter -{ - // 只负责伤害处理 -} - -// ❌ 不推荐:一个系统做太多事 -public class GameplaySystem : ArchSystemAdapter -{ - // 移动、碰撞、伤害、AI... 太多职责 -} -``` - -### 2. 与传统架构结合 - -ECS 可以与 GFramework 的传统架构(Model、System、Utility)结合使用: - -```csharp -// Model 存储全局状态 -public class GameStateModel : AbstractModel -{ - public int Score { get; set; } - public int Level { get; set; } -} - -// ECS System 处理实体逻辑 -public class EnemySpawnSystem : ArchSystemAdapter -{ - private float _spawnTimer; - - protected override void OnUpdate(in float deltaTime) - { - _spawnTimer += deltaTime; - - if (_spawnTimer >= 2.0f) - { - // 获取 Model - var gameState = this.GetModel(); - - // 根据关卡生成敌人 - var enemyCount = gameState.Level * 2; - for (int i = 0; i < enemyCount; i++) - { - World.Create( - new Position(Random.Shared.Next(0, 100), 0), - new Velocity(0, -1), - new Health(50, 50), - new EnemyTag() - ); - } - - _spawnTimer = 0; - } - } -} - -// 传统 System 处理游戏逻辑 -public class ScoreSystem : AbstractSystem -{ - protected override void OnInit() - { - // 监听敌人死亡事件 - this.RegisterEvent(OnEnemyDestroyed); - } - - private void OnEnemyDestroyed(EnemyDestroyedEvent e) - { - var gameState = this.GetModel(); - gameState.Score += 100; - } -} -``` - -### 3. 事件集成 - -ECS 系统可以发送和接收框架事件: - -```csharp -// 定义事件 -public struct EnemyDestroyedEvent -{ - public Entity Enemy { get; init; } - public int Score { get; init; } -} - -// ECS 系统发送事件 -public class HealthSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - var query = new QueryDescription() - .WithAll(); - - World.Query(in query, (Entity entity, ref Health health) => - { - if (health.Current <= 0) - { - // 发送事件 - this.SendEvent(new EnemyDestroyedEvent - { - Enemy = entity, - Score = 100 - }); - - // 销毁实体 - World.Destroy(entity); - } - }); - } -} - -// 传统系统接收事件 -public class UISystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnEnemyDestroyed); - } - - private void OnEnemyDestroyed(EnemyDestroyedEvent e) - { - // 更新 UI - Console.WriteLine($"Enemy destroyed! +{e.Score} points"); - } -} -``` - -### 4. 标签组件 - -使用空结构体作为标签来分类实体: - -```csharp -// 定义标签组件 -public struct PlayerTag { } -public struct EnemyTag { } -public struct BulletTag { } -public struct DeadTag { } - -// 使用标签过滤实体 -public class PlayerMovementSystem : ArchSystemAdapter -{ - private QueryDescription _query; - - protected override void OnArchInitialize() - { - // 只处理玩家实体 - _query = new QueryDescription() - .WithAll() - .WithNone(); - } - - protected override void OnUpdate(in float deltaTime) - { - World.Query(in _query, (ref Position pos, ref Velocity vel) => - { - // 只更新活着的玩家 - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - }); - } -} -``` - -### 5. 组件生命周期管理 - -```csharp -public class LifecycleManagementSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 处理临时效果 - var buffQuery = new QueryDescription().WithAll(); - World.Query(in buffQuery, (Entity entity, ref BuffComponent buff) => - { - buff.Duration -= deltaTime; - - if (buff.Duration <= 0) - { - // 移除过期的 Buff - World.Remove(entity); - } - }); - - // 清理死亡实体 - var deadQuery = new QueryDescription().WithAll(); - World.Query(in deadQuery, (Entity entity) => - { - World.Destroy(entity); - }); - } -} -``` - -## 常见问题 - -### Q: 如何在 ECS 系统中访问其他服务? - -A: `ArchSystemAdapter<T>` 继承自 `AbstractSystem`,可以使用所有 GFramework 的扩展方法: - -```csharp -public class ServiceAccessSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 获取 Model - var playerModel = this.GetModel(); - - // 获取 Utility - var timeUtility = this.GetUtility(); - - // 发送命令 - this.SendCommand(new SaveGameCommand()); - - // 发送查询 - var score = this.SendQuery(new GetScoreQuery()); - - // 发送事件 - this.SendEvent(new GameOverEvent()); - } -} -``` - -### Q: ECS 和传统架构如何选择? - -A: 根据场景选择: - -- **使用 ECS**:大量相似实体、需要高性能批量处理(敌人、子弹、粒子) -- **使用传统架构**:全局状态、单例服务、UI 逻辑、游戏流程控制 - -### Q: 如何调试 ECS 系统? - -A: 使用以下方法: - -```csharp -public class DebugSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 打印实体数量 - Console.WriteLine($"Total entities: {World.Size}"); - - // 查询特定实体 - var query = new QueryDescription().WithAll(); - var count = 0; - World.Query(in query, (Entity entity, ref Position pos) => - { - count++; - Console.WriteLine($"Entity {entity.Id}: ({pos.X}, {pos.Y})"); - }); - - Console.WriteLine($"Entities with Position: {count}"); - } -} -``` - -### Q: 如何处理实体之间的交互? - -A: 使用查询和事件: - -```csharp -public class CollisionSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - var playerQuery = new QueryDescription() - .WithAll(); - var enemyQuery = new QueryDescription() - .WithAll(); - - // 检测玩家和敌人的碰撞 - World.Query(in playerQuery, (Entity player, ref Position playerPos) => - { - World.Query(in enemyQuery, (Entity enemy, ref Position enemyPos) => - { - var distance = Math.Sqrt( - Math.Pow(playerPos.X - enemyPos.X, 2) + - Math.Pow(playerPos.Y - enemyPos.Y, 2) - ); - - if (distance < 1.0f) - { - // 发送碰撞事件 - this.SendEvent(new CollisionEvent - { - Entity1 = player, - Entity2 = enemy - }); - } - }); - }); - } -} -``` - -### Q: 如何优化大量实体的性能? - -A: 参考性能优化章节,主要策略: - -1. 使用 struct 组件 -2. 缓存查询 -3. 使用 ref 访问组件 -4. 批量处理 -5. 合理设计组件大小 -6. 使用 Arch 的并行查询(高级特性) - -### Q: 可以在运行时动态添加/移除组件吗? - -A: 可以,Arch 支持运行时修改实体的组件: - -```csharp -public class DynamicComponentSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - var query = new QueryDescription().WithAll(); - - World.Query(in query, (Entity entity, ref Position pos) => - { - // 动态添加组件 - if (pos.X > 100 && !World.Has(entity)) - { - World.Add(entity, new FastTag()); - } - - // 动态移除组件 - if (pos.X < 0 && World.Has(entity)) - { - World.Remove(entity); - } - }); - } -} -``` - -## 相关资源 - -- [Arch.Core 官方文档](https://github.com/genaray/Arch) -- [Architecture 包使用说明](./architecture.md) -- [System 包使用说明](./system.md) -- [事件系统](./events.md) - ---- - -**许可证**:Apache 2.0 diff --git a/docs/zh-CN/ecs/arch.md b/docs/zh-CN/ecs/arch.md new file mode 100644 index 0000000..19a1441 --- /dev/null +++ b/docs/zh-CN/ecs/arch.md @@ -0,0 +1,751 @@ +--- +title: Arch ECS 集成 +description: GFramework 的 Arch ECS 集成包使用指南,提供高性能的实体组件系统支持。 +--- + +# Arch ECS 集成 + +## 概述 + +`GFramework.Ecs.Arch` 是 GFramework 的 Arch ECS 集成包,提供开箱即用的 ECS(Entity Component +System)支持。基于 [Arch.Core](https://github.com/genaray/Arch) 实现,具有极致的性能和简洁的 API。 + +**主要特性**: + +- 🎯 **显式集成** - 符合 .NET 生态习惯的显式注册方式 +- 🔌 **零依赖** - 不使用时,Core 包无 Arch 依赖 +- 🎯 **类型安全** - 完整的类型系统和编译时检查 +- ⚡ **高性能** - 基于 Arch ECS 的高性能实现 +- 🔧 **易扩展** - 简单的系统适配器模式 +- 📊 **优先级支持** - 系统按优先级顺序执行 + +**性能特点**: + +- 10,000 个实体更新 < 100ms +- 1,000 个实体创建 < 50ms +- 基于 Archetype 的高效内存布局 +- 零 GC 分配的组件访问 + +## 安装 + +```bash +dotnet add package GeWuYou.GFramework.Ecs.Arch +``` + +## 快速开始 + +### 1. 注册 ECS 模块 + +```csharp +using GFramework.Core.architecture; +using GFramework.Ecs.Arch.extensions; + +public class GameArchitecture : Architecture +{ + public GameArchitecture() : base(new ArchitectureConfiguration()) + { + } + + protected override void OnInitialize() + { + // 显式注册 Arch ECS 模块 + this.UseArch(); + } +} + +// 初始化架构 +var architecture = new GameArchitecture(); +architecture.Initialize(); +``` + +### 2. 带配置的注册 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void OnInitialize() + { + // 带配置的注册 + this.UseArch(options => + { + options.WorldCapacity = 2000; // World 初始容量 + options.EnableStatistics = true; // 启用统计信息 + options.Priority = 50; // 模块优先级 + }); + } +} +``` + +### 3. 定义组件 + +组件是纯数据结构,使用 `struct` 定义: + +```csharp +using System.Runtime.InteropServices; + +namespace MyGame.Components; + +[StructLayout(LayoutKind.Sequential)] +public struct Position(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +[StructLayout(LayoutKind.Sequential)] +public struct Velocity(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +[StructLayout(LayoutKind.Sequential)] +public struct Health(float current, float max) +{ + public float Current { get; set; } = current; + public float Max { get; set; } = max; +} +``` + +### 4. 创建系统 + +系统继承自 `ArchSystemAdapter`: + +```csharp +using Arch.Core; +using GFramework.Ecs.Arch; +using MyGame.Components; + +namespace MyGame.Systems; + +/// +/// 移动系统 - 更新实体位置 +/// +public sealed class MovementSystem : ArchSystemAdapter +{ + private QueryDescription _query; + + protected override void OnArchInitialize() + { + // 创建查询:查找所有同时拥有 Position 和 Velocity 组件的实体 + _query = new QueryDescription() + .WithAll(); + } + + protected override void OnUpdate(in float deltaTime) + { + // 查询并更新所有符合条件的实体 + World.Query(in _query, (ref Position pos, ref Velocity vel) => + { + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; + }); + } +} +``` + +### 5. 注册系统 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void OnInitialize() + { + this.UseArch(); + + // 注册 ECS 系统 + RegisterSystem(); + } +} +``` + +### 6. 创建实体 + +```csharp +using Arch.Core; +using GFramework.Core.Abstractions.rule; +using GFramework.SourceGenerators.Abstractions.rule; +using MyGame.Components; + +[ContextAware] +public partial class GameController +{ + public void Start() + { + // 获取 World + var world = this.GetService(); + + // 创建实体 + var player = world.Create( + new Position(0, 0), + new Velocity(0, 0), + new Health(100, 100) + ); + + var enemy = world.Create( + new Position(10, 10), + new Velocity(-1, 0), + new Health(50, 50) + ); + } +} +``` + +### 7. 更新系统 + +```csharp +using GFramework.Ecs.Arch.Abstractions; + +public class GameLoop +{ + private IArchEcsModule _ecsModule; + + public void Initialize() + { + // 获取 ECS 模块 + _ecsModule = architecture.Context.GetService(); + } + + public void Update(float deltaTime) + { + // 更新所有 ECS 系统 + _ecsModule.Update(deltaTime); + } +} +``` + +## 配置选项 + +### ArchOptions + +```csharp +public sealed class ArchOptions +{ + /// + /// World 初始容量(默认:1000) + /// + public int WorldCapacity { get; set; } = 1000; + + /// + /// 是否启用统计信息(默认:false) + /// + public bool EnableStatistics { get; set; } = false; + + /// + /// 模块优先级(默认:50) + /// + public int Priority { get; set; } = 50; +} +``` + +### 配置示例 + +```csharp +this.UseArch(options => +{ + // 设置 World 初始容量 + // 根据预期实体数量设置,避免频繁扩容 + options.WorldCapacity = 2000; + + // 启用统计信息(开发/调试时使用) + options.EnableStatistics = true; + + // 设置模块优先级 + // 数值越小,优先级越高 + options.Priority = 50; +}); +``` + +## 核心概念 + +### Entity(实体) + +实体是游戏世界中的基本对象,本质上是一个唯一标识符: + +```csharp +// 创建空实体 +var entity = world.Create(); + +// 创建带组件的实体 +var entity = world.Create( + new Position(0, 0), + new Velocity(1, 1) +); + +// 销毁实体 +world.Destroy(entity); +``` + +### Component(组件) + +组件是纯数据结构,用于存储实体的状态: + +```csharp +// 添加组件 +world.Add(entity, new Position(0, 0)); + +// 检查组件 +if (world.Has(entity)) +{ + // 获取组件引用(零 GC 分配) + ref var pos = ref world.Get(entity); + pos.X += 10; +} + +// 设置组件(替换现有值) +world.Set(entity, new Position(100, 100)); + +// 移除组件 +world.Remove(entity); +``` + +### System(系统) + +系统包含游戏逻辑,处理具有特定组件组合的实体: + +```csharp +public sealed class DamageSystem : ArchSystemAdapter +{ + private QueryDescription _query; + + protected override void OnArchInitialize() + { + // 初始化查询 + _query = new QueryDescription() + .WithAll(); + } + + protected override void OnUpdate(in float deltaTime) + { + // 处理伤害 + World.Query(in _query, (Entity entity, ref Health health, ref Damage damage) => + { + health.Current -= damage.Value * deltaTime; + + if (health.Current <= 0) + { + health.Current = 0; + World.Remove(entity); + } + }); + } +} +``` + +### World(世界) + +World 是 ECS 的核心容器,管理所有实体和组件: + +```csharp +// World 由 ArchEcsModule 自动创建和注册 +var world = this.GetService(); + +// 获取实体数量 +var entityCount = world.Size; + +// 清空所有实体 +world.Clear(); +``` + +## 系统适配器 + +### ArchSystemAdapter + +`ArchSystemAdapter` 桥接 Arch.System.ISystem 到 GFramework 架构: + +```csharp +public sealed class MySystem : ArchSystemAdapter +{ + // Arch 系统初始化 + protected override void OnArchInitialize() + { + // 创建查询、初始化资源 + } + + // 更新前调用 + protected override void OnBeforeUpdate(in float deltaTime) + { + // 预处理逻辑 + } + + // 主更新逻辑 + protected override void OnUpdate(in float deltaTime) + { + // 处理实体 + } + + // 更新后调用 + protected override void OnAfterUpdate(in float deltaTime) + { + // 后处理逻辑 + } + + // 资源清理 + protected override void OnArchDispose() + { + // 清理资源 + } +} +``` + +### 访问 World + +在系统中可以直接访问 `World` 属性: + +```csharp +public sealed class MySystem : ArchSystemAdapter +{ + protected override void OnUpdate(in float deltaTime) + { + // 访问 World + var entityCount = World.Size; + + // 创建实体 + var entity = World.Create(new Position(0, 0)); + + // 查询实体 + var query = new QueryDescription().WithAll(); + World.Query(in query, (ref Position pos) => + { + // 处理逻辑 + }); + } +} +``` + +### 访问框架服务 + +`ArchSystemAdapter` 继承自 `AbstractSystem`,可以使用所有 GFramework 的扩展方法: + +```csharp +public sealed class ServiceAccessSystem : ArchSystemAdapter +{ + protected override void OnUpdate(in float deltaTime) + { + // 获取 Model + var playerModel = this.GetModel(); + + // 获取 Utility + var timeUtility = this.GetUtility(); + + // 发送命令 + this.SendCommand(new SaveGameCommand()); + + // 发送查询 + var score = this.SendQuery(new GetScoreQuery()); + + // 发送事件 + this.SendEvent(new GameOverEvent()); + } +} +``` + +## 查询实体 + +### 基本查询 + +```csharp +// 查询:必须有 Position 和 Velocity +var query = new QueryDescription() + .WithAll(); + +World.Query(in query, (ref Position pos, ref Velocity vel) => +{ + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; +}); +``` + +### 过滤查询 + +```csharp +// 查询:必须有 Health,但不能有 Damage +var query = new QueryDescription() + .WithAll() + .WithNone(); + +World.Query(in query, (ref Health health) => +{ + // 只处理没有受伤的实体 +}); +``` + +### 可选组件查询 + +```csharp +// 查询:必须有 Position,可选 Velocity +var query = new QueryDescription() + .WithAll() + .WithAny(); + +World.Query(in query, (Entity entity, ref Position pos) => +{ + // 处理逻辑 +}); +``` + +### 访问实体 ID + +```csharp +var query = new QueryDescription().WithAll(); + +World.Query(in query, (Entity entity, ref Position pos) => +{ + // 可以访问实体 ID + Console.WriteLine($"Entity {entity.Id}: ({pos.X}, {pos.Y})"); + + // 可以对实体进行操作 + if (pos.X > 100) + { + World.Destroy(entity); + } +}); +``` + +## 系统优先级 + +系统按照优先级顺序执行,数值越小优先级越高: + +```csharp +using GFramework.Core.Abstractions.bases; +using GFramework.SourceGenerators.Abstractions.bases; + +// 使用 Priority 特性设置优先级 +[Priority(10)] // 高优先级,先执行 +public sealed class InputSystem : ArchSystemAdapter +{ + // ... +} + +[Priority(20)] // 中优先级 +public sealed class MovementSystem : ArchSystemAdapter +{ + // ... +} + +[Priority(30)] // 低优先级,后执行 +public sealed class RenderSystem : ArchSystemAdapter +{ + // ... +} +``` + +执行顺序:InputSystem → MovementSystem → RenderSystem + +## 性能优化 + +### 1. 使用 struct 组件 + +```csharp +// ✅ 推荐:使用 struct +[StructLayout(LayoutKind.Sequential)] +public struct Position(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +// ❌ 不推荐:使用 class +public class Position +{ + public float X { get; set; } + public float Y { get; set; } +} +``` + +### 2. 缓存查询 + +```csharp +public class OptimizedSystem : ArchSystemAdapter +{ + // ✅ 推荐:缓存查询 + private QueryDescription _cachedQuery; + + protected override void OnArchInitialize() + { + _cachedQuery = new QueryDescription() + .WithAll(); + } + + protected override void OnUpdate(in float deltaTime) + { + World.Query(in _cachedQuery, (ref Position pos, ref Velocity vel) => + { + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; + }); + } +} +``` + +### 3. 使用 ref 访问组件 + +```csharp +// ✅ 推荐:使用 ref 避免复制 +World.Query(in query, (ref Position pos, ref Velocity vel) => +{ + pos.X += vel.X; // 直接修改,零 GC +}); + +// ❌ 不推荐:不使用 ref +World.Query(in query, (Position pos, Velocity vel) => +{ + pos.X += vel.X; // 复制值,修改不会生效 +}); +``` + +### 4. 组件大小优化 + +```csharp +// ✅ 推荐:小而专注的组件 +public struct Position(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +public struct Velocity(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +// ❌ 不推荐:大而全的组件 +public struct Transform +{ + public float X, Y, Z; + public float RotationX, RotationY, RotationZ; + public float ScaleX, ScaleY, ScaleZ; + public float VelocityX, VelocityY, VelocityZ; + // ... 太多数据 +} +``` + +## 最佳实践 + +### 1. 组件设计原则 + +- 使用 `struct` 而不是 `class` +- 只包含数据,不包含逻辑 +- 使用 `[StructLayout(LayoutKind.Sequential)]` 优化内存布局 +- 保持组件小而专注 + +### 2. 系统设计原则 + +- 单一职责:每个系统只负责一件事 +- 缓存查询:在 `OnArchInitialize` 中创建查询 +- 使用 ref:访问组件时使用 ref 参数 +- 批量处理:一次查询处理所有实体 + +### 3. 标签组件 + +使用空结构体作为标签来分类实体: + +```csharp +// 定义标签组件 +public struct PlayerTag { } +public struct EnemyTag { } +public struct DeadTag { } + +// 使用标签过滤实体 +var query = new QueryDescription() + .WithAll() + .WithNone(); +``` + +### 4. 与传统架构结合 + +```csharp +// ECS 系统可以访问 Model +public class EnemySpawnSystem : ArchSystemAdapter +{ + protected override void OnUpdate(in float deltaTime) + { + var gameState = this.GetModel(); + + // 根据关卡生成敌人 + for (int i = 0; i < gameState.Level; i++) + { + World.Create( + new Position(Random.Shared.Next(0, 100), 0), + new Velocity(0, -1), + new Health(50, 50) + ); + } + } +} +``` + +## 常见问题 + +### Q: 如何在运行时动态添加/移除组件? + +A: Arch 支持运行时修改实体的组件: + +```csharp +// 动态添加组件 +if (pos.X > 100 && !World.Has(entity)) +{ + World.Add(entity, new FastTag()); +} + +// 动态移除组件 +if (pos.X < 0 && World.Has(entity)) +{ + World.Remove(entity); +} +``` + +### Q: 如何处理实体之间的交互? + +A: 使用嵌套查询或事件: + +```csharp +// 方式 1:嵌套查询 +World.Query(in playerQuery, (Entity player, ref Position playerPos) => +{ + World.Query(in enemyQuery, (Entity enemy, ref Position enemyPos) => + { + // 检测碰撞 + }); +}); + +// 方式 2:使用事件 +this.SendEvent(new CollisionEvent +{ + Entity1 = player, + Entity2 = enemy +}); +``` + +### Q: 如何调试 ECS 系统? + +A: 使用日志和统计信息: + +```csharp +protected override void OnUpdate(in float deltaTime) +{ + // 打印实体数量 + Console.WriteLine($"Total entities: {World.Size}"); + + // 查询特定实体 + var query = new QueryDescription().WithAll(); + var count = 0; + World.Query(in query, (Entity entity, ref Position pos) => + { + count++; + Console.WriteLine($"Entity {entity.Id}: ({pos.X}, {pos.Y})"); + }); +} +``` + +## 相关资源 + +- [Arch.Core 官方文档](https://github.com/genaray/Arch) +- [ECS 概述](./index.md) +- [ECS 最佳实践](./best-practices.md) +- [性能优化指南](./performance.md) + +--- + +**许可证**:MIT License diff --git a/docs/zh-CN/ecs/index.md b/docs/zh-CN/ecs/index.md new file mode 100644 index 0000000..63100e6 --- /dev/null +++ b/docs/zh-CN/ecs/index.md @@ -0,0 +1,297 @@ +--- +title: ECS 系统集成 +description: GFramework 的 ECS(Entity Component System)集成方案,支持多种 ECS 框架。 +--- + +# ECS 系统集成 + +## 概述 + +GFramework 提供了灵活的 ECS(Entity Component System)集成方案,允许你根据项目需求选择合适的 ECS 框架。ECS +是一种数据驱动的架构模式,特别适合处理大量相似实体的场景。 + +## 什么是 ECS? + +ECS(Entity Component System)是一种架构模式,将游戏对象分解为三个核心概念: + +- **Entity(实体)**:游戏世界中的基本对象,本质上是一个唯一标识符 +- **Component(组件)**:纯数据结构,存储实体的状态 +- **System(系统)**:包含游戏逻辑,处理具有特定组件组合的实体 + +### ECS 的优势 + +- **高性能**:数据局部性好,缓存友好 +- **可扩展**:通过组合组件轻松创建新实体类型 +- **并行处理**:系统之间相互独立,易于并行化 +- **数据驱动**:逻辑与数据分离,便于序列化和网络同步 + +### 何时使用 ECS? + +**适合使用 ECS 的场景**: + +- 大量相似实体(敌人、子弹、粒子) +- 需要高性能批量处理 +- 复杂的实体组合和变化 +- 需要并行处理的系统 + +**不适合使用 ECS 的场景**: + +- 全局状态管理 +- 单例服务 +- UI 逻辑 +- 游戏流程控制 + +## 支持的 ECS 框架 + +GFramework 采用可选集成的设计,你可以根据需求选择合适的 ECS 框架: + +### Arch ECS(推荐) + +[Arch](https://github.com/genaray/Arch) 是一个高性能的 C# ECS 框架,具有以下特点: + +- ✅ **极致性能**:基于 Archetype 的内存布局,零 GC 分配 +- ✅ **简单易用**:清晰的 API,易于上手 +- ✅ **功能完整**:支持查询、过滤、并行处理等高级特性 +- ✅ **活跃维护**:社区活跃,持续更新 + +**安装方式**: + +```bash +dotnet add package GeWuYou.GFramework.Ecs.Arch +``` + +**文档链接**:[Arch ECS 集成指南](./arch.md) + +### 其他 ECS 框架 + +GFramework 的设计允许集成其他 ECS 框架,未来可能支持: + +- **DefaultEcs**:轻量级 ECS 框架 +- **Entitas**:成熟的 ECS 框架,Unity 生态常用 +- **自定义 ECS**:你可以基于 GFramework 的模块系统实现自己的 ECS 集成 + +## 快速开始 + +### 1. 选择 ECS 框架 + +根据项目需求选择合适的 ECS 框架。对于大多数项目,我们推荐使用 Arch ECS。 + +### 2. 安装集成包 + +```bash +# 安装 Arch ECS 集成包 +dotnet add package GeWuYou.GFramework.Ecs.Arch +``` + +### 3. 注册 ECS 模块 + +```csharp +using GFramework.Core.architecture; +using GFramework.Ecs.Arc; + +public class GameArchitecture : Architecture +{ + public GameArchitecture() : base(new ArchitectureConfiguration()) + { + } + + protected override void OnInitialize() + { + // 显式注册 Arch ECS 模块 + this.UseArch(options => + { + options.WorldCapacity = 2000; + options.EnableStatistics = true; + }); + } +} +``` + +### 4. 定义组件 + +```csharp +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Sequential)] +public struct Position(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +[StructLayout(LayoutKind.Sequential)] +public struct Velocity(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} +``` + +### 5. 创建系统 + +```csharp +using Arch.Core; +using GFramework.Ecs.Arch; + +public sealed class MovementSystem : ArchSystemAdapter +{ + private QueryDescription _query; + + protected override void OnArchInitialize() + { + _query = new QueryDescription() + .WithAll(); + } + + protected override void OnUpdate(in float deltaTime) + { + World.Query(in _query, (ref Position pos, ref Velocity vel) => + { + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; + }); + } +} +``` + +### 6. 注册系统 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void OnInitialize() + { + this.UseArch(); + + // 注册 ECS 系统 + RegisterSystem(); + } +} +``` + +## 设计理念 + +### 显式集成 + +GFramework 采用显式集成的设计,而不是自动注册: + +```csharp +// ✅ 显式注册 - 清晰、可控 +public class GameArchitecture : Architecture +{ + protected override void OnInitialize() + { + this.UseArch(); // 明确表示使用 Arch ECS + } +} + +// ❌ 自动注册 - 隐式、难以控制 +// 只需引入包,自动注册(不推荐) +``` + +**优势**: + +- 清晰的依赖关系 +- 更好的 IDE 支持 +- 易于测试和调试 +- 符合 .NET 生态习惯 + +### 零依赖原则 + +如果你不使用 ECS,GFramework.Core 包不会引入任何 ECS 相关的依赖: + +```xml + + + + + + + + + + +``` + +### 模块化设计 + +ECS 集成基于 GFramework 的模块系统: + +```csharp +// ECS 模块实现 IServiceModule 接口 +public sealed class ArchEcsModule : IArchEcsModule +{ + public string ModuleName => nameof(ArchEcsModule); + public int Priority => 50; + public bool IsEnabled { get; } + + public void Register(IIocContainer container) { } + public void Initialize() { } + public ValueTask DestroyAsync() { } + public void Update(float deltaTime) { } +} +``` + +## 与传统架构结合 + +ECS 可以与 GFramework 的传统架构(Model、System、Utility)无缝结合: + +```csharp +// Model 存储全局状态 +public class GameStateModel : AbstractModel +{ + public int Score { get; set; } + public int Level { get; set; } +} + +// ECS System 处理实体逻辑 +public class EnemySpawnSystem : ArchSystemAdapter +{ + protected override void OnUpdate(in float deltaTime) + { + // 访问 Model + var gameState = this.GetModel(); + + // 根据关卡生成敌人 + for (int i = 0; i < gameState.Level; i++) + { + World.Create( + new Position(Random.Shared.Next(0, 100), 0), + new Velocity(0, -1), + new Health(50, 50) + ); + } + } +} + +// 传统 System 处理游戏逻辑 +public class ScoreSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnEnemyDestroyed); + } + + private void OnEnemyDestroyed(EnemyDestroyedEvent e) + { + var gameState = this.GetModel(); + gameState.Score += 100; + } +} +``` + +## 下一步 + +- [Arch ECS 集成指南](./arch.md) - 详细的 Arch ECS 使用文档 +- [ECS 最佳实践](./best-practices.md) - ECS 设计模式和优化技巧 +- [性能优化](./performance.md) - ECS 性能优化指南 + +## 相关资源 + +- [Architecture 架构系统](../core/architecture.md) +- [System 系统](../core/system.md) +- [事件系统](../core/events.md) + +--- + +**许可证**:MIT License