按 UI 五层规范重构 SelectRoleForm 并整理事件目录

- SelectRoleForm 五层归位:UseCase 改为构造注入 IProcedureMenu(新增 Runtime/ProcedureInterface),RawData 暴露原始 StatModifier[] 而非展示串,Controller 复用 ItemDescUtility 拼装属性文本,并修复 OpenUIAsync(object) 合法 RawData 分支缺少 return 的误报路径
- StatModifier 去展示职责:删除 _statTypeNames / ToString,富文本格式化下沉到 Presentation 层的 ItemDescUtility.Describe(StatModifier),CreatePropDescription 改用统一入口
  - 事件目录按 UIForm 归档:Base/Event/UI/Menu/* 拆解到 SelectRoleForm/ MenuForm/ DialogForm/ DisplayItemInfoForm/ Combat/ 等各自子目录,MenuSelectRoleReturnEventArgs 改名 SelectRoleReturnEventArgs,语义归属本 UI 模块
- IUIFormController → IUIController 改名,联动 UIControllerBase / UIRouterComponent / Editor
- 同步更新 docs/UI-5层架构设计规范.md 中相关示例与 MenuForm.prefab、ProcedureMenu 引用
This commit is contained in:
SepComet
2026-06-14 13:19:12 +08:00
parent dec18418f5
commit 8cf3802b9d
49 changed files with 128 additions and 117 deletions
@@ -7,61 +7,10 @@ namespace SepCore.Definition
{
public class StatModifier
{
// None = 0,
// MaxHealth = 1,
// MovementSpeed = 2,
// Attack = 3,
// Defense = 4,
// AttackSpeed = 5,
// Critical = 6,
// CriticalDamage = 7,
// Dodge = 8,
// AbsorbRange = 9
private readonly string[] _statTypeNames =
{
"无效",
"最大生命",
"移动速度",
"伤害",
"防御",
"冷却",
"暴击率",
"暴击伤害",
"闪避",
"金币/经验吸收范围"
};
public StatType StatType;
public float Value;
public bool IsPercent;
public override string ToString()
{
if (IsPercent)
{
if (Value > 0)
{
return $"{_statTypeNames[(int)StatType]}: <color=green>+{Value * 100}%</color>";
}
else
{
return $"{_statTypeNames[(int)StatType]}: <color=red>+{Value * 100}%</color>";
}
}
else
{
if (Value > 0)
{
return $"{_statTypeNames[(int)StatType]}: <color=green>+{Value}</color>";
}
else
{
return $"{_statTypeNames[(int)StatType]}: <color=red>+{Value}</color>";
}
}
}
public static StatModifier StringToModifier(string input)
{
if (string.IsNullOrEmpty(input))
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3de64a3f2f644813b51e5145d0a9431c
timeCreated: 1781403042
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 458b35bb1d1848c598f1d318c6523a6a
timeCreated: 1781402959
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 15844be485c343f0a94e2e6fcd90022d
timeCreated: 1781403001
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 8fc42c90ffa598d4e89c908ad82116d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -72,7 +72,7 @@ namespace SepCore.Editor
private void BuildControllerTypeOptions()
{
List<Type> controllerTypes = new();
foreach (Type type in TypeCache.GetTypesDerivedFrom<IUIFormController>())
foreach (Type type in TypeCache.GetTypesDerivedFrom<IUIController>())
{
if (type.IsAbstract || type.IsInterface || type.ContainsGenericParameters)
{
@@ -1,6 +1,7 @@
using Cysharp.Threading.Tasks;
using SepCore.Event;
using SepCore.Definition;
using SepCore.CustomUtility;
using GameFramework.Event;
using UnityEngine;
using UnityGameFramework.Runtime;
@@ -61,7 +62,8 @@ namespace SepCore.UI
propertyContext = new RolePropertyAreaContext
{
RoleName = rawData.SelectedRoleName,
InitialPropertyText = rawData.SelectedRoleInitialPropertyText
InitialPropertyText = ItemDescUtility.CreatePropDescription(rawData.SelectedRoleInitialProperties)
?? string.Empty
};
}
@@ -78,6 +80,7 @@ namespace SepCore.UI
if (userData is SelectRoleRawData rawData)
{
await OpenUIAsync(rawData, timeout);
return;
}
if (userData != null)
@@ -25,7 +25,35 @@ namespace SepCore.CustomUtility
{"attackDuration", "突刺时长"},
{"returnDuration", "收枪时长"}
};
private static readonly string[] _statTypeNames =
{
"无效",
"最大生命",
"移动速度",
"伤害",
"防御",
"冷却",
"暴击率",
"暴击伤害",
"闪避",
"金币/经验吸收范围"
};
public static string Describe(StatModifier modifier)
{
if (modifier == null)
{
return string.Empty;
}
string name = _statTypeNames[(int)modifier.StatType];
string colorTag = modifier.Value > 0 ? "green" : "red";
string suffix = modifier.IsPercent ? "%" : string.Empty;
float displayValue = modifier.IsPercent ? modifier.Value * 100 : modifier.Value;
return $"{name}: <color={colorTag}>+{displayValue}{suffix}</color>";
}
public static string CreatePropDescription(StatModifier[] modifiers)
{
if (modifiers == null || modifiers.Length == 0)
@@ -36,7 +64,7 @@ namespace SepCore.CustomUtility
StringBuilder sb = new StringBuilder();
foreach (StatModifier modifier in modifiers)
{
sb.Append(modifier);
sb.Append(Describe(modifier));
sb.Append('\n');
}
@@ -7,7 +7,7 @@ using ProcedureOwner = GameFramework.Fsm.IFsm<GameFramework.Procedure.IProcedure
namespace SepCore.Procedure
{
public class ProcedureMenu : ProcedureBase
public class ProcedureMenu : ProcedureBase, IProcedureMenu
{
public override bool UseNativeDialog => false;
@@ -15,9 +15,9 @@ namespace SepCore.Procedure
private int _selectedRoleId = 0;
public void StartGame(int selectedRoleId)
public void ConfirmSelectRole(int roleId)
{
_selectedRoleId = selectedRoleId;
_selectedRoleId = roleId;
_startGame = true;
}
@@ -29,7 +29,7 @@ namespace SepCore.Procedure
GameEntry.UIRouter.OpenUIAsync(UIFormType.MenuForm);
var useCase = new SelectRoleUseCase(StartGame);
var useCase = new SelectRoleUseCase(this);
GameEntry.UIRouter.BindUIUseCase(UIFormType.SelectRoleForm, useCase);
QualitySettings.vSyncCount = 0;
@@ -23,8 +23,8 @@ namespace SepCore.UIRouter
[SerializeField] private List<ControllerBinding> _controllerBindings = new();
private readonly Dictionary<UIFormType, IUIFormController> _routeControllers = new();
private readonly Dictionary<UIFormType, Func<IUIFormController>> _controllerFactories = new();
private readonly Dictionary<UIFormType, IUIController> _routeControllers = new();
private readonly Dictionary<UIFormType, Func<IUIController>> _controllerFactories = new();
protected override void Awake()
{
@@ -32,7 +32,7 @@ namespace SepCore.UIRouter
RegisterSerializedBindings();
}
public void RegisterController(UIFormType uiFormType, Func<IUIFormController> controllerFactory)
public void RegisterController(UIFormType uiFormType, Func<IUIController> controllerFactory)
{
if (controllerFactory == null)
{
@@ -40,7 +40,7 @@ namespace SepCore.UIRouter
return;
}
if (_routeControllers.TryGetValue(uiFormType, out IUIFormController controller))
if (_routeControllers.TryGetValue(uiFormType, out IUIController controller))
{
controller.CloseUIAsync().Forget();
_routeControllers.Remove(uiFormType);
@@ -50,14 +50,14 @@ namespace SepCore.UIRouter
}
public void RegisterController<TController>(UIFormType uiFormType)
where TController : IUIFormController, new()
where TController : IUIController, new()
{
RegisterController(uiFormType, () => new TController());
}
public void BindUIUseCase(UIFormType uiFormType, IUIUseCase useCase)
{
IUIFormController controller = GetOrCreateController(uiFormType);
IUIController controller = GetOrCreateController(uiFormType);
if (controller == null)
{
return;
@@ -68,7 +68,7 @@ namespace SepCore.UIRouter
public UniTask OpenUIAsync(UIFormType uiFormType, object userData = null, float timeout = 30f)
{
IUIFormController controller = GetOrCreateController(uiFormType);
IUIController controller = GetOrCreateController(uiFormType);
if (controller == null)
{
return default;
@@ -79,7 +79,7 @@ namespace SepCore.UIRouter
public UniTask CloseUIAsync(UIFormType uiFormType, object userData = null, float timeout = 30f)
{
IUIFormController controller = GetOrCreateController(uiFormType);
IUIController controller = GetOrCreateController(uiFormType);
if (controller == null)
{
return UniTask.CompletedTask;
@@ -88,14 +88,14 @@ namespace SepCore.UIRouter
return controller.CloseUIAsync(userData, timeout);
}
private IUIFormController GetOrCreateController(UIFormType uiFormType)
private IUIController GetOrCreateController(UIFormType uiFormType)
{
if (_routeControllers.TryGetValue(uiFormType, out IUIFormController controller))
if (_routeControllers.TryGetValue(uiFormType, out IUIController controller))
{
return controller;
}
if (!_controllerFactories.TryGetValue(uiFormType, out Func<IUIFormController> controllerFactory))
if (!_controllerFactories.TryGetValue(uiFormType, out Func<IUIController> controllerFactory))
{
Log.Error("UIRouterComponent requires a controller binding for '{0}'.", uiFormType.ToString());
return null;
@@ -141,7 +141,7 @@ namespace SepCore.UIRouter
continue;
}
if (!typeof(IUIFormController).IsAssignableFrom(controllerType))
if (!typeof(IUIController).IsAssignableFrom(controllerType))
{
Log.Warning("UIRouter binding type '{0}' does not implement IUIFormController.",
binding.ControllerTypeName);
@@ -152,10 +152,10 @@ namespace SepCore.UIRouter
}
}
private static IUIFormController CreateController(Type controllerType)
private static IUIController CreateController(Type controllerType)
{
object instance = Activator.CreateInstance(controllerType);
if (instance is IUIFormController controller)
if (instance is IUIController controller)
{
return controller;
}
@@ -165,7 +165,7 @@ namespace SepCore.UIRouter
private void OnDestroy()
{
foreach (KeyValuePair<UIFormType, IUIFormController> pair in _routeControllers)
foreach (KeyValuePair<UIFormType, IUIController> pair in _routeControllers)
{
pair.Value.CloseUIAsync().Forget();
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: f52f9694158bfda42a01330b373cbfe9
guid: f58e0f571a80e674991b1dca13aa02cb
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,7 @@
namespace SepCore.Procedure
{
public interface IProcedureMenu
{
public void ConfirmSelectRole(int roleId);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: beecddd20d3655541a86aafa1d1e31df
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -2,7 +2,7 @@ using Cysharp.Threading.Tasks;
namespace SepCore.UI
{
public interface IUIFormController
public interface IUIController
{
UniTask OpenUIAsync(object userData = null, float timeout = 30f);
@@ -5,7 +5,7 @@ using SepCore.AsyncTask;
namespace SepCore.UI
{
public abstract class UIControllerBase<TContext, TForm> : IUIFormController
public abstract class UIControllerBase<TContext, TForm> : IUIController
where TContext : UIContext
where TForm : UGuiForm
{
@@ -1,3 +1,5 @@
using SepCore.Definition;
namespace SepCore.UI
{
public class SelectRoleRawData
@@ -6,6 +8,6 @@ namespace SepCore.UI
public string[] RoleIconNames;
public int SelectedRoleId;
public string SelectedRoleName;
public string SelectedRoleInitialPropertyText;
public StatModifier[] SelectedRoleInitialProperties;
}
}
@@ -1,7 +1,6 @@
using System;
using System.Text;
using SepCore.DataTable;
using GameFramework.DataTable;
using SepCore.Procedure;
using Random = UnityEngine.Random;
namespace SepCore.UI
@@ -10,13 +9,13 @@ namespace SepCore.UI
{
private readonly IDataTable<DRRole> _roleDataTable;
private readonly Action<int> _onStartGame;
private readonly IProcedureMenu _procedureMenu;
public int SelectedRoleId { get; private set; }
public SelectRoleUseCase(Action<int> onStartGame)
public SelectRoleUseCase(IProcedureMenu procedureMenu)
{
_onStartGame = onStartGame;
_procedureMenu = procedureMenu;
_roleDataTable = GameEntry.DataTable.GetDataTable<DRRole>();
}
@@ -48,7 +47,7 @@ namespace SepCore.UI
bool result = SelectedRoleId >= 0;
if (result)
{
_onStartGame?.Invoke(SelectedRoleId);
_procedureMenu.ConfirmSelectRole(SelectedRoleId);
}
return result;
@@ -73,27 +72,8 @@ namespace SepCore.UI
RoleIconNames = iconNames,
SelectedRoleId = selectedRole?.Id ?? -1,
SelectedRoleName = selectedRole?.RoleName,
SelectedRoleInitialPropertyText = selectedRole != null
? BuildRoleInitialPropertyText(selectedRole)
: null
SelectedRoleInitialProperties = selectedRole?.InitialProperties
};
}
private static string BuildRoleInitialPropertyText(DRRole role)
{
if (role == null || role.InitialProperties == null || role.InitialProperties.Length == 0)
{
return string.Empty;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < role.InitialProperties.Length; i++)
{
sb.Append(role.InitialProperties[i]);
sb.Append('\n');
}
return sb.ToString();
}
}
}
@@ -255,6 +255,11 @@ PrefabInstance:
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 345099064574680782}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Navigation.m_Mode
value: 3
objectReference: {fileID: 0}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Colors.m_SelectedColor.b
@@ -661,6 +666,11 @@ PrefabInstance:
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 637634966344259004}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Navigation.m_Mode
value: 3
objectReference: {fileID: 0}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Colors.m_SelectedColor.b
@@ -1062,6 +1072,11 @@ PrefabInstance:
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 990282440119873477}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Navigation.m_Mode
value: 3
objectReference: {fileID: 0}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Colors.m_SelectedColor.b
@@ -1463,6 +1478,11 @@ PrefabInstance:
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 915430015319378619}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Navigation.m_Mode
value: 3
objectReference: {fileID: 0}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Colors.m_SelectedColor.b
@@ -1870,6 +1890,11 @@ PrefabInstance:
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 6148722132083568899}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Navigation.m_Mode
value: 3
objectReference: {fileID: 0}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Colors.m_SelectedColor.b
@@ -2271,6 +2296,11 @@ PrefabInstance:
propertyPath: m_TargetGraphic
value:
objectReference: {fileID: 4891246053961263497}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Navigation.m_Mode
value: 3
objectReference: {fileID: 0}
- target: {fileID: 4005815405540692376, guid: 0722cd253d6bf014eb4134a2151ec7e3,
type: 3}
propertyPath: m_Colors.m_SelectedColor.b