diff --git a/Client/.claude/settings.local.json b/Client/.claude/settings.local.json new file mode 100644 index 0000000..dae41bb --- /dev/null +++ b/Client/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(find 'C:\\\\Users\\\\admin/Library/Mobile Documents/com~apple~CloudDocs' -name *.cs -path */GameObjectPool/*)" + ] + } +} diff --git a/Client/Assets/Scripts/New.meta b/Client/Assets/Scripts/New.meta new file mode 100644 index 0000000..698ce0f --- /dev/null +++ b/Client/Assets/Scripts/New.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fba5680fc6bcbe442a903a4b86f67d39 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example.meta b/Client/Assets/Scripts/New/Example.meta new file mode 100644 index 0000000..f9d3b3a --- /dev/null +++ b/Client/Assets/Scripts/New/Example.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 402c505f0455ab940813845ca361f217 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/App.meta b/Client/Assets/Scripts/New/Example/App.meta new file mode 100644 index 0000000..62c57f7 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/App.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e448305295085ec44a5199e6d99109a5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/App/ExampleAppRoot.cs b/Client/Assets/Scripts/New/Example/App/ExampleAppRoot.cs new file mode 100644 index 0000000..57e4466 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/App/ExampleAppRoot.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace Aliciza.Services.Example +{ + public sealed class ExampleAppRoot : GameServiceRoot + { + [SerializeField] private bool _registerTimerService = true; + + protected override void RegisterAppServices(ServiceScope appScope) + { + if (_registerTimerService) + { + appScope.Register(new TimerService()); + } + } + } +} diff --git a/Client/Assets/Scripts/New/Example/App/ExampleAppRoot.cs.meta b/Client/Assets/Scripts/New/Example/App/ExampleAppRoot.cs.meta new file mode 100644 index 0000000..d9a8362 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/App/ExampleAppRoot.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 67511ef137aa82745b794acd2f35016e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/App/ITimerService.cs b/Client/Assets/Scripts/New/Example/App/ITimerService.cs new file mode 100644 index 0000000..c597381 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/App/ITimerService.cs @@ -0,0 +1,17 @@ +using System; + +namespace Aliciza.Services.Example +{ + public interface ITimerService : IService + { + float ElapsedTime { get; } + + int Once(float delay, Action callback); + + int Repeat(float interval, Action callback, int repeatCount = -1, float firstDelay = -1f); + + bool Cancel(int timerId); + + void CancelAll(); + } +} diff --git a/Client/Assets/Scripts/New/Example/App/ITimerService.cs.meta b/Client/Assets/Scripts/New/Example/App/ITimerService.cs.meta new file mode 100644 index 0000000..b834e64 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/App/ITimerService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 497da23b9df8595458b3705d38d90f38 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/App/TimerService.cs b/Client/Assets/Scripts/New/Example/App/TimerService.cs new file mode 100644 index 0000000..ff724bd --- /dev/null +++ b/Client/Assets/Scripts/New/Example/App/TimerService.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; + +namespace Aliciza.Services.Example +{ + public sealed class TimerService : ServiceBase, ITimerService, IServiceTickable, IServiceOrder + { + private readonly List _timers = new List(); + + private int _nextTimerId = 1; + + public float ElapsedTime { get; private set; } + + public int Order => -1000; + + public int Once(float delay, Action callback) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + return AddTimer(delay, 0f, 1, callback); + } + + public int Repeat(float interval, Action callback, int repeatCount = -1, float firstDelay = -1f) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + if (interval <= 0f) + { + throw new ArgumentOutOfRangeException(nameof(interval), "Repeat interval must be greater than zero."); + } + + var delay = firstDelay < 0f ? interval : firstDelay; + return AddTimer(delay, interval, repeatCount, callback); + } + + public bool Cancel(int timerId) + { + for (var i = 0; i < _timers.Count; i++) + { + if (_timers[i].Id != timerId) + { + continue; + } + + _timers.RemoveAt(i); + return true; + } + + return false; + } + + public void CancelAll() + { + _timers.Clear(); + } + + public void Tick(float deltaTime) + { + ElapsedTime += deltaTime; + + for (var i = _timers.Count - 1; i >= 0; i--) + { + var timer = _timers[i]; + timer.Remaining -= deltaTime; + if (timer.Remaining > 0f) + { + _timers[i] = timer; + continue; + } + + timer.Callback?.Invoke(); + + if (timer.RepeatCount == 1) + { + _timers.RemoveAt(i); + continue; + } + + if (timer.RepeatCount > 1) + { + timer.RepeatCount--; + } + + if (timer.Interval <= 0f) + { + _timers.RemoveAt(i); + continue; + } + + timer.Remaining += timer.Interval; + if (timer.Remaining <= 0f) + { + timer.Remaining = timer.Interval; + } + + _timers[i] = timer; + } + } + + protected override void OnInitialize() + { + ElapsedTime = 0f; + } + + protected override void OnDestroyService() + { + CancelAll(); + ElapsedTime = 0f; + } + + private int AddTimer(float delay, float interval, int repeatCount, Action callback) + { + if (delay < 0f) + { + throw new ArgumentOutOfRangeException(nameof(delay), "Delay cannot be negative."); + } + + if (repeatCount == 0) + { + throw new ArgumentOutOfRangeException(nameof(repeatCount), "Repeat count cannot be zero."); + } + + var id = _nextTimerId++; + _timers.Add(new TimerTask + { + Id = id, + Remaining = delay, + Interval = interval, + RepeatCount = repeatCount, + Callback = callback, + }); + return id; + } + + private struct TimerTask + { + public int Id; + public float Remaining; + public float Interval; + public int RepeatCount; + public Action Callback; + } + } +} diff --git a/Client/Assets/Scripts/New/Example/App/TimerService.cs.meta b/Client/Assets/Scripts/New/Example/App/TimerService.cs.meta new file mode 100644 index 0000000..be90954 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/App/TimerService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76e7933ad4d82024890e70055ada0d28 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/Battle.meta b/Client/Assets/Scripts/New/Example/Battle.meta new file mode 100644 index 0000000..530605d --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Battle.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5797a82d5a1e5a24abf2b98f114cb1ab +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/Battle/BattleLoopService.cs b/Client/Assets/Scripts/New/Example/Battle/BattleLoopService.cs new file mode 100644 index 0000000..3a1831d --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Battle/BattleLoopService.cs @@ -0,0 +1,51 @@ +using UnityEngine; + +namespace Aliciza.Services.Example +{ + public sealed class BattleLoopService : ServiceBase, IBattleLoopService, IServiceTickable, IServiceOrder + { + private ITimerService _timerService; + private int _pulseTimerId = -1; + + public float ElapsedTime { get; private set; } + + public int PulseCount { get; private set; } + + public int Order => 100; + + public void Tick(float deltaTime) + { + ElapsedTime += deltaTime; + } + + protected override void OnInitialize() + { + ElapsedTime = 0f; + PulseCount = 0; + + if (World.TryGet(out _timerService)) + { + _pulseTimerId = _timerService.Repeat(1f, OnPulse); + } + } + + protected override void OnDestroyService() + { + if (_timerService != null && _pulseTimerId > 0) + { + _timerService.Cancel(_pulseTimerId); + } + + _pulseTimerId = -1; + _timerService = null; + ElapsedTime = 0f; + PulseCount = 0; + } + + private void OnPulse() + { + PulseCount++; + Debug.Log($"[BattleLoopService] Pulse {PulseCount}, Elapsed={ElapsedTime:F2}"); + } + } +} diff --git a/Client/Assets/Scripts/New/Example/Battle/BattleLoopService.cs.meta b/Client/Assets/Scripts/New/Example/Battle/BattleLoopService.cs.meta new file mode 100644 index 0000000..8049d54 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Battle/BattleLoopService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b4ddefabf153f4542aab67ba6bd036e5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/Battle/BattleScope.cs b/Client/Assets/Scripts/New/Example/Battle/BattleScope.cs new file mode 100644 index 0000000..2b35366 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Battle/BattleScope.cs @@ -0,0 +1,7 @@ +namespace Aliciza.Services.Example +{ + /// + /// 战斗 Scope 标记类。 + /// + public sealed class BattleScope { } +} diff --git a/Client/Assets/Scripts/New/Example/Battle/BattleScope.cs.meta b/Client/Assets/Scripts/New/Example/Battle/BattleScope.cs.meta new file mode 100644 index 0000000..001a0d1 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Battle/BattleScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fed8623053560524d8ed617cd2348343 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/Battle/ExampleBattleScopeController.cs b/Client/Assets/Scripts/New/Example/Battle/ExampleBattleScopeController.cs new file mode 100644 index 0000000..706702b --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Battle/ExampleBattleScopeController.cs @@ -0,0 +1,59 @@ +using UnityEngine; + +namespace Aliciza.Services.Example +{ + public sealed class ExampleBattleScopeController : MonoBehaviour + { + [SerializeField] private int _scopeOrder = 100; + [SerializeField] private bool _toggleByInput = true; + + private ServiceScope _battleScope; + + public bool HasBattleScope => _battleScope != null && !_battleScope.IsDisposed; + + private void Update() + { + if (!_toggleByInput) return; + + if (!GameServices.TryGet(out var inputService)) return; + + if (!inputService.ToggleBattlePressedThisFrame) return; + + if (HasBattleScope) ExitBattle(); + else EnterBattle(); + } + + private void OnDestroy() + { + ExitBattle(); + } + + [ContextMenu("Enter Battle")] + public void EnterBattle() + { + if (HasBattleScope) return; + + if (!GameServices.HasWorld) + { + Debug.LogWarning("[ExampleBattleScopeController] Cannot create Battle scope: ServiceWorld does not exist."); + return; + } + + _battleScope = GameServices.CreateScope(_scopeOrder); + _battleScope.Register(new BattleLoopService()); + Debug.Log("[ExampleBattleScopeController] Created BattleScope."); + } + + [ContextMenu("Exit Battle")] + public void ExitBattle() + { + if (_battleScope == null) return; + + if (GameServices.HasWorld) + GameServices.DestroyScope(); + + _battleScope = null; + Debug.Log("[ExampleBattleScopeController] Destroyed BattleScope."); + } + } +} diff --git a/Client/Assets/Scripts/New/Example/Battle/ExampleBattleScopeController.cs.meta b/Client/Assets/Scripts/New/Example/Battle/ExampleBattleScopeController.cs.meta new file mode 100644 index 0000000..5737769 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Battle/ExampleBattleScopeController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 546eeb5c7490a8e4ab7b2baafc332852 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/Battle/IBattleLoopService.cs b/Client/Assets/Scripts/New/Example/Battle/IBattleLoopService.cs new file mode 100644 index 0000000..8d02ca8 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Battle/IBattleLoopService.cs @@ -0,0 +1,9 @@ +namespace Aliciza.Services.Example +{ + public interface IBattleLoopService : IService + { + float ElapsedTime { get; } + + int PulseCount { get; } + } +} diff --git a/Client/Assets/Scripts/New/Example/Battle/IBattleLoopService.cs.meta b/Client/Assets/Scripts/New/Example/Battle/IBattleLoopService.cs.meta new file mode 100644 index 0000000..8a4f6fd --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Battle/IBattleLoopService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b7d08a93f695ca4c8576510d3c72f93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/Scene.meta b/Client/Assets/Scripts/New/Example/Scene.meta new file mode 100644 index 0000000..59df0da --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Scene.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 73ff46e60c117724986b35ac3575e001 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/Scene/ExampleInputManager.cs b/Client/Assets/Scripts/New/Example/Scene/ExampleInputManager.cs new file mode 100644 index 0000000..4af2251 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Scene/ExampleInputManager.cs @@ -0,0 +1,39 @@ +using UnityEngine; + +namespace Aliciza.Services.Example +{ + [DefaultExecutionOrder(-100)] + public sealed class ExampleInputManager : MonoServiceBehaviour, IInputService + { + [SerializeField] private string _horizontalAxis = "Horizontal"; + [SerializeField] private string _verticalAxis = "Vertical"; + [SerializeField] private KeyCode _confirmKey = KeyCode.Space; + [SerializeField] private KeyCode _toggleBattleKey = KeyCode.B; + + public Vector2 Move { get; private set; } + + public bool ConfirmPressedThisFrame { get; private set; } + + public bool ToggleBattlePressedThisFrame { get; private set; } + + private void Update() + { + Move = new Vector2(Input.GetAxisRaw(_horizontalAxis), Input.GetAxisRaw(_verticalAxis)); + ConfirmPressedThisFrame = Input.GetKeyDown(_confirmKey); + ToggleBattlePressedThisFrame = Input.GetKeyDown(_toggleBattleKey); + } + + private void LateUpdate() + { + ConfirmPressedThisFrame = false; + ToggleBattlePressedThisFrame = false; + } + + protected override void OnServiceDestroy() + { + Move = Vector2.zero; + ConfirmPressedThisFrame = false; + ToggleBattlePressedThisFrame = false; + } + } +} diff --git a/Client/Assets/Scripts/New/Example/Scene/ExampleInputManager.cs.meta b/Client/Assets/Scripts/New/Example/Scene/ExampleInputManager.cs.meta new file mode 100644 index 0000000..5788374 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Scene/ExampleInputManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6a3327a01fceb94ba49f5dec2888d6b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/Scene/IInputService.cs b/Client/Assets/Scripts/New/Example/Scene/IInputService.cs new file mode 100644 index 0000000..3ef0dd8 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Scene/IInputService.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +namespace Aliciza.Services.Example +{ + public interface IInputService : IService + { + Vector2 Move { get; } + + bool ConfirmPressedThisFrame { get; } + + bool ToggleBattlePressedThisFrame { get; } + } +} diff --git a/Client/Assets/Scripts/New/Example/Scene/IInputService.cs.meta b/Client/Assets/Scripts/New/Example/Scene/IInputService.cs.meta new file mode 100644 index 0000000..e8a0964 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Scene/IInputService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2ef833d119cbe7c43bd3d5e61dae31a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/Example/Scene/SceneScope.cs b/Client/Assets/Scripts/New/Example/Scene/SceneScope.cs new file mode 100644 index 0000000..7b3487c --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Scene/SceneScope.cs @@ -0,0 +1,7 @@ +namespace Aliciza.Services.Example +{ + /// + /// 示例场景 Scope 标记类。 + /// + public sealed class SceneScope { } +} diff --git a/Client/Assets/Scripts/New/Example/Scene/SceneScope.cs.meta b/Client/Assets/Scripts/New/Example/Scene/SceneScope.cs.meta new file mode 100644 index 0000000..6f70e99 --- /dev/null +++ b/Client/Assets/Scripts/New/Example/Scene/SceneScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e109d3faffc69294b90a5ca646ce2f69 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework.meta b/Client/Assets/Scripts/New/ServiceFramework.meta new file mode 100644 index 0000000..d49268b --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7907eae3ee5d54e438b951d07879f538 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core.meta b/Client/Assets/Scripts/New/ServiceFramework/Core.meta new file mode 100644 index 0000000..3b38e5f --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 48e89f2eceb320547b4986db840128da +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/AppScope.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/AppScope.cs new file mode 100644 index 0000000..736999f --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/AppScope.cs @@ -0,0 +1,7 @@ +namespace Aliciza.Services +{ + /// + /// 框架内置的 App Scope 标记类,生命周期与 ServiceWorld 相同。 + /// + public sealed class AppScope { } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/AppScope.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/AppScope.cs.meta new file mode 100644 index 0000000..e95ef57 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/AppScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d205821b5dae0264fb6c5d1ea78f9b43 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/AppServices.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/AppServices.cs new file mode 100644 index 0000000..4e21874 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/AppServices.cs @@ -0,0 +1,70 @@ +using System; + +namespace Aliciza.Services +{ + public static class GameServices + { + private static ServiceWorld _world; + + public static bool HasWorld => _world != null; + + public static ServiceWorld World + { + get + { + if (_world == null) + throw new InvalidOperationException("ServiceWorld has not been created yet."); + return _world; + } + } + + public static ServiceScope App => World.AppScope; + + public static ServiceWorld EnsureWorld(int appScopeOrder = -10000) + { + if (_world == null) + _world = new ServiceWorld(appScopeOrder); + return _world; + } + + // ── Scope 管理 ────────────────────────────────────────────────────────── + + public static ServiceScope CreateScope(int order = 0) where TScope : class + => World.CreateScope(order); + + public static ServiceScope GetOrCreateScope(int order = 0) where TScope : class + => World.GetOrCreateScope(order); + + public static bool TryGetScope(out ServiceScope scope) where TScope : class + { + if (_world == null) { scope = null; return false; } + return _world.TryGetScope(out scope); + } + + public static bool DestroyScope() where TScope : class + { + if (_world == null) return false; + return _world.DestroyScope(); + } + + // ── Service 查找 ──────────────────────────────────────────────────────── + + public static bool TryGet(out T service) where T : class, IService + { + if (_world == null) { service = null; return false; } + return _world.TryGet(out service); + } + + public static T Require() where T : class, IService + => World.Require(); + + // ── 生命周期 ──────────────────────────────────────────────────────────── + + public static void Shutdown() + { + if (_world == null) return; + _world.Dispose(); + _world = null; + } + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/AppServices.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/AppServices.cs.meta new file mode 100644 index 0000000..5bcacd8 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/AppServices.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7186431b5e61b3c4f9865bd9901ce831 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/IMonoService.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/IMonoService.cs new file mode 100644 index 0000000..64141d3 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/IMonoService.cs @@ -0,0 +1,8 @@ +namespace Aliciza.Services +{ + /// + /// 标记一个 IService 实现来自 MonoBehaviour。 + /// ServiceScope 通过此接口识别 Mono 服务,避免 Core 层依赖 UnityEngine。 + /// + public interface IMonoService : IService { } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/IMonoService.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/IMonoService.cs.meta new file mode 100644 index 0000000..381e576 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/IMonoService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 64c7431bc2d38d24594f92006ac398d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/IService.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/IService.cs new file mode 100644 index 0000000..fe55199 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/IService.cs @@ -0,0 +1,8 @@ +namespace Aliciza.Services +{ + public interface IService + { + void Initialize(ServiceContext context); + void Destroy(); + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/IService.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/IService.cs.meta new file mode 100644 index 0000000..e83f7ee --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/IService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1cab09453acbb93498bd781202713656 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceOrder.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceOrder.cs new file mode 100644 index 0000000..e1abd59 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceOrder.cs @@ -0,0 +1,7 @@ +namespace Aliciza.Services +{ + public interface IServiceOrder + { + int Order { get; } + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceOrder.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceOrder.cs.meta new file mode 100644 index 0000000..ac24793 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceOrder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f66ea6b1b8a144b4184b2b078305f8df +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceTicks.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceTicks.cs new file mode 100644 index 0000000..3324475 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceTicks.cs @@ -0,0 +1,22 @@ +namespace Aliciza.Services +{ + public interface IServiceTickable + { + void Tick(float deltaTime); + } + + public interface IServiceLateTickable + { + void LateTick(float deltaTime); + } + + public interface IServiceFixedTickable + { + void FixedTick(float fixedDeltaTime); + } + + public interface IServiceGizmoDrawable + { + void DrawGizmos(); + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceTicks.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceTicks.cs.meta new file mode 100644 index 0000000..69a99d4 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/IServiceTicks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a7ff388c5465f4c439bf85bb76c2d786 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ReferenceComparer.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/ReferenceComparer.cs new file mode 100644 index 0000000..3224ae3 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ReferenceComparer.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Aliciza.Services +{ + internal sealed class ReferenceComparer : IEqualityComparer + where T : class + { + public static readonly ReferenceComparer Instance = new ReferenceComparer(); + + private ReferenceComparer() { } + + public bool Equals(T x, T y) => ReferenceEquals(x, y); + + public int GetHashCode(T obj) + => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ReferenceComparer.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/ReferenceComparer.cs.meta new file mode 100644 index 0000000..b457acc --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ReferenceComparer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3afc1438ca0eba4892ac93b1165d60d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceBase.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceBase.cs new file mode 100644 index 0000000..dbd36a2 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceBase.cs @@ -0,0 +1,36 @@ +namespace Aliciza.Services +{ + public abstract class ServiceBase : IService + { + public ServiceContext Context { get; private set; } + + public bool IsInitialized { get; private set; } + + protected ServiceWorld World => Context.World; + + protected ServiceScope Scope => Context.Scope; + + void IService.Initialize(ServiceContext context) + { + if (IsInitialized) + throw new System.InvalidOperationException($"{GetType().FullName} is already initialized."); + + Context = context; + IsInitialized = true; + OnInitialize(); + } + + void IService.Destroy() + { + if (!IsInitialized) return; + + OnDestroyService(); + IsInitialized = false; + Context = default; + } + + protected abstract void OnInitialize(); + + protected abstract void OnDestroyService(); + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceBase.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceBase.cs.meta new file mode 100644 index 0000000..d227b38 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af65a59b2e4b11b419fc09505ecd1c88 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContext.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContext.cs new file mode 100644 index 0000000..95b1874 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContext.cs @@ -0,0 +1,32 @@ +namespace Aliciza.Services +{ + public readonly struct ServiceContext + { + public ServiceContext(ServiceWorld world, ServiceScope scope) + { + World = world; + Scope = scope; + } + + public ServiceWorld World { get; } + + public ServiceScope Scope { get; } + + public ServiceScope AppScope => World.AppScope; + + public T Require() where T : class, IService + => World.Require(Scope); + + public bool TryGet(out T service) where T : class, IService + => World.TryGet(Scope, out service); + + public ServiceScope CreateScope(int order = 0) where TScope : class + => World.CreateScope(order); + + public ServiceScope GetOrCreateScope(int order = 0) where TScope : class + => World.GetOrCreateScope(order); + + public bool TryGetScope(out ServiceScope scope) where TScope : class + => World.TryGetScope(out scope); + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContext.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContext.cs.meta new file mode 100644 index 0000000..ce8559b --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd30fae6aee72a04db7507d194215b40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContractUtility.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContractUtility.cs new file mode 100644 index 0000000..b73a780 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContractUtility.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; + +namespace Aliciza.Services +{ + internal static class ServiceContractUtility + { + private static readonly HashSet ExcludedContracts = new HashSet + { + typeof(IService), + typeof(IServiceTickable), + typeof(IServiceLateTickable), + typeof(IServiceFixedTickable), + typeof(IServiceGizmoDrawable), + typeof(IServiceOrder), + }; + + public static List Collect(Type serviceType, IReadOnlyList extraContracts) + { + var contracts = new List { serviceType }; + var unique = new HashSet { serviceType }; + + var interfaces = serviceType.GetInterfaces(); + for (var i = 0; i < interfaces.Length; i++) + { + var contract = interfaces[i]; + if (!typeof(IService).IsAssignableFrom(contract)) continue; + if (ExcludedContracts.Contains(contract)) continue; + if (unique.Add(contract)) contracts.Add(contract); + } + + if (extraContracts == null) return contracts; + + for (var i = 0; i < extraContracts.Count; i++) + { + var extraContract = extraContracts[i]; + ValidateExtraContract(serviceType, extraContract); + if (unique.Add(extraContract)) contracts.Add(extraContract); + } + + return contracts; + } + + private static void ValidateExtraContract(Type serviceType, Type contract) + { + if (contract == null) + throw new ArgumentNullException(nameof(contract)); + + if (!typeof(IService).IsAssignableFrom(contract)) + throw new InvalidOperationException($"{contract.FullName} must inherit {nameof(IService)}."); + + if (!contract.IsAssignableFrom(serviceType)) + throw new InvalidOperationException($"{serviceType.FullName} does not implement {contract.FullName}."); + + if (ExcludedContracts.Contains(contract)) + throw new InvalidOperationException($"{contract.FullName} cannot be used as a service contract."); + } + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContractUtility.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContractUtility.cs.meta new file mode 100644 index 0000000..536cdd5 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceContractUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95c468fca1e70ab4b931d26d98af3fd9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceScope.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceScope.cs new file mode 100644 index 0000000..6aed2c3 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceScope.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; + +namespace Aliciza.Services +{ + public sealed class ServiceScope : IDisposable + { + private readonly Dictionary _servicesByContract = new Dictionary(); + private readonly Dictionary> _contractsByService = new Dictionary>(ReferenceComparer.Instance); + private readonly List _registrationOrder = new List(); + + private readonly List _tickables = new List(); + private readonly List _lateTickables = new List(); + private readonly List _fixedTickables = new List(); + private readonly List _gizmoDrawables = new List(); + + private IServiceTickable[] _tickableSnapshot = Array.Empty(); + private IServiceLateTickable[] _lateTickableSnapshot = Array.Empty(); + private IServiceFixedTickable[] _fixedTickableSnapshot = Array.Empty(); + private IServiceGizmoDrawable[] _gizmoSnapshot = Array.Empty(); + + private bool _tickablesDirty; + private bool _lateTickablesDirty; + private bool _fixedTickablesDirty; + private bool _gizmoDrawablesDirty; + + internal ServiceScope(ServiceWorld world, string name, int order) + { + World = world ?? throw new ArgumentNullException(nameof(world)); + Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentException("Scope name cannot be empty.", nameof(name)) : name; + Order = order; + } + + public ServiceWorld World { get; } + + public string Name { get; } + + public int Order { get; } + + public bool IsDisposed { get; private set; } + + public T Register(T service, params Type[] extraContracts) + where T : class, IService + { + EnsureNotDisposed(); + + if (service == null) + throw new ArgumentNullException(nameof(service)); + + ValidateService(service); + + if (_contractsByService.ContainsKey(service)) + throw new InvalidOperationException($"Service {service.GetType().FullName} is already registered in scope {Name}."); + + var contracts = ServiceContractUtility.Collect(service.GetType(), extraContracts); + for (var i = 0; i < contracts.Count; i++) + { + var contract = contracts[i]; + if (_servicesByContract.TryGetValue(contract, out var existing)) + { + throw new InvalidOperationException( + $"Scope {Name} already contains contract {contract.FullName} bound to {existing.GetType().FullName}."); + } + } + + _contractsByService.Add(service, contracts); + _registrationOrder.Add(service); + for (var i = 0; i < contracts.Count; i++) + _servicesByContract.Add(contracts[i], service); + + try + { + service.Initialize(new ServiceContext(World, this)); + AddToLifecycleLists(service); + } + catch + { + RemoveBindings(service); + throw; + } + + return service; + } + + public bool Unregister() where T : class, IService + { + if (!_servicesByContract.TryGetValue(typeof(T), out var service)) + return false; + return Unregister(service); + } + + public bool Unregister(IService service) + { + if (service == null || !_contractsByService.ContainsKey(service)) + return false; + + RemoveFromLifecycleLists(service); + RemoveBindings(service); + service.Destroy(); + return true; + } + + public bool TryGet(out T service) where T : class, IService + { + if (_servicesByContract.TryGetValue(typeof(T), out var raw)) + { + service = raw as T; + return service != null; + } + service = null; + return false; + } + + public T Require() where T : class, IService + { + if (TryGet(out T service)) return service; + throw new InvalidOperationException($"Scope {Name} does not contain service {typeof(T).FullName}."); + } + + public bool HasContract(Type contractType) + => _servicesByContract.ContainsKey(contractType); + + internal void Tick(float deltaTime) + { + var snapshot = GetTickSnapshot(); + for (var i = 0; i < snapshot.Length; i++) snapshot[i].Tick(deltaTime); + } + + internal void LateTick(float deltaTime) + { + var snapshot = GetLateTickSnapshot(); + for (var i = 0; i < snapshot.Length; i++) snapshot[i].LateTick(deltaTime); + } + + internal void FixedTick(float fixedDeltaTime) + { + var snapshot = GetFixedTickSnapshot(); + for (var i = 0; i < snapshot.Length; i++) snapshot[i].FixedTick(fixedDeltaTime); + } + + internal void DrawGizmos() + { + var snapshot = GetGizmoSnapshot(); + for (var i = 0; i < snapshot.Length; i++) snapshot[i].DrawGizmos(); + } + + public void Dispose() + { + if (IsDisposed) return; + + var snapshot = _registrationOrder.ToArray(); + for (var i = snapshot.Length - 1; i >= 0; i--) + { + var service = snapshot[i]; + if (!_contractsByService.ContainsKey(service)) continue; + RemoveFromLifecycleLists(service); + RemoveBindings(service); + service.Destroy(); + } + + IsDisposed = true; + } + + private void EnsureNotDisposed() + { + if (IsDisposed) throw new ObjectDisposedException(Name); + } + + private static void ValidateService(IService service) + { + if (service is IMonoService && + (service is IServiceTickable || + service is IServiceLateTickable || + service is IServiceFixedTickable || + service is IServiceGizmoDrawable)) + { + throw new InvalidOperationException( + $"Mono service {service.GetType().FullName} cannot implement tick lifecycle interfaces."); + } + } + + private void AddToLifecycleLists(IService service) + { + if (service is IServiceTickable tickable) { _tickables.Add(tickable); _tickablesDirty = true; } + if (service is IServiceLateTickable late) { _lateTickables.Add(late); _lateTickablesDirty = true; } + if (service is IServiceFixedTickable fixed_) { _fixedTickables.Add(fixed_); _fixedTickablesDirty = true; } + if (service is IServiceGizmoDrawable gizmo) { _gizmoDrawables.Add(gizmo); _gizmoDrawablesDirty = true; } + } + + private void RemoveFromLifecycleLists(IService service) + { + if (service is IServiceTickable tickable && _tickables.Remove(tickable)) _tickablesDirty = true; + if (service is IServiceLateTickable late && _lateTickables.Remove(late)) _lateTickablesDirty = true; + if (service is IServiceFixedTickable fixed_ && _fixedTickables.Remove(fixed_)) _fixedTickablesDirty = true; + if (service is IServiceGizmoDrawable gizmo && _gizmoDrawables.Remove(gizmo)) _gizmoDrawablesDirty = true; + } + + private void RemoveBindings(IService service) + { + if (_contractsByService.TryGetValue(service, out var contracts)) + { + for (var i = 0; i < contracts.Count; i++) + _servicesByContract.Remove(contracts[i]); + } + _contractsByService.Remove(service); + _registrationOrder.Remove(service); + } + + private IServiceTickable[] GetTickSnapshot() + { + if (_tickablesDirty) + { + _tickables.Sort(CompareByOrder); + _tickableSnapshot = _tickables.Count > 0 ? _tickables.ToArray() : Array.Empty(); + _tickablesDirty = false; + } + return _tickableSnapshot; + } + + private IServiceLateTickable[] GetLateTickSnapshot() + { + if (_lateTickablesDirty) + { + _lateTickables.Sort(CompareByOrder); + _lateTickableSnapshot = _lateTickables.Count > 0 ? _lateTickables.ToArray() : Array.Empty(); + _lateTickablesDirty = false; + } + return _lateTickableSnapshot; + } + + private IServiceFixedTickable[] GetFixedTickSnapshot() + { + if (_fixedTickablesDirty) + { + _fixedTickables.Sort(CompareByOrder); + _fixedTickableSnapshot = _fixedTickables.Count > 0 ? _fixedTickables.ToArray() : Array.Empty(); + _fixedTickablesDirty = false; + } + return _fixedTickableSnapshot; + } + + private IServiceGizmoDrawable[] GetGizmoSnapshot() + { + if (_gizmoDrawablesDirty) + { + _gizmoDrawables.Sort(CompareByOrder); + _gizmoSnapshot = _gizmoDrawables.Count > 0 ? _gizmoDrawables.ToArray() : Array.Empty(); + _gizmoDrawablesDirty = false; + } + return _gizmoSnapshot; + } + + private static int CompareByOrder(T a, T b) + { + var left = a is IServiceOrder oa ? oa.Order : 0; + var right = b is IServiceOrder ob ? ob.Order : 0; + return left.CompareTo(right); + } + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceScope.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceScope.cs.meta new file mode 100644 index 0000000..d82248e --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceScope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90a7d784a3aff2f4f832fa67901e6931 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceWorld.cs b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceWorld.cs new file mode 100644 index 0000000..091fd03 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceWorld.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; + +namespace Aliciza.Services +{ + public sealed class ServiceWorld : IDisposable + { + private readonly List _scopes = new List(); + private readonly Dictionary _scopesByType = new Dictionary(); + + private ServiceScope[] _scopeSnapshot = Array.Empty(); + private bool _scopesDirty; + + public ServiceWorld(int appScopeOrder = -10000) + { + AppScope = CreateScopeInternal(typeof(AppScope), appScopeOrder); + } + + public ServiceScope AppScope { get; } + + // ── Scope 管理(Type-based) ──────────────────────────────────────────── + + public ServiceScope CreateScope(int order = 0) where TScope : class + { + var type = typeof(TScope); + if (_scopesByType.ContainsKey(type)) + throw new InvalidOperationException($"Scope {type.Name} already exists."); + return CreateScopeInternal(type, order); + } + + public ServiceScope GetOrCreateScope(int order = 0) where TScope : class + { + var type = typeof(TScope); + if (_scopesByType.TryGetValue(type, out var existing)) + return existing; + return CreateScopeInternal(type, order); + } + + public bool TryGetScope(out ServiceScope scope) where TScope : class + => _scopesByType.TryGetValue(typeof(TScope), out scope); + + public ServiceScope GetScope() where TScope : class + { + if (TryGetScope(out var scope)) return scope; + throw new InvalidOperationException($"Scope {typeof(TScope).Name} does not exist."); + } + + public bool DestroyScope() where TScope : class + { + if (typeof(TScope) == typeof(AppScope)) + throw new InvalidOperationException("AppScope can only be destroyed when the world is disposed."); + + var type = typeof(TScope); + if (!_scopesByType.TryGetValue(type, out var scope)) + return false; + + _scopesByType.Remove(type); + _scopes.Remove(scope); + _scopesDirty = true; + scope.Dispose(); + return true; + } + + // ── Service 查找 ──────────────────────────────────────────────────────── + + public bool TryGet(out T service) where T : class, IService + => TryGet(null, out service); + + public bool TryGet(ServiceScope preferredScope, out T service) where T : class, IService + { + if (preferredScope != null && !preferredScope.IsDisposed && preferredScope.TryGet(out service)) + return true; + + var snapshot = GetScopeSnapshot(); + for (var i = snapshot.Length - 1; i >= 0; i--) + { + var scope = snapshot[i]; + if (ReferenceEquals(scope, preferredScope)) continue; + if (scope.TryGet(out service)) return true; + } + + service = null; + return false; + } + + public T Require() where T : class, IService => Require(null); + + public T Require(ServiceScope preferredScope) where T : class, IService + { + if (TryGet(preferredScope, out T service)) return service; + throw new InvalidOperationException($"Service {typeof(T).FullName} was not found in any active scope."); + } + + // ── Tick ──────────────────────────────────────────────────────────────── + + public void Tick(float deltaTime) + { + var snapshot = GetScopeSnapshot(); + for (var i = 0; i < snapshot.Length; i++) snapshot[i].Tick(deltaTime); + } + + public void LateTick(float deltaTime) + { + var snapshot = GetScopeSnapshot(); + for (var i = 0; i < snapshot.Length; i++) snapshot[i].LateTick(deltaTime); + } + + public void FixedTick(float fixedDeltaTime) + { + var snapshot = GetScopeSnapshot(); + for (var i = 0; i < snapshot.Length; i++) snapshot[i].FixedTick(fixedDeltaTime); + } + + public void DrawGizmos() + { + var snapshot = GetScopeSnapshot(); + for (var i = 0; i < snapshot.Length; i++) snapshot[i].DrawGizmos(); + } + + // ── Dispose ───────────────────────────────────────────────────────────── + + public void Dispose() + { + var snapshot = _scopes.ToArray(); + for (var i = snapshot.Length - 1; i >= 0; i--) + snapshot[i].Dispose(); + _scopes.Clear(); + _scopesByType.Clear(); + _scopeSnapshot = Array.Empty(); + } + + // ── 内部 ──────────────────────────────────────────────────────────────── + + private ServiceScope CreateScopeInternal(Type scopeType, int order) + { + var scope = new ServiceScope(this, scopeType.Name, order); + _scopes.Add(scope); + _scopesByType.Add(scopeType, scope); + _scopes.Sort(CompareScopeOrder); + _scopesDirty = true; + return scope; + } + + private ServiceScope[] GetScopeSnapshot() + { + if (_scopesDirty) + { + _scopeSnapshot = _scopes.Count > 0 ? _scopes.ToArray() : Array.Empty(); + _scopesDirty = false; + } + return _scopeSnapshot; + } + + private static int CompareScopeOrder(ServiceScope left, ServiceScope right) + => left.Order.CompareTo(right.Order); + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceWorld.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceWorld.cs.meta new file mode 100644 index 0000000..74c2ef3 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Core/ServiceWorld.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d48a8032734a55647a565ee72471c5c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Unity.meta b/Client/Assets/Scripts/New/ServiceFramework/Unity.meta new file mode 100644 index 0000000..b0c44f0 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c69106e6f9633554ea75fbc122821e2a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Unity/GameServiceRoot.cs b/Client/Assets/Scripts/New/ServiceFramework/Unity/GameServiceRoot.cs new file mode 100644 index 0000000..45e4945 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Unity/GameServiceRoot.cs @@ -0,0 +1,68 @@ +using UnityEngine; + +namespace Aliciza.Services +{ + [DefaultExecutionOrder(-32000)] + [DisallowMultipleComponent] + public class GameServiceRoot : MonoBehaviour + { + private static GameServiceRoot s_activeRoot; + + [SerializeField] private bool _dontDestroyOnLoad = true; + [SerializeField] private int _appScopeOrder = -10000; + + private bool _ownsWorld; + + protected virtual void Awake() + { + if (s_activeRoot != null && s_activeRoot != this) + { + enabled = false; + return; + } + + s_activeRoot = this; + + if (_dontDestroyOnLoad) + DontDestroyOnLoad(gameObject); + + var createdWorld = !GameServices.HasWorld; + var world = GameServices.EnsureWorld(_appScopeOrder); + _ownsWorld = createdWorld; + + if (createdWorld) + RegisterAppServices(world.AppScope); + } + + protected virtual void Update() + { + if (GameServices.HasWorld) GameServices.World.Tick(Time.deltaTime); + } + + protected virtual void LateUpdate() + { + if (GameServices.HasWorld) GameServices.World.LateTick(Time.deltaTime); + } + + protected virtual void FixedUpdate() + { + if (GameServices.HasWorld) GameServices.World.FixedTick(Time.fixedDeltaTime); + } + + protected virtual void OnDrawGizmos() + { + if (GameServices.HasWorld) GameServices.World.DrawGizmos(); + } + + protected virtual void OnDestroy() + { + if (s_activeRoot == this) + s_activeRoot = null; + + if (_ownsWorld && GameServices.HasWorld) + GameServices.Shutdown(); + } + + protected virtual void RegisterAppServices(ServiceScope appScope) { } + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Unity/GameServiceRoot.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Unity/GameServiceRoot.cs.meta new file mode 100644 index 0000000..936e56f --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Unity/GameServiceRoot.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f1e04080395e4e649a43e98b673c5911 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/ServiceFramework/Unity/MonoServiceBehaviour.cs b/Client/Assets/Scripts/New/ServiceFramework/Unity/MonoServiceBehaviour.cs new file mode 100644 index 0000000..330670d --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Unity/MonoServiceBehaviour.cs @@ -0,0 +1,99 @@ +using UnityEngine; + +namespace Aliciza.Services +{ + /// + /// Mono 服务基类(不自动注册,适合需要手动控制注册时机的场景)。 + /// + public abstract class MonoServiceBehaviour : MonoBehaviour, IMonoService + { + public ServiceContext Context { get; private set; } + + public bool IsInitialized { get; private set; } + + protected ServiceWorld World => Context.World; + + protected ServiceScope Scope => Context.Scope; + + void IService.Initialize(ServiceContext context) + { + if (IsInitialized) + throw new System.InvalidOperationException($"{GetType().FullName} is already initialized."); + + Context = context; + IsInitialized = true; + OnServiceInitialize(); + } + + void IService.Destroy() + { + if (!IsInitialized) return; + + OnServiceDestroy(); + IsInitialized = false; + Context = default; + } + + protected virtual void OnServiceInitialize() { } + + protected virtual void OnServiceDestroy() { } + } + + /// + /// Mono 服务基类(自动注册到 )。 + /// + /// 场景服务:_dontDestroyOnLoad = false(默认),销毁时自动注销。
+ /// 跨场景服务:_dontDestroyOnLoad = true,首个实例持久化并注册; + /// 后续场景中出现的重复实例自动销毁自身。 + ///
+ /// + /// 子类通过 执行额外的 Awake 逻辑, + /// 通过 执行注册后的初始化, + /// 通过 执行注销前的清理。 + /// + ///
+ public abstract class MonoServiceBehaviour : MonoServiceBehaviour + where TScope : class + { + [SerializeField] private bool _dontDestroyOnLoad = false; + + // 注意:使用 Start 而非 Awake 注册,确保 GameServiceRoot.Awake(创建 World)必然先于此执行。 + // DefaultExecutionOrder 会影响所有生命周期(含 Awake),用 Start 可彻底规避执行顺序陷阱。 + private void Awake() + { + if (_dontDestroyOnLoad) + DontDestroyOnLoad(gameObject); + + OnAwake(); + } + + private void Start() + { + var scope = GameServices.GetOrCreateScope(); + + // 跨场景重复实例检测:契约已被占用则销毁自身 + if (scope.HasContract(GetType())) + { + Destroy(gameObject); + return; + } + + scope.Register(this); + } + + private void OnDestroy() + { + if (!IsInitialized) return; + if (!GameServices.HasWorld) return; + if (!GameServices.TryGetScope(out var scope)) return; + + scope.Unregister(this); + } + + /// + /// 在 Awake 阶段执行(早于 Start 中的自动注册)。 + /// 适合缓存组件引用等不依赖服务系统的初始化。 + /// + protected virtual void OnAwake() { } + } +} diff --git a/Client/Assets/Scripts/New/ServiceFramework/Unity/MonoServiceBehaviour.cs.meta b/Client/Assets/Scripts/New/ServiceFramework/Unity/MonoServiceBehaviour.cs.meta new file mode 100644 index 0000000..a16bb63 --- /dev/null +++ b/Client/Assets/Scripts/New/ServiceFramework/Unity/MonoServiceBehaviour.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af63e3465b03eaf4ca3a82aebcab6e26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/New/readme.md b/Client/Assets/Scripts/New/readme.md new file mode 100644 index 0000000..36a41d1 --- /dev/null +++ b/Client/Assets/Scripts/New/readme.md @@ -0,0 +1,12 @@ +先不要分析我的代码,先对我提出的要求设计一个架构。 + +为了避免游戏逻辑业务繁琐 Manager 满天飞 到处都是Instance引用 Update顺序不可控提出以下设计。 + +游戏中会存在一开始就在场景里的Manager挂载GameObject上,因为这类Mono他需要对场景里的资源有引用比如InputManager,这类 +Manager生命周期由自己管理 +第二种Manager纯C#的形式存在 不依赖Mono 所有生命周期都有顶层管理,比如TimerManager 它会继承IService IService 也可以 +继承Initlize 和Destory 这是必须要继承的,也可以同时继承IServiceTickable 实现类似Update的功能,同理Late Fixed OnGizmo +等等。Mono类的接口 可以继承IService,但是不允许继承Tick 等等这种的重复生命周期的接口。 + +游戏内会存在比如一开始就要创建的纯C# Manager 也有启动游戏在登陆后或者进入大厅后才创建的Manager,也有一开始就放在场景 +里的Manager,也有进入战斗场景后才会创建的CameraManager和TimelineManager等等,禁止使用依赖注入。 diff --git a/Client/Assets/Scripts/New/readme.md.meta b/Client/Assets/Scripts/New/readme.md.meta new file mode 100644 index 0000000..75bf047 --- /dev/null +++ b/Client/Assets/Scripts/New/readme.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 48f5f075d7e396348956f3a822a97d56 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Packages/com.alicizax.unity.framework b/Client/Packages/com.alicizax.unity.framework index 76cc128..4a04654 160000 --- a/Client/Packages/com.alicizax.unity.framework +++ b/Client/Packages/com.alicizax.unity.framework @@ -1 +1 @@ -Subproject commit 76cc128c4f9744150c2af60d5b2b06fc20249059 +Subproject commit 4a0465450a0940f697978bdd7d5cf156117efb80 diff --git a/Client/UserSettings/Layouts/default-2022.dwlt b/Client/UserSettings/Layouts/default-2022.dwlt index 8f0598c..54b9957 100644 --- a/Client/UserSettings/Layouts/default-2022.dwlt +++ b/Client/UserSettings/Layouts/default-2022.dwlt @@ -19,7 +19,7 @@ MonoBehaviour: width: 1920 height: 997 m_ShowMode: 4 - m_Title: Project + m_Title: Inspector m_RootView: {fileID: 4} m_MinSize: {x: 875, y: 300} m_MaxSize: {x: 10000, y: 10000} @@ -41,7 +41,7 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 665 - width: 485 + width: 403 height: 282 m_MinSize: {x: 51, y: 71} m_MaxSize: {x: 4001, y: 4021} @@ -70,12 +70,12 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 485 + width: 403 height: 947 m_MinSize: {x: 100, y: 100} m_MaxSize: {x: 8096, y: 16192} vertical: 1 - controlID: 25 + controlID: 26 draggingID: 0 --- !u!114 &4 MonoBehaviour: @@ -174,7 +174,7 @@ MonoBehaviour: m_MinSize: {x: 400, y: 100} m_MaxSize: {x: 32384, y: 16192} vertical: 0 - controlID: 24 + controlID: 154 draggingID: 0 --- !u!114 &8 MonoBehaviour: @@ -193,7 +193,7 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 485 + width: 403 height: 665 m_MinSize: {x: 201, y: 221} m_MaxSize: {x: 4001, y: 4021} @@ -219,14 +219,14 @@ MonoBehaviour: - {fileID: 11} m_Position: serializedVersion: 2 - x: 485 + x: 403 y: 0 - width: 396 + width: 351 height: 947 m_MinSize: {x: 100, y: 100} m_MaxSize: {x: 8096, y: 16192} vertical: 1 - controlID: 70 + controlID: 71 draggingID: 0 --- !u!114 &10 MonoBehaviour: @@ -245,7 +245,7 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 396 + width: 351 height: 595 m_MinSize: {x: 200, y: 200} m_MaxSize: {x: 4000, y: 4000} @@ -271,7 +271,7 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 595 - width: 396 + width: 351 height: 352 m_MinSize: {x: 102, y: 121} m_MaxSize: {x: 4002, y: 4021} @@ -295,9 +295,9 @@ MonoBehaviour: m_Children: [] m_Position: serializedVersion: 2 - x: 881 + x: 754 y: 0 - width: 455 + width: 470 height: 947 m_MinSize: {x: 232, y: 271} m_MaxSize: {x: 10002, y: 10021} @@ -321,12 +321,12 @@ MonoBehaviour: m_Children: [] m_Position: serializedVersion: 2 - x: 1336 + x: 1224 y: 0 - width: 584 + width: 696 height: 947 - m_MinSize: {x: 275, y: 50} - m_MaxSize: {x: 4000, y: 4000} + m_MinSize: {x: 276, y: 71} + m_MaxSize: {x: 4001, y: 4021} m_ActualView: {fileID: 20} m_Panes: - {fileID: 20} @@ -354,7 +354,7 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 738 - width: 484 + width: 402 height: 261 m_SerializedDataModeController: m_DataMode: 0 @@ -408,23 +408,23 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 21 - width: 484 + width: 402 height: 240 - m_Scale: {x: 0.22222222, y: 0.22222222} - m_Translation: {x: 242, y: 120} + m_Scale: {x: 0.209375, y: 0.209375} + m_Translation: {x: 201, y: 120} m_MarginLeft: 0 m_MarginRight: 0 m_MarginTop: 0 m_MarginBottom: 0 m_LastShownAreaInsideMargins: serializedVersion: 2 - x: -1089 - y: -540 - width: 2178 - height: 1080 + x: -960 + y: -573.13434 + width: 1920 + height: 1146.2687 m_MinimalGUI: 1 - m_defaultScale: 0.22222222 - m_LastWindowPixelSize: {x: 484, y: 261} + m_defaultScale: 0.209375 + m_LastWindowPixelSize: {x: 402, y: 261} m_ClearInEditMode: 1 m_NoCameraWarning: 1 m_LowResolutionForAspectRatios: 01000000000000000000 @@ -522,7 +522,7 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 73 - width: 484 + width: 402 height: 644 m_SerializedDataModeController: m_DataMode: 0 @@ -1163,9 +1163,9 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 485 + x: 403 y: 73 - width: 394 + width: 349 height: 574 m_SerializedDataModeController: m_DataMode: 0 @@ -1180,23 +1180,23 @@ MonoBehaviour: m_SceneHierarchy: m_TreeViewState: scrollPos: {x: 0, y: 0} - m_SelectedIDs: 3a6d0000 + m_SelectedIDs: 142a0000 m_LastClickedID: 0 - m_ExpandedIDs: c6a8ffffc8a8ffffa2aaffff0efbffff10fbfffff4ffffffc8720000 + m_ExpandedIDs: 0efbffff10fbffff m_RenameOverlay: m_UserAcceptedRename: 0 - m_Name: Cube(Clone) - m_OriginalName: Cube(Clone) + m_Name: + m_OriginalName: m_EditFieldRect: serializedVersion: 2 x: 0 y: 0 width: 0 height: 0 - m_UserData: -24750 + m_UserData: 0 m_IsWaitingForDelay: 0 m_IsRenaming: 0 - m_OriginalEventType: 0 + m_OriginalEventType: 11 m_IsRenamingFilename: 0 m_ClientGUIView: {fileID: 10} m_SearchString: @@ -1226,9 +1226,9 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 485 + x: 403 y: 668 - width: 394 + width: 349 height: 331 m_SerializedDataModeController: m_DataMode: 0 @@ -1260,9 +1260,9 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 881 + x: 754 y: 73 - width: 453 + width: 468 height: 926 m_SerializedDataModeController: m_DataMode: 0 @@ -1285,7 +1285,7 @@ MonoBehaviour: m_SkipHidden: 0 m_SearchArea: 2 m_Folders: - - Packages/com.alicizax.unity.framework/Runtime/Localization + - Packages/com.alicizax.unity.framework/Runtime m_Globs: [] m_OriginalText: m_ImportLogFlags: 0 @@ -1301,7 +1301,7 @@ MonoBehaviour: scrollPos: {x: 0, y: 0} m_SelectedIDs: e48c0000 m_LastClickedID: 36068 - m_ExpandedIDs: 000000007e020000fe0c0000606a0000626a0000646a0000666a0000686a00006a6a00006c6a00006e6a0000706a0000726a0000746a0000766a0000786a00007a6a00007c6a00007e6a0000806a0000826a0000846a0000866a0000886a00008a6a00008c6a00008e6a0000906a0000926a0000946a0000966a0000986a00009a6a00009c6a00009e6a0000a06a0000a26a0000a46a0000a66a0000a86a0000aa6a0000ac6a0000ae6a0000b06a0000b26a0000b46a0000b66a0000b86a0000ba6a0000bc6a0000be6a0000c06a0000c26a0000c46a0000c66a0000c86a0000ca6a0000cc6a0000ce6a0000d06a0000d26a0000d46a0000d66a0000d86a0000da6a0000dc6a0000de6a0000e06a0000e26a0000e46a0000e66a0000e86a0000ea6a0000ec6a0000ee6a0000f06a0000f26a0000f46a0000f66a0000f86a0000 + m_ExpandedIDs: 000000007e020000060d0000a06a0000a26a0000a46a0000a66a0000a86a0000aa6a0000ac6a0000ae6a0000b06a0000b26a0000b46a0000b66a0000b86a0000ba6a0000bc6a0000be6a0000c06a0000c26a0000c46a0000c66a0000c86a0000ca6a0000cc6a0000ce6a0000d06a0000d26a0000d46a0000d66a0000d86a0000da6a0000dc6a0000de6a0000e06a0000e26a0000e46a0000e66a0000e86a0000ea6a0000ec6a0000ee6a0000f06a0000f26a0000f46a0000f66a0000f86a0000fa6a0000fc6a0000fe6a0000006b0000026b0000046b0000066b0000086b00000a6b00000c6b00000e6b0000106b0000126b0000146b0000166b0000186b00001a6b00001c6b00001e6b0000206b0000226b0000246b0000266b0000286b00002a6b00002c6b00002e6b0000306b0000326b0000346b0000366b0000386b0000 m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -1326,24 +1326,24 @@ MonoBehaviour: m_Icon: {fileID: 0} m_ResourceFile: m_AssetTreeState: - scrollPos: {x: 0, y: 580} + scrollPos: {x: 0, y: 768} m_SelectedIDs: m_LastClickedID: 0 - m_ExpandedIDs: ffffffff000000007e020000fe0c0000606a0000626a0000646a0000666a0000686a00006a6a00006c6a0000706a0000726a0000746a0000766a0000786a00007a6a00007c6a00007e6a0000806a0000826a0000846a0000866a0000886a00008a6a00008c6a00008e6a0000906a0000926a0000946a0000966a0000986a00009a6a00009c6a00009e6a0000a06a0000a26a0000a46a0000a66a0000a86a0000aa6a0000ac6a0000ae6a0000b06a0000b26a0000b46a0000b66a0000b86a0000ba6a0000bc6a0000c06a0000c26a0000c46a0000c66a0000c86a0000ca6a0000cc6a0000d06a0000d26a0000d46a0000d66a0000d86a0000da6a0000dc6a0000e66a0000e86a0000ea6a0000ec6a0000ee6a0000f06a0000f26a0000f66a0000f86a0000ee6c0000fc6c00003a6d0000267500002a750000367500007c77000090770000ffffff7f + m_ExpandedIDs: ffffffff000000007e020000060d0000a06a0000a26a0000a46a0000a66a0000a86a0000aa6a0000ac6a0000ae6a0000b06a0000b26a0000b46a0000b66a0000b86a0000ba6a0000bc6a0000be6a0000c06a0000c26a0000c46a0000c66a0000c86a0000ca6a0000cc6a0000ce6a0000d06a0000d26a0000d46a0000d66a0000d86a0000da6a0000dc6a0000de6a0000e06a0000e26a0000e46a0000e66a0000e86a0000ea6a0000ec6a0000ee6a0000f06a0000f26a0000f46a0000f66a0000f86a0000fa6a0000fc6a0000fe6a0000006b0000026b0000046b0000066b0000086b00000a6b00000c6b00000e6b0000106b0000126b0000146b0000166b0000186b00001a6b00001c6b00001e6b0000206b0000246b0000266b0000286b00002a6b00002c6b0000306b0000326b0000346b0000366b0000386b00008c6d0000aa6d0000c86d0000d46d0000d66d0000ffffff7f m_RenameOverlay: m_UserAcceptedRename: 0 - m_Name: ObjectPoolSystemDesign - m_OriginalName: ObjectPoolSystemDesign + m_Name: + m_OriginalName: m_EditFieldRect: serializedVersion: 2 x: 0 y: 0 width: 0 height: 0 - m_UserData: 11722 + m_UserData: 0 m_IsWaitingForDelay: 0 m_IsRenaming: 0 - m_OriginalEventType: 0 + m_OriginalEventType: 11 m_IsRenamingFilename: 1 m_ClientGUIView: {fileID: 12} m_SearchString: @@ -1405,9 +1405,9 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 1336 + x: 1224 y: 73 - width: 583 + width: 695 height: 926 m_SerializedDataModeController: m_DataMode: 0