diff --git a/.gitignore b/.gitignore index ee07a4d..f1f5e64 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,4 @@ crashlytics-build.properties /[Aa]ssets/[Ss]treamingAssets/aa/* /UI参考 - -/DisplayItemInfoForm_Summary.md -/UI_Design_Summary.md +/AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 6aab92b..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,44 +0,0 @@ -# Repository Guidelines - -## Project Structure & Module Organization -This is a Unity project (Unity 2022.3.62f3c1). Core game code and assets live under `Assets/`: -- `Assets/GameMain/` for game-specific scripts, scenes, and content. -- `Assets/GameFramework/` for shared framework code and editor tooling. -- `Assets/Plugins/` for third-party integrations (e.g., DOTween). -- `Assets/Resources/` and `Assets/StreamingAssets/` for runtime-loaded data. -- `Json/` and `数据表/` for data files and tables used by the game. -- `Tools/` for local utilities or pipelines. - -Avoid editing generated folders: `Library/`, `Temp/`, `Logs/`, `obj/`, `ProjectSettings/` (unless configuration changes are intentional). - -## Build, Test, and Development Commands -Primary workflow is through the Unity Editor: -- Open the project in Unity Hub, then press Play to run locally. -- Optional CLI launch: `Unity -projectPath .` (use your local Unity editor path). -- The solution file `VampireLike.sln` supports IDE navigation (Rider/VS/VS Code). - -## Coding Style & Naming Conventions -- C# style: 4-space indentation, braces on the same line, one public type per file. -- Match filename to main type (e.g., `PlayerController.cs` defines `PlayerController`). -- Use `PascalCase` for public types/members, `camelCase` for locals/parameters. -- Prefer `SerializeField` for private Unity fields that must be editable in the Inspector. - -## Testing Guidelines -`com.unity.test-framework` is included, but there is no dedicated `Assets/**/Tests` directory yet. When adding tests: -- Place under `Assets/Tests/` or `Assets//Tests/`. -- Name files `*Tests.cs` and use NUnit-style `[Test]` methods. -- Run via Unity Test Runner (Window > General > Test Runner). - -## Commit & Pull Request Guidelines -This repository does not include Git history, so no commit convention is enforced. Recommended default: -- Short, imperative subject (e.g., `Add enemy spawn tuning`). -- Include scope tags when helpful (e.g., `UI: Fix pause menu layout`). - -For pull requests, include: -- A concise summary and testing notes. -- Linked issues or tasks when applicable. -- Screenshots or short clips for UI/visual changes. - -## Configuration Tips -- Keep `Packages/manifest.json` and `ProjectSettings/` in sync when changing dependencies or project settings. -- Large binary assets should stay in `Assets/` with `.meta` files committed alongside. diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Player.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Player.cs index 3327b4a..d3cfb61 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/Player.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Player.cs @@ -93,6 +93,7 @@ namespace Entity set { if (value == _enable) return; + _enable = value; _movementComponent.SetMove(value); _backpackComponent.SetWeaponState(value); _inputComponent.SetListening(value); diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/TargetableObject.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/TargetableObject.cs index 27e2486..86f3e7f 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/TargetableObject.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/TargetableObject.cs @@ -6,12 +6,10 @@ //------------------------------------------------------------ using Components; -using Definition; using Definition.DataStruct; using Entity.EntityData; -using StarForce; +using Game.Utility; using UnityEngine; -using UnityGameFramework.Runtime; namespace Entity { diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife.cs index 1d1d7f9..12603dd 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife.cs @@ -6,7 +6,7 @@ using Definition.DataStruct; using Definition.Enum; using DG.Tweening; using Entity.EntityData; -using StarForce; +using Game.Utility; using UnityEngine; using UnityGameFramework.Runtime; diff --git a/Assets/GameMain/Scripts/Procedure/Game/ProcedureGame.cs b/Assets/GameMain/Scripts/Procedure/Game/ProcedureGame.cs index b152640..83c47ba 100644 --- a/Assets/GameMain/Scripts/Procedure/Game/ProcedureGame.cs +++ b/Assets/GameMain/Scripts/Procedure/Game/ProcedureGame.cs @@ -94,8 +94,6 @@ namespace Procedure GameEntry.Entity.ShowPlayer(_currentPlayerData); GameEntry.UIRouter.OpenUI(UIFormType.HudForm); - - InitGameState(); } protected override void OnUpdate(IFsm procedureOwner, float elapseSeconds, diff --git a/Assets/GameMain/Scripts/UI/Base/UIFormControllerCommonBase.cs b/Assets/GameMain/Scripts/UI/Base/UIFormControllerCommonBase.cs new file mode 100644 index 0000000..e6a4cf5 --- /dev/null +++ b/Assets/GameMain/Scripts/UI/Base/UIFormControllerCommonBase.cs @@ -0,0 +1,183 @@ +using Definition.Enum; +using GameFramework.Event; +using UnityGameFramework.Runtime; + +namespace UI +{ + public abstract class UIFormControllerCommonBase : UIFormControllerBase + where TContext : UIContext + where TForm : UGuiForm + { + private TContext _context; + private TForm _form; + private int? _formSerialId; + private bool _pendingRefresh; + private bool _isBindEvent; + + protected TContext Context => _context; + + protected TForm Form => _form; + + protected int? FormSerialId => _formSerialId; + + protected abstract UIFormType UIFormTypeId { get; } + + protected abstract void RefreshUI(TForm form, TContext context); + + protected virtual void SubscribeCustomEvents() + { + } + + protected virtual void UnsubscribeCustomEvents() + { + } + + protected virtual void CloseLoadedFormDirect(TForm form) + { + form.Close(); + } + + protected void SetContext(TContext context) + { + _context = context; + } + + protected void RefreshCurrentUI() + { + if (_context == null) + { + return; + } + + if (_form == null) + { + _pendingRefresh = true; + return; + } + + RefreshUI(_form, _context); + _pendingRefresh = false; + } + + protected override int? OpenUIInternal(TContext context) + { + if (context == null) + { + Log.Warning("{0}.OpenUI() context is null.", GetType().Name); + return null; + } + + _context = context; + + if (_form != null && _formSerialId.HasValue && GameEntry.UI.HasUIForm(_formSerialId.Value)) + { + RefreshUI(_form, _context); + return _formSerialId; + } + + CloseUI(); + _pendingRefresh = true; + SubscribeEvents(); + _formSerialId = GameEntry.UI.OpenUIForm(UIFormTypeId, _context); + return _formSerialId; + } + + public override void CloseUI() + { + _pendingRefresh = false; + UnsubscribeEvents(); + + if (_formSerialId.HasValue) + { + if (GameEntry.UI.HasUIForm(_formSerialId.Value)) + { + GameEntry.UI.CloseUIForm(_formSerialId.Value); + } + + _form = null; + _formSerialId = null; + return; + } + + if (_form != null) + { + CloseLoadedFormDirect(_form); + _form = null; + } + } + + private void SubscribeEvents() + { + if (_isBindEvent) + { + return; + } + + GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); + GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); + SubscribeCustomEvents(); + + _isBindEvent = true; + } + + private void UnsubscribeEvents() + { + if (!_isBindEvent) + { + return; + } + + GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); + GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); + UnsubscribeCustomEvents(); + + _isBindEvent = false; + } + + private void OpenUIFormSuccess(object sender, GameEventArgs e) + { + if (!(e is OpenUIFormSuccessEventArgs args)) + { + return; + } + + if (!_formSerialId.HasValue) + { + return; + } + + if (args.UIForm == null || args.UIForm.SerialId != _formSerialId.Value || args.UserData != _context) + { + return; + } + + _form = args.UIForm.Logic as TForm; + if (_form == null) + { + Log.Warning("{0} open success but form logic is invalid.", GetType().Name); + return; + } + + if (_pendingRefresh) + { + RefreshCurrentUI(); + } + } + + private void CloseUIFormComplete(object sender, GameEventArgs e) + { + if (!(e is CloseUIFormCompleteEventArgs args)) + { + return; + } + + if (args.SerialId != _formSerialId) + { + return; + } + + _form = null; + _formSerialId = null; + } + } +} diff --git a/Assets/GameMain/Scripts/UI/Base/UIFormControllerCommonBase.cs.meta b/Assets/GameMain/Scripts/UI/Base/UIFormControllerCommonBase.cs.meta new file mode 100644 index 0000000..5a25e39 --- /dev/null +++ b/Assets/GameMain/Scripts/UI/Base/UIFormControllerCommonBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 696c0314286f89c4082c2aa2eddaec2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/UI/Base/UIFormControllerTemplate.cs b/Assets/GameMain/Scripts/UI/Base/UIFormControllerTemplate.cs index 199ce15..9187364 100644 --- a/Assets/GameMain/Scripts/UI/Base/UIFormControllerTemplate.cs +++ b/Assets/GameMain/Scripts/UI/Base/UIFormControllerTemplate.cs @@ -1,60 +1,17 @@ using Definition.Enum; -using GameFramework.Event; using UnityGameFramework.Runtime; namespace UI { - public class TUIFormController : UIFormControllerBase + public class TUIFormController : UIFormControllerCommonBase { private IUIUseCase _useCase; - private UIContext _context; - private TUIForm _tForm; - private int? _tFormSerialId; - private bool _pendingRefresh; - private bool _isBindEvent; - private void SubscribeEvents() + protected override UIFormType UIFormTypeId => UIFormType.TUIForm; + + protected override void RefreshUI(TUIForm form, UIContext context) { - if (_isBindEvent) return; - - GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); - - _isBindEvent = true; - } - - private void UnsubscribeEvents() - { - if (!_isBindEvent) return; - - GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); - - _isBindEvent = false; - } - - protected override int? OpenUIInternal(UIContext context) - { - if (context == null) - { - Log.Warning("TUIFormController open failed. context is null."); - return null; - } - - _context = context; - - if (_tForm != null && _tFormSerialId.HasValue && - GameEntry.UI.HasUIForm(_tFormSerialId.Value)) - { - _tForm.RefreshUI(_context); - return _tFormSerialId; - } - - CloseUI(); - _pendingRefresh = true; - SubscribeEvents(); - _tFormSerialId = GameEntry.UI.OpenUIForm(UIFormType.TUIForm, context); - return _tFormSerialId; + form.RefreshUI(context); } public override int? OpenUI(object userData = null) @@ -70,110 +27,18 @@ namespace UI return null; } - return OpenUIInternal(_context); - } - - public override void CloseUI() - { - _pendingRefresh = false; - UnsubscribeEvents(); - - if (_tFormSerialId.HasValue) - { - if (GameEntry.UI.HasUIForm(_tFormSerialId.Value)) - { - GameEntry.UI.CloseUIForm(_tFormSerialId.Value); - } - - _tForm = null; - _tFormSerialId = null; - return; - } - - if (_tForm != null) - { - _tForm.Close(); - _tForm = null; - } + return OpenUIInternal(Context); } public override void BindUseCase(IUIUseCase useCase) { - if (!(useCase is IUIUseCase UIFormUseCase)) + if (!(useCase is IUIUseCase uiFormUseCase)) { Log.Error("LevelUpForm.BindUseCase() useCase is invalid."); return; } - _useCase = UIFormUseCase; + _useCase = uiFormUseCase; } - - private void TryRefreshUI() - { - if (_context == null) - { - return; - } - - if (_tForm == null) - { - _pendingRefresh = true; - return; - } - - _tForm.RefreshUI(_context); - _pendingRefresh = false; - } - - #region EventHanlders - - private void OpenUIFormSuccess(object sender, GameEventArgs e) - { - if (!(e is OpenUIFormSuccessEventArgs args)) - { - return; - } - - if (!_tFormSerialId.HasValue) - { - return; - } - - if (args.UIForm == null || args.UIForm.SerialId != _tFormSerialId.Value || args.UserData != _context) - { - return; - } - - _tForm = args.UIForm.Logic as TUIForm; - - if (_tForm == null) - { - Log.Warning("DialogFormController open success but form logic is invalid."); - return; - } - - if (_pendingRefresh) - { - TryRefreshUI(); - } - } - - private void CloseUIFormComplete(object sender, GameEventArgs e) - { - if (!(e is CloseUIFormCompleteEventArgs args)) - { - return; - } - - if (args.SerialId != _tFormSerialId) - { - return; - } - - _tForm = null; - _tFormSerialId = null; - } - - #endregion } } diff --git a/Assets/GameMain/Scripts/UI/CommonButton.cs b/Assets/GameMain/Scripts/UI/CommonButton.cs index f856a25..0f19e98 100644 --- a/Assets/GameMain/Scripts/UI/CommonButton.cs +++ b/Assets/GameMain/Scripts/UI/CommonButton.cs @@ -18,11 +18,11 @@ namespace UI private const float OnHoverAlpha = 0.7f; private const float OnClickAlpha = 0.6f; - [SerializeField] private UnityEvent _onPointerEnterAction = null; + [SerializeField] private UnityEvent _onPointerEnterAction = new(); - [SerializeField] private UnityEvent _onClickAction = null; + [SerializeField] private UnityEvent _onClickAction = new(); - [SerializeField] private UnityEvent _onPointerExitAction = null; + [SerializeField] private UnityEvent _onPointerExitAction = new(); [SerializeField] private bool _enableFade = true; diff --git a/Assets/GameMain/Scripts/UI/GameScene/Controller/DisplayItemInfoFormController.cs b/Assets/GameMain/Scripts/UI/GameScene/Controller/DisplayItemInfoFormController.cs index d023b6c..4d4ae51 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/Controller/DisplayItemInfoFormController.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/Controller/DisplayItemInfoFormController.cs @@ -1,39 +1,20 @@ using Definition.Enum; -using GameFramework.Event; using UnityGameFramework.Runtime; namespace UI { - public class DisplayItemInfoFormController : UIFormControllerBase + public class DisplayItemInfoFormController : UIFormControllerCommonBase { - private DisplayItemInfoFormContext _context; + protected override UIFormType UIFormTypeId => UIFormType.DisplayItemInfoForm; - private DisplayItemInfoForm _itemInfoForm; - - private int? _itemInfoFormSerialId; - - private bool _pendingRefresh; - - private bool _isBindEvent = false; - - private void SubscribeEvents() + protected override void RefreshUI(DisplayItemInfoForm form, DisplayItemInfoFormContext context) { - if (_isBindEvent) return; - - GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); - - _isBindEvent = true; + form.RefreshUI(context); } - private void UnsubscribeEvents() + protected override void CloseLoadedFormDirect(DisplayItemInfoForm form) { - if (!_isBindEvent) return; - - GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); - - _isBindEvent = false; + GameEntry.UI.CloseUIForm(form); } private static DisplayItemInfoFormContext BuildContext(DisplayItemInfoFormRawData rawData) @@ -56,32 +37,6 @@ namespace UI }; } - #region UI Methods - - protected override int? OpenUIInternal(DisplayItemInfoFormContext context) - { - if (context == null) - { - Log.Warning("ItemInfoFormController open failed. context is null."); - return null; - } - - _context = context; - - if (_itemInfoForm != null && _itemInfoFormSerialId.HasValue && - GameEntry.UI.HasUIForm(_itemInfoFormSerialId.Value)) - { - _itemInfoForm.RefreshUI(_context); - return _itemInfoFormSerialId; - } - - CloseUI(); - _pendingRefresh = true; - SubscribeEvents(); - _itemInfoFormSerialId = GameEntry.UI.OpenUIForm(UIFormType.DisplayItemInfoForm, context); - return _itemInfoFormSerialId; - } - public int? OpenUI(DisplayItemInfoFormRawData rawData) { DisplayItemInfoFormContext context = BuildContext(rawData); @@ -105,33 +60,8 @@ namespace UI Log.Warning("DisplayItemInfoFormController.OpenUI() userData type is invalid."); return null; } - - return OpenUIInternal(_context); - } - public override void CloseUI() - { - _pendingRefresh = false; - - UnsubscribeEvents(); - - if (_itemInfoFormSerialId.HasValue) - { - if (GameEntry.UI.HasUIForm(_itemInfoFormSerialId.Value)) - { - GameEntry.UI.CloseUIForm(_itemInfoFormSerialId.Value); - } - - _itemInfoForm = null; - _itemInfoFormSerialId = null; - return; - } - - if (_itemInfoForm != null) - { - GameEntry.UI.CloseUIForm(_itemInfoForm); - _itemInfoForm = null; - } + return OpenUIInternal(Context); } public override void BindUseCase(IUIUseCase useCase) @@ -139,78 +69,7 @@ namespace UI if (!(useCase is DisplayItemInfoFormUseCase)) { Log.Error("DisplayItemInfoForm.BindUseCase() useCase is invalid."); - return; } } - - private void TryRefreshUI() - { - if (_context == null) - { - return; - } - - if (_itemInfoForm == null) - { - _pendingRefresh = true; - return; - } - - _itemInfoForm.RefreshUI(_context); - _pendingRefresh = false; - } - - #endregion - - #region EventHanlders - - private void OpenUIFormSuccess(object sender, GameEventArgs e) - { - if (!(e is OpenUIFormSuccessEventArgs args)) - { - return; - } - - if (!_itemInfoFormSerialId.HasValue) - { - return; - } - - if (args.UIForm == null || args.UIForm.SerialId != _itemInfoFormSerialId.Value || args.UserData != _context) - { - return; - } - - _itemInfoForm = args.UIForm.Logic as DisplayItemInfoForm; - - if (_itemInfoForm == null) - { - Log.Warning("DisplayItemInfoFormController open success but form logic is invalid."); - return; - } - - if (_pendingRefresh) - { - TryRefreshUI(); - } - } - - private void CloseUIFormComplete(object sender, GameEventArgs e) - { - if (!(e is CloseUIFormCompleteEventArgs args)) - { - return; - } - - if (args.SerialId != _itemInfoFormSerialId) - { - return; - } - - _itemInfoForm = null; - _itemInfoFormSerialId = null; - } - - #endregion } } diff --git a/Assets/GameMain/Scripts/UI/GameScene/Controller/HudFormController.cs b/Assets/GameMain/Scripts/UI/GameScene/Controller/HudFormController.cs index a789a50..a746551 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/Controller/HudFormController.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/Controller/HudFormController.cs @@ -1,39 +1,15 @@ using Definition.Enum; -using GameFramework.Event; using UnityGameFramework.Runtime; namespace UI { - public class HudFormController : UIFormControllerBase + public class HudFormController : UIFormControllerCommonBase { - private HudFormContext _context; + protected override UIFormType UIFormTypeId => UIFormType.HudForm; - private HudForm _hudForm; - - private int? _hudFormSerialId; - - private bool _pendingRefresh; - - private bool _isBindEvent; - - private void SubscribeEvents() + protected override void RefreshUI(HudForm form, HudFormContext context) { - if (_isBindEvent) return; - - GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); - - _isBindEvent = true; - } - - private void UnsubscribeEvents() - { - if (!_isBindEvent) return; - - GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); - - _isBindEvent = false; + form.RefreshUI(context); } private static HudFormContext BuildHudFormContext() @@ -41,32 +17,6 @@ namespace UI return new HudFormContext(); } - #region UI Methods - - protected override int? OpenUIInternal(HudFormContext context) - { - if (context == null) - { - Log.Warning("HudFormController.OpenUI() context is null."); - return null; - } - - _context = context; - - if (_hudForm != null && _hudFormSerialId.HasValue && - GameEntry.UI.HasUIForm(_hudFormSerialId.Value)) - { - _hudForm.RefreshUI(_context); - return _hudFormSerialId; - } - - CloseUI(); - _pendingRefresh = true; - SubscribeEvents(); - _hudFormSerialId = GameEntry.UI.OpenUIForm(UIFormType.HudForm, context); - return _hudFormSerialId; - } - public override int? OpenUI(object userData = null) { if (userData is HudFormContext context) @@ -88,105 +38,9 @@ namespace UI return OpenUIInternal(context); } - public override void CloseUI() - { - _pendingRefresh = false; - UnsubscribeEvents(); - - if (_hudFormSerialId.HasValue) - { - if (GameEntry.UI.HasUIForm(_hudFormSerialId.Value)) - { - GameEntry.UI.CloseUIForm(_hudFormSerialId.Value); - } - - _hudForm = null; - _hudFormSerialId = null; - return; - } - - if (_hudForm != null) - { - _hudForm.Close(); - _hudForm = null; - } - } - public override void BindUseCase(IUIUseCase useCase) { Log.Info("HudFormController doesn't need UseCase"); } - - private void TryRefreshUI() - { - if (_context == null) - { - return; - } - - if (_hudForm == null) - { - _pendingRefresh = true; - return; - } - - _hudForm.RefreshUI(_context); - _pendingRefresh = false; - } - - #endregion - - - #region Event Handlers - - private void OpenUIFormSuccess(object sender, GameEventArgs e) - { - if (!(e is OpenUIFormSuccessEventArgs args)) - { - return; - } - - if (!_hudFormSerialId.HasValue) - { - return; - } - - if (args.UIForm == null || args.UIForm.SerialId != _hudFormSerialId.Value || - args.UserData != _context) - { - return; - } - - _hudForm = args.UIForm.Logic as HudForm; - - if (_hudForm == null) - { - Log.Warning("HudFormController open success but form logic is invalid."); - return; - } - - if (_pendingRefresh) - { - TryRefreshUI(); - } - } - - private void CloseUIFormComplete(object sender, GameEventArgs e) - { - if (!(e is CloseUIFormCompleteEventArgs args)) - { - return; - } - - if (args.SerialId != _hudFormSerialId) - { - return; - } - - _hudForm = null; - _hudFormSerialId = null; - } - - #endregion } } diff --git a/Assets/GameMain/Scripts/UI/GameScene/Controller/LevelUpFormController.cs b/Assets/GameMain/Scripts/UI/GameScene/Controller/LevelUpFormController.cs index c2e072b..4e1e82d 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/Controller/LevelUpFormController.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/Controller/LevelUpFormController.cs @@ -3,51 +3,40 @@ using CustomEvent; using Definition.Enum; using Game.Utility; using GameFramework.Event; -using Procedure; using UnityGameFramework.Runtime; namespace UI { - public class LevelUpFormController : UIFormControllerBase + public class LevelUpFormController : UIFormControllerCommonBase { private LevelUpFormUseCase _useCase; - private bool _pendingRefresh; + protected override UIFormType UIFormTypeId => UIFormType.LevelUpForm; - private int? _levelUpFormSerialId; - - private LevelUpForm _levelUpForm; - - private LevelUpFormContext _context; - - private bool _isBindEvent; - - private void SubscribeEvents() + protected override void RefreshUI(LevelUpForm form, LevelUpFormContext context) { - if (_isBindEvent) return; - - GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); - GameEntry.Event.Subscribe(RefreshEventArgs.EventId, OnRefresh); - GameEntry.Event.Subscribe(LevelUpPropSelectedEventArgs.EventId, OnLevelUpPropSelected); - - _isBindEvent = true; + form.RefreshUI(context); } - private void UnsubscribeEvents() + protected override void SubscribeCustomEvents() { - if (!_isBindEvent) return; + GameEntry.Event.Subscribe(RefreshEventArgs.EventId, OnRefresh); + GameEntry.Event.Subscribe(LevelUpPropSelectedEventArgs.EventId, OnLevelUpPropSelected); + } - GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); + protected override void UnsubscribeCustomEvents() + { GameEntry.Event.Unsubscribe(RefreshEventArgs.EventId, OnRefresh); GameEntry.Event.Unsubscribe(LevelUpPropSelectedEventArgs.EventId, OnLevelUpPropSelected); - - _isBindEvent = false; } private static LevelUpFormContext BuildContext(LevelUpFormRawData rawData) { + if (rawData == null || rawData.Rewards == null) + { + return null; + } + List props = new List(rawData.Rewards.Count); foreach (var reward in rawData.Rewards) { @@ -72,33 +61,6 @@ namespace UI }; } - - #region UI Methods - - protected override int? OpenUIInternal(LevelUpFormContext context) - { - if (context == null) - { - Log.Warning("LevelUpFormController.OpenUI() context is null."); - return null; - } - - _context = context; - - if (_levelUpForm != null && _levelUpFormSerialId.HasValue && - GameEntry.UI.HasUIForm(_levelUpFormSerialId.Value)) - { - _levelUpForm.RefreshUI(_context); - return _levelUpFormSerialId; - } - - CloseUI(); - _pendingRefresh = true; - SubscribeEvents(); - _levelUpFormSerialId = GameEntry.UI.OpenUIForm(UIFormType.LevelUpForm, context); - return _levelUpFormSerialId; - } - public override int? OpenUI(object userData = null) { if (userData is LevelUpFormContext context) @@ -133,47 +95,6 @@ namespace UI return OpenUIInternal(context); } - private void TryRefreshUI() - { - if (_context == null) - { - return; - } - - if (_levelUpForm == null) - { - _pendingRefresh = true; - return; - } - - _levelUpForm.RefreshUI(_context); - _pendingRefresh = false; - } - - public override void CloseUI() - { - _pendingRefresh = false; - UnsubscribeEvents(); - - if (_levelUpFormSerialId.HasValue) - { - if (GameEntry.UI.HasUIForm(_levelUpFormSerialId.Value)) - { - GameEntry.UI.CloseUIForm(_levelUpFormSerialId.Value); - } - - _levelUpForm = null; - _levelUpFormSerialId = null; - return; - } - - if (_levelUpForm != null) - { - _levelUpForm.Close(); - _levelUpForm = null; - } - } - public override void BindUseCase(IUIUseCase useCase) { if (!(useCase is LevelUpFormUseCase levelUpFormUseCase)) @@ -185,10 +106,6 @@ namespace UI _useCase = levelUpFormUseCase; } - #endregion - - #region Service - private void SelectReward(int selectedIndex) { if (_useCase == null) @@ -198,7 +115,6 @@ namespace UI } LevelUpFormRawData rawData = _useCase.SelectReward(selectedIndex); - if (rawData == null) { return; @@ -224,51 +140,6 @@ namespace UI OpenUI(rawData); } - #endregion - - #region Event Handlers - - private void OpenUIFormSuccess(object sender, GameEventArgs e) - { - if (!(e is OpenUIFormSuccessEventArgs args)) return; - - if (!_levelUpFormSerialId.HasValue) return; - - if (args.UIForm == null || args.UIForm.SerialId != _levelUpFormSerialId.Value || args.UserData != _context) - { - return; - } - - _levelUpForm = args.UIForm.Logic as LevelUpForm; - - if (_levelUpForm == null) - { - Log.Warning("LevelUpFormController open success but form logic is invalid."); - return; - } - - if (_pendingRefresh) - { - TryRefreshUI(); - } - } - - private void CloseUIFormComplete(object sender, GameEventArgs e) - { - if (!(e is CloseUIFormCompleteEventArgs args)) - { - return; - } - - if (args.SerialId != _levelUpFormSerialId) - { - return; - } - - _levelUpForm = null; - _levelUpFormSerialId = null; - } - private void OnRefresh(object sender, GameEventArgs e) { if (!(sender is LevelUpForm)) @@ -293,7 +164,5 @@ namespace UI SelectReward(args.SelectedId); } - - #endregion } } diff --git a/Assets/GameMain/Scripts/UI/GameScene/Controller/ShopFormController.cs b/Assets/GameMain/Scripts/UI/GameScene/Controller/ShopFormController.cs index c8f11a3..1e36996 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/Controller/ShopFormController.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/Controller/ShopFormController.cs @@ -9,50 +9,34 @@ using UnityGameFramework.Runtime; namespace UI { - public class ShopFormController : UIFormControllerBase + public class ShopFormController : UIFormControllerCommonBase { private ShopFormUseCase _useCase; - - private bool _pendingRefresh; - - private int? _shopFormSerialId; - - private ShopForm _shopForm; - private ShopFormRawData _rawData; - private ShopFormContext _context; + protected override UIFormType UIFormTypeId => UIFormType.ShopForm; - private bool _isBindEvent; - - private void SubscribeEvents() + protected override void RefreshUI(ShopForm form, ShopFormContext context) { - if (_isBindEvent) return; + form.RefreshUI(context); + } - GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); + protected override void SubscribeCustomEvents() + { GameEntry.Event.Subscribe(RefreshEventArgs.EventId, Refresh); GameEntry.Event.Subscribe(ShopPurchaseEventArgs.EventId, ShopPurchase); GameEntry.Event.Subscribe(ShopContinueEventArgs.EventId, ShopContinue); GameEntry.Event.Subscribe(DisplayItemShowEventArgs.EventId, DisplayItemShow); GameEntry.Event.Subscribe(DisplayItemHideEventArgs.EventId, DisplayItemHide); - - _isBindEvent = true; } - private void UnsubscribeEvents() + protected override void UnsubscribeCustomEvents() { - if (!_isBindEvent) return; - - GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); GameEntry.Event.Unsubscribe(RefreshEventArgs.EventId, Refresh); GameEntry.Event.Unsubscribe(ShopPurchaseEventArgs.EventId, ShopPurchase); GameEntry.Event.Unsubscribe(ShopContinueEventArgs.EventId, ShopContinue); GameEntry.Event.Unsubscribe(DisplayItemShowEventArgs.EventId, DisplayItemShow); GameEntry.Event.Unsubscribe(DisplayItemHideEventArgs.EventId, DisplayItemHide); - - _isBindEvent = false; } #region BuildContext @@ -72,38 +56,52 @@ namespace UI RefreshPrice = rawData.RefreshPrice, PlayerCoin = rawData.PlayerCoin, GoodsItems = rawData.GoodsItems, - PropListContext = BuildDisplayListAreaContext("道具", rawData.PropItems, rawData.PropMaxCount), - WeaponListContext = BuildDisplayListAreaContext("武器", rawData.WeaponItems, rawData.WeaponMaxCount) + PropListContext = BuildDisplayListAreaContext(DisplayListAreaType.Prop, rawData.PropItems, rawData.PropMaxCount), + WeaponListContext = BuildDisplayListAreaContext(DisplayListAreaType.Weapon, rawData.WeaponItems, rawData.WeaponMaxCount) }; } - private static DisplayListAreaContext BuildDisplayListAreaContext(string title, IReadOnlyList items, + private static DisplayListAreaContext BuildDisplayListAreaContext(DisplayListAreaType listType, IReadOnlyList items, int maxCount) { - DisplayItemContext[] itemContexts = new DisplayItemContext[items.Count]; - if (title == "武器") + string title = GetDisplayListTitle(listType); + if (items == null) { - if (items is IReadOnlyList weapons) + return new DisplayListAreaContext { - for (int i = 0; i < weapons.Count; i++) - { - WeaponBase weapon = weapons[i]; - if (weapon == null) break; - itemContexts[i] = BuildWeaponItem(weapon); - } - } + Title = title, + CurrentCount = 0, + MaxCount = maxCount, + ItemContexts = System.Array.Empty() + }; } - else if (title == "道具") + + DisplayItemContext[] itemContexts = new DisplayItemContext[items.Count]; + switch (listType) { - if (items is IReadOnlyList propItems) - { - for (int i = 0; i < propItems.Count; i++) + case DisplayListAreaType.Weapon: + if (items is IReadOnlyList weapons) { - PropItem propItem = propItems[i]; - if (propItem == null) break; - itemContexts[i] = BuildPropItem(propItem); + for (int i = 0; i < weapons.Count; i++) + { + WeaponBase weapon = weapons[i]; + if (weapon == null) break; + itemContexts[i] = BuildWeaponItem(weapon); + } } - } + break; + + case DisplayListAreaType.Prop: + if (items is IReadOnlyList propItems) + { + for (int i = 0; i < propItems.Count; i++) + { + PropItem propItem = propItems[i]; + if (propItem == null) break; + itemContexts[i] = BuildPropItem(propItem); + } + } + break; } int currentCount = itemContexts.Length; @@ -116,6 +114,16 @@ namespace UI }; } + private static string GetDisplayListTitle(DisplayListAreaType listType) + { + return listType switch + { + DisplayListAreaType.Weapon => "武器", + DisplayListAreaType.Prop => "道具", + _ => string.Empty + }; + } + private static DisplayItemContext BuildPropItem(PropItem propItem) { string iconAssetName = null; @@ -135,7 +143,6 @@ namespace UI }; } - private static DisplayItemContext BuildWeaponItem(WeaponBase weaponBase) { string iconAssetName = null; @@ -176,33 +183,8 @@ namespace UI #endregion - #region UI Methods - protected override int? OpenUIInternal(ShopFormContext context) - { - if (context == null) - { - Log.Warning("ShopFormController.OpenUI() context is null."); - return null; - } - - _context = context; - - if (_shopForm != null && _shopFormSerialId.HasValue && - GameEntry.UI.HasUIForm(_shopFormSerialId.Value)) - { - _shopForm.RefreshUI(_context); - return _shopFormSerialId; - } - - CloseUI(); - _pendingRefresh = true; - SubscribeEvents(); - _shopFormSerialId = GameEntry.UI.OpenUIForm(UIFormType.ShopForm, context); - return _shopFormSerialId; - } - public int? OpenUI(ShopFormRawData rawData) { ShopFormContext context = BuildContext(rawData); @@ -237,29 +219,6 @@ namespace UI return OpenUI(rawData); } - public override void CloseUI() - { - _pendingRefresh = false; - UnsubscribeEvents(); - if (_shopFormSerialId.HasValue) - { - if (GameEntry.UI.HasUIForm(_shopFormSerialId.Value)) - { - GameEntry.UI.CloseUIForm(_shopFormSerialId.Value); - } - - _shopForm = null; - _shopFormSerialId = null; - return; - } - - if (_shopForm != null) - { - _shopForm.Close(); - _shopForm = null; - } - } - public override void BindUseCase(IUIUseCase useCase) { if (!(useCase is ShopFormUseCase shopFormUseCase)) @@ -271,118 +230,60 @@ namespace UI _useCase = shopFormUseCase; } - private void TryRefreshUI() - { - if (_context == null) - { - return; - } - - if (_shopForm == null) - { - _pendingRefresh = true; - return; - } - - _shopForm.RefreshUI(_context); - _pendingRefresh = false; - } - #endregion #region Service private void RefreshGoodsItems(ShopRefreshResult result) { - if (_context == null || result == null) + if (Context == null || result == null) { return; } - _context.GoodsItems = result.GoodsItems; - _context.RefreshPrice = result.RefreshPrice; + Context.GoodsItems = result.GoodsItems; + Context.RefreshPrice = result.RefreshPrice; - if (_shopForm == null) + if (Form == null) { return; } - _shopForm.RefreshGoodsItems(result.GoodsItems); - _shopForm.RefreshRefreshPrice(result.RefreshPrice); + Form.RefreshGoodsItems(result.GoodsItems); + Form.RefreshRefreshPrice(result.RefreshPrice); } private void ApplyGoodsPurchased(ShopPurchaseResult result) { - if (_context == null || result == null) + if (Context == null || result == null) { return; } - if (_context.GoodsItems != null && result.GoodsIndex >= 0 && result.GoodsIndex < _context.GoodsItems.Count) + if (Context.GoodsItems != null && result.GoodsIndex >= 0 && result.GoodsIndex < Context.GoodsItems.Count) { - _context.GoodsItems[result.GoodsIndex] = null; + Context.GoodsItems[result.GoodsIndex] = null; } if (result.DisplayItem != null) { if (result.DisplayItem.IsWeapon) { - AppendDisplayItemContext(_context.WeaponListContext, result.DisplayItem); + AppendDisplayItemContext(Context.WeaponListContext, result.DisplayItem); } else { - AppendDisplayItemContext(_context.PropListContext, result.DisplayItem); + AppendDisplayItemContext(Context.PropListContext, result.DisplayItem); } } - _shopForm?.ApplyGoodsPurchased(result.GoodsIndex, result.DisplayItem); + Form?.ApplyGoodsPurchased(result.GoodsIndex, result.DisplayItem); } #endregion #region Event Handlers - private void OpenUIFormSuccess(object sender, GameEventArgs e) - { - if (!(e is OpenUIFormSuccessEventArgs args)) return; - - if (!_shopFormSerialId.HasValue) return; - - if (args.UIForm == null || args.UIForm.SerialId != _shopFormSerialId.Value || args.UserData != _context) - { - return; - } - - _shopForm = args.UIForm.Logic as ShopForm; - - if (_shopForm == null) - { - Log.Warning("ShopFormController open success but form logic is invalid."); - return; - } - - if (_pendingRefresh) - { - TryRefreshUI(); - } - } - - private void CloseUIFormComplete(object sender, GameEventArgs e) - { - if (!(e is CloseUIFormCompleteEventArgs args)) - { - return; - } - - if (args.SerialId != _shopFormSerialId) - { - return; - } - - _shopForm = null; - _shopFormSerialId = null; - } - private void Refresh(object sender, GameEventArgs e) { if (!(sender is ShopForm)) @@ -442,7 +343,7 @@ namespace UI private void DisplayItemShow(object sender, GameEventArgs e) { - if (!(e is DisplayItemShowEventArgs args)) return; + if (!(e is DisplayItemShowEventArgs args) || _rawData == null) return; DisplayItemInfoFormRawData rawData = new(); rawData.TargetPos = args.TargetPos; @@ -472,7 +373,6 @@ namespace UI GameEntry.UIRouter.OpenUI(UIFormType.DisplayItemInfoForm, rawData); } - private void DisplayItemHide(object sender, GameEventArgs e) { if (!(e is DisplayItemHideEventArgs)) return; diff --git a/Assets/GameMain/Scripts/UI/GameScene/UseCase/ShopFormUseCase.cs b/Assets/GameMain/Scripts/UI/GameScene/UseCase/ShopFormUseCase.cs index e33fed2..2563aca 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/UseCase/ShopFormUseCase.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/UseCase/ShopFormUseCase.cs @@ -242,6 +242,14 @@ namespace UI }; } + if (goods.GoodsType == GoodsType.Weapon) + { + // TODO: Weapon purchase apply flow depends on the upcoming weapon system integration. + // Implement weapon creation/equip/add-to-inventory here when weapon runtime model is ready. + Log.Warning("ShopFormUseCase::ApplyGoodsPurchase: Weapon purchase flow is not implemented yet."); + return null; + } + return null; } } diff --git a/Assets/GameMain/Scripts/UI/GameScene/View/DisplayListArea.cs b/Assets/GameMain/Scripts/UI/GameScene/View/DisplayListArea.cs index 2f806aa..76f308c 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/View/DisplayListArea.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/View/DisplayListArea.cs @@ -8,6 +8,13 @@ using UnityGameFramework.Runtime; namespace UI { + public enum DisplayListAreaType : byte + { + None = 0, + Prop = 1, + Weapon = 2 + } + public class DisplayListArea : MonoBehaviour { [SerializeField] private TMP_Text _titleText; @@ -72,7 +79,7 @@ namespace UI public void OnDestroy() { - _displayItemObjectPool.ReleaseAllUnused(); + _displayItemObjectPool?.ReleaseAllUnused(); } public DisplayItem AddItem(DisplayItemContext itemContext) @@ -275,4 +282,4 @@ namespace UI _countText.text = maxCount == -1 ? $"({currentCount})" : $"({currentCount}/{maxCount})"; } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/UI/GameScene/View/LevelUpForm.cs b/Assets/GameMain/Scripts/UI/GameScene/View/LevelUpForm.cs index b43d3ce..052ea5a 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/View/LevelUpForm.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/View/LevelUpForm.cs @@ -23,7 +23,8 @@ namespace UI } if (_context.Props == null) return; - for (int i = 0; i < _propItems.Length; i++) + int count = Mathf.Min(_propItems.Length, _context.Props.Count); + for (int i = 0; i < count; i++) { _propItems[i].gameObject.SetActive(true); _propItems[i].Init(_context.Props[i]); @@ -66,4 +67,4 @@ namespace UI GameEntry.Event.Fire(this, RefreshEventArgs.Create(_context.RefreshPrice)); } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/UI/General/Controller/DialogFormController.cs b/Assets/GameMain/Scripts/UI/General/Controller/DialogFormController.cs index 8d42a09..7114f95 100644 --- a/Assets/GameMain/Scripts/UI/General/Controller/DialogFormController.cs +++ b/Assets/GameMain/Scripts/UI/General/Controller/DialogFormController.cs @@ -1,39 +1,20 @@ using Definition.Enum; -using GameFramework.Event; using UnityGameFramework.Runtime; namespace UI { - public class DialogFormController : UIFormControllerBase + public class DialogFormController : UIFormControllerCommonBase { - private DialogFormContext _context; - - private DialogForm _dialogForm; - - private int? _dialogFormSerialId; - - private bool _pendingRefresh; - - private bool _isBindEvent; + protected override UIFormType UIFormTypeId => UIFormType.DialogForm; - private void SubscribeEvents() + protected override void RefreshUI(DialogForm form, DialogFormContext context) { - if (_isBindEvent) return; - - GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); - - _isBindEvent = true; + form.RefreshUI(context); } - private void UnsubscribeEvents() + protected override void CloseLoadedFormDirect(DialogForm form) { - if (!_isBindEvent) return; - - GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); - - _isBindEvent = false; + GameEntry.UI.CloseUIForm(form); } private static DialogFormContext BuildContext(DialogFormRawData rawData) @@ -59,30 +40,6 @@ namespace UI }; } - protected override int? OpenUIInternal(DialogFormContext context) - { - if (context == null) - { - Log.Warning("DialogFormController.OpenUI() context is null."); - return null; - } - - _context = context; - - if (_dialogForm != null && _dialogFormSerialId.HasValue && - GameEntry.UI.HasUIForm(_dialogFormSerialId.Value)) - { - _dialogForm.RefreshUI(_context); - return _dialogFormSerialId; - } - - CloseUI(); - _pendingRefresh = true; - SubscribeEvents(); - _dialogFormSerialId = GameEntry.UI.OpenUIForm(UIFormType.DialogForm, context); - return _dialogFormSerialId; - } - public int? OpenUI(DialogFormRawData rawData) { DialogFormContext context = BuildContext(rawData); @@ -101,42 +58,13 @@ namespace UI return OpenUI(rawData); } - if (userData is DialogFormRawData dialogParams) - { - return OpenUIInternal(BuildContext(dialogParams)); - } - if (userData != null) { Log.Warning("DialogFormController.OpenUI() userData type is invalid."); return null; } - return OpenUIInternal(_context); - } - - public override void CloseUI() - { - _pendingRefresh = false; - UnsubscribeEvents(); - - if (_dialogFormSerialId.HasValue) - { - if (GameEntry.UI.HasUIForm(_dialogFormSerialId.Value)) - { - GameEntry.UI.CloseUIForm(_dialogFormSerialId.Value); - } - - _dialogForm = null; - _dialogFormSerialId = null; - return; - } - - if (_dialogForm != null) - { - GameEntry.UI.CloseUIForm(_dialogForm); - _dialogForm = null; - } + return OpenUIInternal(Context); } public override void BindUseCase(IUIUseCase useCase) @@ -146,70 +74,5 @@ namespace UI Log.Warning("DialogFormController does not use a use case."); } } - - private void TryRefreshUI() - { - if (_context == null) - { - return; - } - - if (_dialogForm == null) - { - _pendingRefresh = true; - return; - } - - _dialogForm.RefreshUI(_context); - _pendingRefresh = false; - } - - private void OpenUIFormSuccess(object sender, GameEventArgs e) - { - if (!(e is OpenUIFormSuccessEventArgs args)) - { - return; - } - - if (!_dialogFormSerialId.HasValue) - { - return; - } - - if (args.UIForm == null || args.UIForm.SerialId != _dialogFormSerialId.Value || - args.UserData != _context) - { - return; - } - - _dialogForm = args.UIForm.Logic as DialogForm; - - if (_dialogForm == null) - { - Log.Warning("DialogFormController open success but form logic is invalid."); - return; - } - - if (_pendingRefresh) - { - TryRefreshUI(); - } - } - - private void CloseUIFormComplete(object sender, GameEventArgs e) - { - if (!(e is CloseUIFormCompleteEventArgs args)) - { - return; - } - - if (args.SerialId != _dialogFormSerialId) - { - return; - } - - _dialogForm = null; - _dialogFormSerialId = null; - } } } diff --git a/Assets/GameMain/Scripts/UI/General/RawData/DialogFormRawData.cs b/Assets/GameMain/Scripts/UI/General/RawData/DialogFormRawData.cs index f2dfbd9..1cc2b49 100644 --- a/Assets/GameMain/Scripts/UI/General/RawData/DialogFormRawData.cs +++ b/Assets/GameMain/Scripts/UI/General/RawData/DialogFormRawData.cs @@ -100,7 +100,7 @@ namespace UI /// /// 用户自定义数据。 /// - public string UserData + public object UserData { get; set; diff --git a/Assets/GameMain/Scripts/UI/MenuScene/Controller/SelectRoleFormController.cs b/Assets/GameMain/Scripts/UI/MenuScene/Controller/SelectRoleFormController.cs index d566bf4..c31e86f 100644 --- a/Assets/GameMain/Scripts/UI/MenuScene/Controller/SelectRoleFormController.cs +++ b/Assets/GameMain/Scripts/UI/MenuScene/Controller/SelectRoleFormController.cs @@ -5,44 +5,29 @@ using UnityGameFramework.Runtime; namespace UI { - public class SelectRoleFormController : UIFormControllerBase + public class SelectRoleFormController : UIFormControllerCommonBase { private SelectRoleFormUseCase _useCase; - private SelectRoleFormContext _context; + protected override UIFormType UIFormTypeId => UIFormType.SelectRoleForm; - private SelectRoleForm _selectRoleForm; - - private int? _selectRoleFormSerialId; - - private bool _pendingRefresh; - - private bool _isBindEvent; - - private void SubscribeEvents() + protected override void RefreshUI(SelectRoleForm form, SelectRoleFormContext context) { - if (_isBindEvent) return; + form.RefreshUI(context); + } - GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); + protected override void SubscribeCustomEvents() + { GameEntry.Event.Subscribe(MenuSelectRoleReturnEventArgs.EventId, OnMenuSelectRoleReturn); GameEntry.Event.Subscribe(MenuSelectRoleSelectedEventArgs.EventId, OnMenuSelectRoleSelected); GameEntry.Event.Subscribe(MenuSelectRoleConfirmEventArgs.EventId, OnMenuSelectRoleConfirm); - - _isBindEvent = true; } - private void UnsubscribeEvents() + protected override void UnsubscribeCustomEvents() { - if (!_isBindEvent) return; - - GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); GameEntry.Event.Unsubscribe(MenuSelectRoleReturnEventArgs.EventId, OnMenuSelectRoleReturn); GameEntry.Event.Unsubscribe(MenuSelectRoleSelectedEventArgs.EventId, OnMenuSelectRoleSelected); GameEntry.Event.Unsubscribe(MenuSelectRoleConfirmEventArgs.EventId, OnMenuSelectRoleConfirm); - - _isBindEvent = false; } private static SelectRoleFormContext BuildContext(SelectRoleFormRawData rawData) @@ -85,32 +70,6 @@ namespace UI }; } - #region UI Methods - - protected override int? OpenUIInternal(SelectRoleFormContext context) - { - if (context == null) - { - Log.Warning("SelectRoleFormController.OpenUI() context is null."); - return null; - } - - _context = context; - - if (_selectRoleForm != null && _selectRoleFormSerialId.HasValue && - GameEntry.UI.HasUIForm(_selectRoleFormSerialId.Value)) - { - _selectRoleForm.RefreshUI(_context); - return _selectRoleFormSerialId; - } - - CloseUI(); - _pendingRefresh = true; - SubscribeEvents(); - _selectRoleFormSerialId = GameEntry.UI.OpenUIForm(UIFormType.SelectRoleForm, context); - return _selectRoleFormSerialId; - } - public override int? OpenUI(object userData = null) { if (userData is SelectRoleFormContext selectRoleFormContext) @@ -124,35 +83,17 @@ namespace UI return null; } + if (_useCase == null) + { + Log.Error("SelectRoleFormController.OpenUI() useCase is null."); + return null; + } + SelectRoleFormRawData rawData = _useCase.CreateModel(); SelectRoleFormContext context = BuildContext(rawData); return OpenUIInternal(context); } - public override void CloseUI() - { - _pendingRefresh = false; - UnsubscribeEvents(); - - if (_selectRoleFormSerialId.HasValue) - { - if (GameEntry.UI.HasUIForm(_selectRoleFormSerialId.Value)) - { - GameEntry.UI.CloseUIForm(_selectRoleFormSerialId.Value); - } - - _selectRoleForm = null; - _selectRoleFormSerialId = null; - return; - } - - if (_selectRoleForm != null) - { - _selectRoleForm.Close(); - _selectRoleForm = null; - } - } - public override void BindUseCase(IUIUseCase useCase) { if (!(useCase is SelectRoleFormUseCase selectRoleUseCase)) @@ -164,90 +105,14 @@ namespace UI _useCase = selectRoleUseCase; } - private void TryRefreshUI() - { - if (_context == null) - { - return; - } - - if (_selectRoleForm == null) - { - _pendingRefresh = true; - return; - } - - _selectRoleForm.RefreshUI(_context); - _pendingRefresh = false; - } - - #endregion - - #region Service - public void UpdateShowRole(RolePropertyAreaContext rolePropertyAreaContext) { - if (_context != null) + if (Context != null) { - _context.RolePropertyAreaContext = rolePropertyAreaContext; + Context.RolePropertyAreaContext = rolePropertyAreaContext; } - if (_selectRoleForm != null) - { - _selectRoleForm.UpdateShowRole(rolePropertyAreaContext); - } - } - - #endregion - - #region Event Handlers - - private void OpenUIFormSuccess(object sender, GameEventArgs e) - { - if (!(e is OpenUIFormSuccessEventArgs args)) - { - return; - } - - if (!_selectRoleFormSerialId.HasValue) - { - return; - } - - if (args.UIForm == null || args.UIForm.SerialId != _selectRoleFormSerialId.Value || - args.UserData != _context) - { - return; - } - - _selectRoleForm = args.UIForm.Logic as SelectRoleForm; - - if (_selectRoleForm == null) - { - Log.Warning("SelectRoleFormController open success but form logic is invalid."); - return; - } - - if (_pendingRefresh) - { - TryRefreshUI(); - } - } - - private void CloseUIFormComplete(object sender, GameEventArgs e) - { - if (!(e is CloseUIFormCompleteEventArgs args)) - { - return; - } - - if (args.SerialId != _selectRoleFormSerialId) - { - return; - } - - _selectRoleForm = null; - _selectRoleFormSerialId = null; + Form?.UpdateShowRole(rolePropertyAreaContext); } private void OnMenuSelectRoleReturn(object sender, GameEventArgs e) @@ -274,8 +139,8 @@ namespace UI return; } - _context = context; - UpdateShowRole(_context.RolePropertyAreaContext); + SetContext(context); + UpdateShowRole(context.RolePropertyAreaContext); } private void OnMenuSelectRoleConfirm(object sender, GameEventArgs e) @@ -285,9 +150,7 @@ namespace UI return; } - _useCase.ConfirmSelectedRole(); + _useCase?.ConfirmSelectedRole(); } - - #endregion } } diff --git a/Assets/GameMain/Scripts/UI/MenuScene/Controller/StartMenuFormController.cs b/Assets/GameMain/Scripts/UI/MenuScene/Controller/StartMenuFormController.cs index d811140..34d1da0 100644 --- a/Assets/GameMain/Scripts/UI/MenuScene/Controller/StartMenuFormController.cs +++ b/Assets/GameMain/Scripts/UI/MenuScene/Controller/StartMenuFormController.cs @@ -5,82 +5,39 @@ using UnityGameFramework.Runtime; namespace UI { - public class StartMenuFormController : UIFormControllerBase + public class StartMenuFormController : UIFormControllerCommonBase { - private StartMenuFormContext _context; + protected override UIFormType UIFormTypeId => UIFormType.StartMenuForm; - private StartMenuForm _startMenuForm; - - private int? _startMenuFormSerialId; - - private bool _pendingRefresh; - - private bool _isBindEvent; - - private void SubscribeEvents() + protected override void RefreshUI(StartMenuForm form, StartMenuFormContext context) { - if (_isBindEvent) return; - - GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); + form.RefreshUI(context); + } + protected override void SubscribeCustomEvents() + { GameEntry.Event.Subscribe(MenuStartGameEventArgs.EventId, OnMenuStartGameButtonClick); GameEntry.Event.Subscribe(MenuFileButtonClickEventArgs.EventId, OnMenuFileButtonClick); GameEntry.Event.Subscribe(MenuGuideButtonClickEventArgs.EventId, OnMenuGuideButtonClick); GameEntry.Event.Subscribe(MenuSettingButtonClickEventArgs.EventId, OnMenuSettingButtonClick); GameEntry.Event.Subscribe(MenuQuitButtonClickEventArgs.EventId, OnMenuQuitButtonClick); GameEntry.Event.Subscribe(MenuAboutButtonClickEventArgs.EventId, OnMenuAboutButtonClick); - - _isBindEvent = true; } - private void UnsubscribeEvents() + protected override void UnsubscribeCustomEvents() { - if (!_isBindEvent) return; - - GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); - GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete); - GameEntry.Event.Unsubscribe(MenuStartGameEventArgs.EventId, OnMenuStartGameButtonClick); GameEntry.Event.Unsubscribe(MenuFileButtonClickEventArgs.EventId, OnMenuFileButtonClick); GameEntry.Event.Unsubscribe(MenuGuideButtonClickEventArgs.EventId, OnMenuGuideButtonClick); GameEntry.Event.Unsubscribe(MenuSettingButtonClickEventArgs.EventId, OnMenuSettingButtonClick); GameEntry.Event.Unsubscribe(MenuQuitButtonClickEventArgs.EventId, OnMenuQuitButtonClick); GameEntry.Event.Unsubscribe(MenuAboutButtonClickEventArgs.EventId, OnMenuAboutButtonClick); - - _isBindEvent = false; } private static StartMenuFormContext BuildStartMenuFormContext() { return new StartMenuFormContext(); } - - #region UI Methods - - protected override int? OpenUIInternal(StartMenuFormContext context) - { - if (context == null) - { - Log.Warning("StartMenuFormController.OpenUI() context is null."); - return null; - } - - _context = context; - - if (_startMenuForm != null && _startMenuFormSerialId.HasValue && - GameEntry.UI.HasUIForm(_startMenuFormSerialId.Value)) - { - _startMenuForm.RefreshUI(_context); - return _startMenuFormSerialId; - } - - CloseUI(); - _pendingRefresh = true; - SubscribeEvents(); - _startMenuFormSerialId = GameEntry.UI.OpenUIForm(UIFormType.StartMenuForm, context); - return _startMenuFormSerialId; - } public override int? OpenUI(object userData = null) { @@ -103,105 +60,11 @@ namespace UI return OpenUIInternal(context); } - public override void CloseUI() - { - _pendingRefresh = false; - UnsubscribeEvents(); - - if (_startMenuFormSerialId.HasValue) - { - if (GameEntry.UI.HasUIForm(_startMenuFormSerialId.Value)) - { - GameEntry.UI.CloseUIForm(_startMenuFormSerialId.Value); - } - - _startMenuForm = null; - _startMenuFormSerialId = null; - return; - } - - if (_startMenuForm != null) - { - _startMenuForm.Close(); - _startMenuForm = null; - } - } - public override void BindUseCase(IUIUseCase useCase) { Log.Info("StartMenuForm doesn't need UseCase"); } - private void TryRefreshUI() - { - if (_context == null) - { - return; - } - - if (_startMenuForm == null) - { - _pendingRefresh = true; - return; - } - - _startMenuForm.RefreshUI(_context); - _pendingRefresh = false; - } - - #endregion - - - #region Event Handlers - - private void OpenUIFormSuccess(object sender, GameEventArgs e) - { - if (!(e is OpenUIFormSuccessEventArgs args)) - { - return; - } - - if (!_startMenuFormSerialId.HasValue) - { - return; - } - - if (args.UIForm == null || args.UIForm.SerialId != _startMenuFormSerialId.Value || - args.UserData != _context) - { - return; - } - - _startMenuForm = args.UIForm.Logic as StartMenuForm; - - if (_startMenuForm == null) - { - Log.Warning("StartMenuFormController open success but form logic is invalid."); - return; - } - - if (_pendingRefresh) - { - TryRefreshUI(); - } - } - - private void CloseUIFormComplete(object sender, GameEventArgs e) - { - if (!(e is CloseUIFormCompleteEventArgs args)) - { - return; - } - - if (args.SerialId != _startMenuFormSerialId) - { - return; - } - - _startMenuForm = null; - _startMenuFormSerialId = null; - } - private void OnMenuStartGameButtonClick(object sender, GameEventArgs e) { if (!(sender is StartMenuForm) || !(e is MenuStartGameEventArgs)) @@ -270,7 +133,5 @@ namespace UI Log.Warning("Menu about button click is not implemented."); } - - #endregion } } diff --git a/Assets/GameMain/Scripts/Utility/AIUtility.cs b/Assets/GameMain/Scripts/Utility/AIUtility.cs index d69f26b..9fe1ff0 100644 --- a/Assets/GameMain/Scripts/Utility/AIUtility.cs +++ b/Assets/GameMain/Scripts/Utility/AIUtility.cs @@ -15,18 +15,16 @@ using UnityEngine; using UnityGameFramework.Runtime; using Random = UnityEngine.Random; -namespace StarForce +namespace Game.Utility { /// /// AI 工具类。 /// public static class AIUtility { - private static Dictionary s_CampPairToRelation = - new Dictionary(); + private static Dictionary s_CampPairToRelation = new(); - private static Dictionary, CampType[]> s_CampAndRelationToCamps = - new Dictionary, CampType[]>(); + private static Dictionary, CampType[]> s_CampAndRelationToCamps = new(); static AIUtility() { @@ -68,9 +66,7 @@ namespace StarForce { if (first > second) { - CampType temp = first; - first = second; - second = temp; + (first, second) = (second, first); } RelationType relationType; @@ -92,8 +88,7 @@ namespace StarForce public static CampType[] GetCamps(CampType camp, RelationType relation) { KeyValuePair key = new KeyValuePair(camp, relation); - CampType[] result = null; - if (s_CampAndRelationToCamps.TryGetValue(key, out result)) + if (s_CampAndRelationToCamps.TryGetValue(key, out var result)) { return result; } @@ -194,7 +189,7 @@ namespace StarForce } int entityDamageHP = CalcDamageHP(weaponImpactData.AttackBase, weaponImpactData.AttackStat, - entityImpactData.DefenseStat, entityImpactData.DefenseStat); + entityImpactData.DefenseStat, entityImpactData.DodgeStat); entity.ApplyDamage(weapon, entityDamageHP); return; diff --git a/Todo List.md b/Todo List.md deleted file mode 100644 index da9ff60..0000000 --- a/Todo List.md +++ /dev/null @@ -1,40 +0,0 @@ -# 3D 类土豆兄弟开发 - -## 流程设计 -```mermaid -flowchart LR - A["开始菜单"]-->B["进入游戏"] - B-->C["选择角色(进阶)"] - C-->D - B-->D["选择初始武器"] - D-->E["进入关卡"] - E-->F["战斗,获取资源"] - F-->G["关卡结束,进入商店"] - G-->H["进入下一关"] - H-->E - F-->I["玩家死亡"] - I-->J["游戏结算"] - H-->K["完成所有关卡"] - K-->J - J-->A -``` - -## 开发需求 -### 基础部分 -#### UI 部分 -- [x] 开始菜单 UI -- [ ] 局内 HUD -- [ ] 局内商店页面 -- [ ] 设置页面 - -#### 游戏逻辑部分 -- [ ] 玩家操作 -- [ ] 玩家武器 - - [ ] 自动攻击 - - [ ] 等级制,多次获得同一把武器会升级 -- [ ] 敌人:近战、远程…… -- [ ] 关卡制,每关结束后进入商店购买武器和道具 - -### 进阶部分 -- [ ] 武器词缀:每把高级武器会随机带有一个该等级的词缀,附带额外效果 -- [ ] 多角色:每个角色初始属性不同,不同角色技能不同 \ No newline at end of file diff --git a/TodoList.md b/TodoList.md new file mode 100644 index 0000000..59dbc7f --- /dev/null +++ b/TodoList.md @@ -0,0 +1,119 @@ +# 3D 类吸血鬼幸存者项目 Todo(GameMain 侧规划) + +> 范围说明:本清单基于当前 `Assets/GameMain` 代码现状制定,未涉及 `Assets/GameFramework` 底层实现。 + +## 0. 当前代码现状(已确认) +- [x] 已有完整流程骨架:`Menu -> Game(Battle/LevelUp/Shop)`,以及基础实体系统(Player/Enemy/Weapon/Drop/UI)。 +- [x] 目前仍是传统 `MonoBehaviour + 每实体 OnUpdate` 驱动,暂无 Job System/Burst 实装。 +- [x] 已有一个 Instancing Shader:`Assets/GameMain/Materials/Shaders/SimpleInstancedFlash.shader`,但未接入运行时批量渲染管线。 +- [x] 未发现代码热更新方案接入(如 HybridCLR/ILRuntime/xLua 等)。 + +## 1. P0 基线修正与性能基准 +- [ ] 建立性能基准场景(建议复用 `Game.unity` + 压测参数): + - 指标:`1k / 3k / 5k` 敌人时的 FPS、CPU Main Thread、GC Alloc、Draw Calls。 + - 输出:一份基线表格(开发机配置 + Unity Profiler 截图)。 +- [x] 修正当前高风险逻辑问题(避免后续优化建立在不稳定行为上): + - `ProcedureGame.OnEnter()` 与 `_hudInitialized` 逻辑中有重复初始化状态机风险(`InitGameState()` 被调用两次)。 + - `Player.Enable` setter 未更新 `_enable` 字段,状态切换语义不完整。 + - `PlayerData` 构造中 `MaxHealthBase` 初始化异常(自赋值)。 + - `AIUtility.PerformCollision()` 武器伤害计算参数里疑似把 `DodgeStat` 传成 `DefenseStat`。 +- [ ] 给关键战斗链路加最小回归测试(PlayMode): + - 伤害结算、掉落、回合切换(Battle/LevelUp/Shop)。 + +**验收标准** +- 基线数据可复现。 +- 以上问题修正后,核心流程可稳定连续跑 10 分钟无异常日志。 + +## 2. P1 Simulation 分层(为 Job/Burst 做结构准备) +- [ ] 新建 `Simulation` 层(建议目录:`Assets/GameMain/Scripts/Simulation`): + - `SimulationWorld`:统一持有敌人/投射物/掉落物的纯数据容器。 + - `EnemySimData / ProjectileSimData / PickupSimData`:结构化、连续内存友好的数据定义。 + - `EntityBinding`:维护 `EntityId <-> SimulationIndex` 映射。 +- [ ] 将“逻辑计算”和“表现层(Transform/Animator/特效/UI)”拆离: + - 逻辑层输出 position/rotation/state。 + - 表现层只消费结果做显示。 +- [ ] 先保持现有 GameFramework 实体生命周期不变,仅替换更新路径。 + +**验收标准** +- 敌人移动/追踪由 Simulation 统一调度,不再逐个 Enemy MonoBehaviour 执行核心逻辑。 + +## 3. P2 Job System + Burst 落地(核心性能阶段) +- [ ] 引入并锁定依赖版本(Unity 2022.3 对应): + - `com.unity.collections` + - `com.unity.jobs` + - `com.unity.burst` + - `com.unity.mathematics` +- [ ] 第一批 Job 化模块(优先级从高到低): + 1. 敌人移动与朝向更新(`IJobParallelFor`)。 + 2. 目标选择加速(空间哈希/网格分桶,减少全量最近邻搜索)。 + 3. 投射物批量移动与寿命回收。 + 4. AOE/碰撞候选筛选(先 broad phase,后精算)。 +- [ ] Burst 编译策略: + - 热路径 Job 全部 `[BurstCompile]`。 + - 禁止在 Job 内使用托管分配、虚调用、LINQ。 +- [ ] 主线程仅做:输入采样、状态切换、UI同步、实体显隐。 + +**验收标准** +- 在 3k 敌人规模下,CPU Main Thread 明显下降(目标 >= 30%)。 +- Profiler 中战斗帧 GC Alloc 接近 0(持续帧)。 + +## 4. P3 GPU Instancing 渲染管线(与 Job 并行推进) +- [ ] 先做“低风险版”批处理: + - 同 Mesh/Material 的敌人分组,使用 `Graphics.DrawMeshInstanced`(每批最多 1023)。 +- [ ] 再升级“高上限版”: + - 使用 `Graphics.DrawMeshInstancedIndirect` + `ComputeBuffer` 管理实例矩阵/颜色/状态。 +- [ ] 建立 `InstanceRendererComponent`: + - 输入:Simulation 输出的 transform/state。 + - 输出:按 enemy archetype 的批量绘制。 +- [ ] 将受击闪白、稀有度颜色等通过 `MaterialPropertyBlock` 或实例化属性下发(复用现有 Instanced Shader 思路)。 +- [ ] 与现有碰撞体系解耦: + - 逻辑碰撞走 Simulation,渲染不再依赖每敌人独立 GameObject Renderer。 + +**验收标准** +- 5k 敌人规模 Draw Calls 显著下降。 +- 渲染主线程耗时可控,且视觉行为(受击、朝向、死亡)与逻辑一致。 + +## 5. P4 代码热更新(建议 HybridCLR) +- [ ] 技术选型定稿:建议 `HybridCLR`(Unity 2022 + C# 生态兼容更自然)。 +- [ ] Assembly 拆分: + - `Main`:启动、资源更新、基础桥接(不可热更)。 + - `Hotfix`:玩法规则、数值公式、技能与敌人行为树(可热更)。 +- [ ] 运行时加载流程: + - 启动时通过现有资源更新流程拉取热更 DLL(与版本号绑定)。 + - 加载 AOT metadata + Hotfix DLL,反射启动 `HotfixEntry`。 +- [ ] 建立热更边界规范: + - Hotfix 不直接依赖编辑器代码。 + - 跨域调用统一走接口/Facade(避免大量反射散落)。 +- [ ] 回滚机制: + - DLL 校验失败时回退上一个稳定版本。 + +**验收标准** +- 不发整包即可替换一条技能逻辑并在设备上生效。 +- 热更失败可自动回退,启动不中断。 + +## 6. P5 玩法目标对齐(与技术栈并行) +- [ ] 武器系统补完: + - 自动攻击、多武器并存、同武器升级/进化。 + - `Shop` 武器购买流程补完(当前已有 TODO)。 +- [ ] 敌人系统扩展: + - 近战/远程/精英/首领模板化,支持波次参数化。 +- [ ] 关卡节奏: + - `DRLevel` 扩展为“时间轴+事件波次+奖励节点”。 +- [ ] 数值可调试工具: + - 实时查看 Dps、受击、击杀效率、掉落速率。 + +**验收标准** +- 一局 10~20 分钟循环可闭环,且关卡难度曲线平滑。 + +## 7. 推荐执行顺序(避免返工) +- [ ] 里程碑 A:`P0 -> P1`(稳定结构 + 可观测) +- [ ] 里程碑 B:`P2`(CPU 性能突破) +- [ ] 里程碑 C:`P3`(渲染性能突破) +- [ ] 里程碑 D:`P4`(线上快速迭代能力) +- [ ] 里程碑 E:`P5`(内容量与可玩性扩展) + +## 8. 交付物清单(每阶段都要有) +- [ ] 设计文档(接口、数据结构、生命周期)。 +- [ ] Profiling 对比(改造前后同场景同参数)。 +- [ ] 回归用例(至少战斗、关卡切换、商店、升级)。 +- [ ] 风险与回滚说明(特别是热更新与渲染链路)。