diff --git a/Client/Assets/Art/Atlas/Atlas_Common_bb.spriteatlasv2 b/Client/Assets/Art/Atlas/Atlas_Common_bb.spriteatlasv2 new file mode 100644 index 0000000..1712c36 --- /dev/null +++ b/Client/Assets/Art/Atlas/Atlas_Common_bb.spriteatlasv2 @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!612988286 &1 +SpriteAtlasAsset: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + serializedVersion: 2 + m_MasterAtlas: {fileID: 0} + m_ImporterData: + packables: + - {fileID: 21300000, guid: 5fe20064a619ceb4aacf49802ee4f70e, type: 3} + m_IsVariant: 0 diff --git a/Client/Assets/Art/Atlas/Atlas_Common_bb.spriteatlasv2.meta b/Client/Assets/Art/Atlas/Atlas_Common_bb.spriteatlasv2.meta new file mode 100644 index 0000000..8e5ee29 --- /dev/null +++ b/Client/Assets/Art/Atlas/Atlas_Common_bb.spriteatlasv2.meta @@ -0,0 +1,69 @@ +fileFormatVersion: 2 +guid: dc412ce8a0c03474dbc19475ebe3791a +SpriteAtlasImporter: + externalObjects: {} + textureSettings: + serializedVersion: 2 + anisoLevel: 1 + compressionQuality: 50 + maxTextureSize: 2048 + textureCompression: 0 + filterMode: 1 + generateMipMaps: 0 + readable: 0 + crunchedCompression: 0 + sRGB: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 50 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 1 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 49 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 1 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 50 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 1 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + packingSettings: + serializedVersion: 2 + padding: 4 + blockOffset: 1 + allowAlphaSplitting: 0 + enableRotation: 0 + enableTightPacking: 1 + enableAlphaDilation: 1 + secondaryTextureSettings: {} + variantMultiplier: 1 + bindAsDefault: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/AudioGroupConfigs.asset b/Client/Assets/AudioGroupConfigs.asset new file mode 100644 index 0000000..5026e6c --- /dev/null +++ b/Client/Assets/AudioGroupConfigs.asset @@ -0,0 +1,120 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 35a9ab9e2f42f744ca3801948d9b8c2e, type: 3} + m_Name: AudioGroupConfigs + m_EditorClassIdentifier: + m_GroupConfigs: + - m_Name: "\u97F3\u6548" + m_Mute: 0 + m_Volume: 1 + m_AgentHelperCount: 4 + m_ExposedVolumeParameter: SoundVolume + m_SpatialBlend: 1 + m_DopplerLevel: 1 + m_Spread: 0 + m_SourcePriority: 128 + m_ReverbZoneMix: 1 + m_OcclusionEnabled: 0 + m_OcclusionMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_OcclusionCheckInterval: 0.12 + m_OcclusionLowPassCutoff: 1200 + m_OcclusionVolumeMultiplier: 0.55 + AudioType: 0 + audioRolloffMode: 0 + minDistance: 2 + maxDistance: 80 + - m_Name: "\u754C\u9762\u97F3\u6548" + m_Mute: 0 + m_Volume: 1 + m_AgentHelperCount: 12 + m_ExposedVolumeParameter: UISoundVolume + m_SpatialBlend: 0 + m_DopplerLevel: 1 + m_Spread: 0 + m_SourcePriority: 128 + m_ReverbZoneMix: 1 + m_OcclusionEnabled: 0 + m_OcclusionMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_OcclusionCheckInterval: 0.12 + m_OcclusionLowPassCutoff: 1200 + m_OcclusionVolumeMultiplier: 0.55 + AudioType: 1 + audioRolloffMode: 0 + minDistance: 1 + maxDistance: 25 + - m_Name: "\u97F3\u4E50" + m_Mute: 0 + m_Volume: 1 + m_AgentHelperCount: 1 + m_ExposedVolumeParameter: MusicVolume + m_SpatialBlend: 0 + m_DopplerLevel: 1 + m_Spread: 0 + m_SourcePriority: 32 + m_ReverbZoneMix: 1 + m_OcclusionEnabled: 0 + m_OcclusionMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_OcclusionCheckInterval: 0.12 + m_OcclusionLowPassCutoff: 1200 + m_OcclusionVolumeMultiplier: 0.55 + AudioType: 2 + audioRolloffMode: 0 + minDistance: 1 + maxDistance: 25 + - m_Name: "\u8BED\u97F3" + m_Mute: 0 + m_Volume: 1 + m_AgentHelperCount: 6 + m_ExposedVolumeParameter: VoiceVolume + m_SpatialBlend: 1 + m_DopplerLevel: 1 + m_Spread: 0 + m_SourcePriority: 128 + m_ReverbZoneMix: 1 + m_OcclusionEnabled: 1 + m_OcclusionMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_OcclusionCheckInterval: 0.12 + m_OcclusionLowPassCutoff: 1200 + m_OcclusionVolumeMultiplier: 0.55 + AudioType: 3 + audioRolloffMode: 0 + minDistance: 2 + maxDistance: 80 + - m_Name: "\u73AF\u5883\u97F3" + m_Mute: 0 + m_Volume: 1 + m_AgentHelperCount: 6 + m_ExposedVolumeParameter: AmbientVolume + m_SpatialBlend: 1 + m_DopplerLevel: 1 + m_Spread: 0 + m_SourcePriority: 128 + m_ReverbZoneMix: 1 + m_OcclusionEnabled: 1 + m_OcclusionMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_OcclusionCheckInterval: 0.12 + m_OcclusionLowPassCutoff: 1200 + m_OcclusionVolumeMultiplier: 0.55 + AudioType: 4 + audioRolloffMode: 0 + minDistance: 2 + maxDistance: 80 diff --git a/Client/Assets/Books/EditorExtension/BaseTools.md.meta b/Client/Assets/AudioGroupConfigs.asset.meta similarity index 52% rename from Client/Assets/Books/EditorExtension/BaseTools.md.meta rename to Client/Assets/AudioGroupConfigs.asset.meta index 4cbc99d..4aa25be 100644 --- a/Client/Assets/Books/EditorExtension/BaseTools.md.meta +++ b/Client/Assets/AudioGroupConfigs.asset.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: 3cd21cd1bc02c44449bf7b999ae7bdd4 -TextScriptImporter: +guid: 7ae4699f0778d2441907aeccaf6c9b29 +NativeFormatImporter: externalObjects: {} + mainObjectFileID: 11400000 userData: assetBundleName: assetBundleVariant: diff --git a/Client/Assets/Books/EditorExtension/AtlasPostprocessor.md b/Client/Assets/Books/EditorExtension/AtlasPostprocessor.md deleted file mode 100644 index b4fb7ef..0000000 --- a/Client/Assets/Books/EditorExtension/AtlasPostprocessor.md +++ /dev/null @@ -1,83 +0,0 @@ -# EditorExtension Atlas 自动图集模块手册 - -## 模块概述 -该模块位于 `Editor/Postprocessor/Atlas`,用于维护图集配置、响应 Sprite 导入/删除、标记脏图集并执行全量生成。 - -## 适用场景 -- 以目录为单位自动生成 UI 图集。 -- 不同平台使用不同压缩格式。 -- 导入资源时自动更新图集状态。 - -## 快速上手 -### 打开配置窗口 -- 菜单:`Tools/Extension/图集工具/Configuration Panel` - -### 全量生成 -```csharp -EditorSpriteSaveInfo.ForceGenerateAll(); -``` - -## 模块组成 -- `AtlasConfiguration`:图集配置单例。 -- `AtlasConfigWindow`:配置窗口。 -- `EditorSpriteSaveInfo`:缓存、脏标记、全量生成。 -- `SpritePostprocessor`:接入 Unity 导入流程。 - -## 可调用 API -### 类型:`AtlasConfiguration` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/Postprocessor/Atlas/AtlasConfiguration.cs` - -#### 公开配置字段 -- `outputAtlasDir` -- `sourceAtlasRoot` -- `androidFormat` -- `iosFormat` -- `webglFormat` -- `padding` -- `enableRotation` -- `blockOffset` -- `tightPacking` -- `compressionQuality` -- `autoGenerate` -- `enableLogging` -- `enableV2` -- `excludeKeywords` -- `excludeFolders` - -示例: -```csharp -AtlasConfiguration.Instance.autoGenerate = true; -AtlasConfiguration.Save(true); -``` - -### 类型:`AtlasConfigWindow` -#### `public static void ShowWindow()` -- 作用:打开图集配置窗口。 - -### 类型:`EditorSpriteSaveInfo` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/Postprocessor/Atlas/EditorSpriteSaveInfo.cs` - -#### `public static void ConvertToSprite(string assetPath)` -- 作用:把资源路径转换或校正为 Sprite 导入设置。 - -#### `public static void OnImportSprite(string assetPath)` -- 作用:处理 Sprite 导入。 - -#### `public static void OnDeleteSprite(string assetPath)` -- 作用:处理 Sprite 删除。 - -#### `public static void ForceGenerateAll()` -- 作用:全量生成图集。 - -#### `public static void ClearCache()` -- 作用:清理模块缓存。 - -#### `public static void MarkParentAtlasesDirty(string assetPath)` -- 作用:标记资源所在父图集为脏。 - -### 类型:`SpritePostprocessor` -- 说明:继承 `AssetPostprocessor`,作为导入事件入口,本身无额外公开调用接口。 - -## 注意事项 -- 建议原图目录与输出目录严格分离。 -- 开启自动生成时,排除目录和关键字要统一维护。 diff --git a/Client/Assets/Books/EditorExtension/AtlasPostprocessor.md.meta b/Client/Assets/Books/EditorExtension/AtlasPostprocessor.md.meta deleted file mode 100644 index 412c7b7..0000000 --- a/Client/Assets/Books/EditorExtension/AtlasPostprocessor.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: edc716e06a048f240a9f9bda8ff156ce -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/EditorExtension/BaseTools.md b/Client/Assets/Books/EditorExtension/BaseTools.md deleted file mode 100644 index 9ada900..0000000 --- a/Client/Assets/Books/EditorExtension/BaseTools.md +++ /dev/null @@ -1,87 +0,0 @@ -# EditorExtension 基础工具模块手册 - -## 模块概述 -基础工具模块由 `EditorIcons` 和 `EditorToolFunctionAttribute` 两部分组成: -- `EditorIcons`:浏览、检索、预览并导出 Unity 内置编辑器图标。 -- `EditorToolFunctionAttribute`:把静态方法声明为“编辑器快捷工具”,供 `EditorQuickToolBar` 自动收集。 - -## 适用场景 -- 快速查找 Unity 内置图标。 -- 将团队常用编辑器方法集中到工具栏菜单。 -- 避免为每个工具重复创建顶级菜单。 - -## 快速上手 -### 打开图标浏览器 -- 菜单:`Tools/Extension/Editor Icons` - -### 声明工具函数 -```csharp -using AlicizaX.Editor.Extension; -using UnityEngine; - -public static class DemoEditorTools -{ - [EditorToolFunction("Demo/打印当前场景", 10)] - public static void PrintScene() - { - Debug.Log(UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().path); - } -} -``` - -## 详细使用说明 -### `EditorIcons` -- 支持关键字搜索。 -- 支持大小图标切换。 -- 支持单个导出 PNG。 -- 支持批量导出全部图标到目录。 - -### `EditorToolFunctionAttribute` -- 仅扫描静态方法。 -- 扫描时机为编辑器加载阶段。 -- 扫描结果按 `MenuOrder` 升序排列。 -- 建议被标记的方法不要带参数。 - -## 可调用 API -### 类型:`EditorIcons` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/EditorIcons.cs` - -#### `public static void EditorIconsOpen()` -- 作用:打开图标浏览窗口。 -- 参数:无。 -- 返回值:`void`。 - -#### `public static string[] ico_list` -- 作用:内置图标名称列表。 -- 示例: -```csharp -foreach (var iconName in EditorIcons.ico_list) -{ - Debug.Log(iconName); -} -``` - -### 类型:`AlicizaX.Editor.Extension.EditorToolFunctionAttribute` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/EditorToolFunctionAttribute.cs` - -#### `public EditorToolFunctionAttribute(string menu, int menuOrder = 0)` -- 作用:声明工具菜单路径与排序值。 -- 参数: - - `menu`:菜单路径。 - - `menuOrder`:排序值。 - -#### `public string ToolMenuPath { get; }` -- 作用:获取菜单路径。 - -#### `public int MenuOrder { get; }` -- 作用:获取排序值。 - -#### `public MethodInfo MethodInfo { get; }` -- 作用:获取实际绑定的方法信息。 - -#### `public void SetMethodInfo(MethodInfo methodInfo)` -- 作用:由收集器在扫描时写入目标方法。 - -## 集成建议 -- 推荐把所有项目编辑器命令统一声明为 `EditorToolFunctionAttribute`。 -- 图标挑选可先通过 `EditorIcons` 查名,再用于按钮和菜单项。 diff --git a/Client/Assets/Books/EditorExtension/CustomToolbarItems.md b/Client/Assets/Books/EditorExtension/CustomToolbarItems.md deleted file mode 100644 index 5d1fe83..0000000 --- a/Client/Assets/Books/EditorExtension/CustomToolbarItems.md +++ /dev/null @@ -1,55 +0,0 @@ -# EditorExtension 自定义工具栏项目模块手册 - -## 模块概述 -`ToolBarExtension` 目录在 `Toolbar` 框架之上实现了一组项目级工具栏元素: -- `EditorQuickToolBar` -- `LocalizationDropdownField` -- `ResourceModeDropdownField` -- `SwitchSceneToolBar` -- `BuildSettingWindow`(当前源码为注释模板) - -## 适用场景 -- 快速切换语言、资源模式与场景。 -- 把项目工具统一收口到主工具栏。 -- 自动调度 `EditorToolFunctionAttribute` 标记的方法。 - -## 快速上手 -这些类型均通过 `MainToolbarElementAttribute` 自动挂载,无需手动注册。 - -## 可调用 API -### 类型:`AlicizaX.Editor.Extension.EditorQuickToolBar` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/ToolBarExtension/EditorQuickToolBar.cs` -- `InitializeElement()`:初始化工具下拉按钮。 - -### 类型:`LocalizationDropdownField` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/ToolBarExtension/LocalizationDropdownField.cs` -- `InitializeElement()`:初始化语言切换下拉框。 -- `InvokeOnValidateInScene()`:强制刷新场景中所有 `UXTextMeshPro` 的编辑器预览。 - -### 类型:`AlicizaX.Editor.Extension.ResourceModeDropdownField` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/ToolBarExtension/ResourceModeDropdownField.cs` -- `InitializeElement()`:初始化资源模式下拉框。 - -### 类型:`SwitchSceneToolBar` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/ToolBarExtension/SwitchSceneToolBar.cs` -- `InitializeElement()`:初始化场景切换按钮并监听场景切换。 - -### 类型:`BuildSettingWindow` -- 说明:当前源码整体注释,无生效公开 API,可作为未来扩展模板。 - -## 详细使用说明 -### `EditorQuickToolBar` -- 从 `EditorToolFunctionAttributeCollector.Attributes` 读取菜单项。 -- 点击菜单后通过反射执行静态方法。 - -### `LocalizationDropdownField` -- 读取语言列表并写入 `EditorPrefs`。 -- 切换后会刷新场景里的 `UXTextMeshPro` 文本预览。 - -### `ResourceModeDropdownField` -- 提供 `Editor / Offline / Host / Webgl` 四种模式。 -- 模式结果通过 `EditorPrefs` 持久化。 - -### `SwitchSceneToolBar` -- 默认扫描 `Assets/Bundles/` 与 `Assets/Scenes/` 下的场景。 -- 切换前会检查当前场景未保存状态。 diff --git a/Client/Assets/Books/EditorExtension/CustomToolbarItems.md.meta b/Client/Assets/Books/EditorExtension/CustomToolbarItems.md.meta deleted file mode 100644 index c278d9e..0000000 --- a/Client/Assets/Books/EditorExtension/CustomToolbarItems.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 19d768fd857ab3d42887f3cddd38bf37 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/EditorExtension/HybridCLR.md b/Client/Assets/Books/EditorExtension/HybridCLR.md deleted file mode 100644 index 2813b36..0000000 --- a/Client/Assets/Books/EditorExtension/HybridCLR.md +++ /dev/null @@ -1,74 +0,0 @@ -# EditorExtension HybridCLR 模块手册 - -## 模块概述 -`HybridCLR` 模块封装热更新 DLL 构建、AOT 补充 DLL 复制、热更 DLL 复制与宏开关。核心入口为 `BuildDLLCommand`。 - -## 适用场景 -- 一键编译并拷贝热更新 DLL。 -- 启用/关闭 `ENABLE_HYBRIDCLR`。 -- 输出 `.dll.bytes` 供运行时加载。 - -## 快速上手 -### 菜单 -- `HybridCLR/Tools/Define Symbols/Disable HybridCLR` -- `HybridCLR/Tools/Define Symbols/Enable HybridCLR` -- `HybridCLR/Tools/BuildAssets And CopyTo AssemblyTextAssetPath` - -### 代码 -```csharp -using UnityEditor; - -BuildDLLCommand.Enable(); -BuildDLLCommand.BuildAndCopyDlls(EditorUserBuildSettings.activeBuildTarget); -``` - -## 工作流程 -1. 通过 `Enable()` / `Disable()` 控制宏与 HybridCLR 设置。 -2. `BuildAndCopyDlls()` 编译热更程序集。 -3. `CopyAOTHotUpdateDlls()` 复制 AOT 与 HotUpdate DLL。 -4. `AssetDatabase.Refresh()` 刷新资源库。 - -## 可调用 API -源码:`Packages/com.alicizax.unity.editor.extension/Editor/HybridCLR/BuildDLLCommand.cs` - -### 类型:`BuildDLLCommand` -#### `public static string AssemblyTextAssetPath` -- 作用:目标输出目录。 - -#### `public static void Disable()` -- 作用:关闭 HybridCLR 宏与设置。 - -#### `public static void Enable()` -- 作用:启用 HybridCLR 宏与设置。 - -#### `public static void BuildAndCopyDlls()` -- 作用:按当前激活平台编译并复制 DLL。 - -#### `public static void GenerateAll()` -- 作用:执行 `PrebuildCommand.GenerateAll()`。 - -#### `public static void BuildAndCopyDlls(BuildTarget target)` -- 作用:按指定平台编译并复制 DLL。 - -#### `public static void CopyAOTHotUpdateDlls(BuildTarget target)` -- 作用:统一复制 AOT 与 HotUpdate DLL。 - -#### `public static void CopyAOTAssembliesToAssetPath()` -- 作用:复制 AOT 补充程序集到输出目录。 - -#### `public static void CopyHotUpdateAssembliesToAssetPath()` -- 作用:复制 HotUpdate 程序集到输出目录。 - -## 示例 -```csharp -[UnityEditor.MenuItem("Tools/HybridCLR/全部生成")] -public static void BuildAll() -{ - BuildDLLCommand.GenerateAll(); - BuildDLLCommand.BuildAndCopyDlls(UnityEditor.EditorUserBuildSettings.activeBuildTarget); -} -``` - -## 注意事项 -- 依赖 HybridCLR 包和相关生成脚本完整可用。 -- AOT 源目录通常需要先执行生成或构建流程后才存在。 diff --git a/Client/Assets/Books/EditorExtension/HybridCLR.md.meta b/Client/Assets/Books/EditorExtension/HybridCLR.md.meta deleted file mode 100644 index 5296656..0000000 --- a/Client/Assets/Books/EditorExtension/HybridCLR.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: efaf24517e127234183a8852e2de38db -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/EditorExtension/ReferenceFinder.md b/Client/Assets/Books/EditorExtension/ReferenceFinder.md deleted file mode 100644 index d1bcca2..0000000 --- a/Client/Assets/Books/EditorExtension/ReferenceFinder.md +++ /dev/null @@ -1,103 +0,0 @@ -# EditorExtension ReferenceFinder 引用分析模块手册 - -## 模块概述 -`ReferenceFinder` 用于分析项目资源的依赖与反向引用关系,支持缓存、树状展示和排序。 - -## 适用场景 -- 查询资源被谁引用。 -- 清理无用资源前做依赖确认。 -- 排查深层依赖链和循环引用风险。 - -## 快速上手 -```csharp -using TEngine.Editor; - -ResourceReferenceInfo.FindRef(); -``` - -## 模块结构 -- `ResourceReferenceInfo`:入口与界面层。 -- `ReferenceFinderData`:采集、缓存、状态管理。 -- `AssetTreeView`:树状显示。 -- `SortHelper`:排序逻辑。 - -## 可调用 API -### 类型:`TEngine.Editor.ReferenceFinderData` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/ReferenceFinder/ReferenceFinderData.cs` - -#### 公开成员 -- `AssetState` -- `MinThreadCount` -- `allAssets` -- `assetDict` -- `CollectDependenciesInfo()` -- `ReadAssetInfo()` -- `GetAsset(string dataPath, string assetPath)` -- `ReadFromCache()` -- `UpdateAssetState(string guid)` -- `GetInfoByState(AssetState state)` -- `ClearCache()` -- `GetRefCount(AssetDescription desc, AssetDescription parentDesc)` - -#### `AssetDescription` 字段 -- `assetDependencyHashString` -- `dependencies` -- `name` -- `path` -- `references` -- `state` - -### 类型:`TEngine.Editor.ResourceReferenceInfo` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/ReferenceFinder/ResourceReferenceInfo.cs` - -#### 公开成员 -- `Data` -- `needUpdateAssetTree` -- `needUpdateState` -- `selectedAssetGuid` -- `mAssetTreeView` -- `FindRef()` -- `DrawOptionBar()` - -### 类型:`AssetTreeView` -- `AssetTreeView(TreeViewState state, MultiColumnHeader multicolumnHeader)` -- `SortExpandItem()` -- `CreateDefaultMultiColumnHeaderState(float treeViewWidth, bool isDepend)` - -### 类型:`ClickColumn` -- `SortInColumn` -- `SortWithIndex` -- `ClickColumn(MultiColumnHeaderState state)` -- `SortByName()` -- `SortByPath()` - -### 类型:`DragAreaGetObject` -- `GetObjects(string meg = null)` - -### 类型:`SortHelper` -- `Init()` -- `ChangeSortType(...)` -- `SortByName()` -- `SortByPath()` -- `SortChild(...)` -- `NormalSort(...)` -- `FastSort(...)` -- `CompareWithName(...)` -- `CompareWithNameDesc(...)` -- `CompareWithPath(...)` -- `CompareWithPathDesc(...)` - -### 其他公开类型 -- `ListInfo` -- `SortType` -- `SortConfig` - -## 示例 -```csharp -var data = new TEngine.Editor.ReferenceFinderData(); -data.CollectDependenciesInfo(); -``` - -## 注意事项 -- 大项目建议优先使用缓存。 -- 资源大规模变更后建议先清缓存再重新采集。 diff --git a/Client/Assets/Books/EditorExtension/ReferenceFinder.md.meta b/Client/Assets/Books/EditorExtension/ReferenceFinder.md.meta deleted file mode 100644 index 2948202..0000000 --- a/Client/Assets/Books/EditorExtension/ReferenceFinder.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 6e2acda8e9c7959409e0c16d83cc852f -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/EditorExtension/TexturePacker.md b/Client/Assets/Books/EditorExtension/TexturePacker.md deleted file mode 100644 index 308b132..0000000 --- a/Client/Assets/Books/EditorExtension/TexturePacker.md +++ /dev/null @@ -1,42 +0,0 @@ -# EditorExtension TexturePacker 模块手册 - -## 模块概述 -`TexturePacker` 提供一个轻量级编辑器窗口 `UnityTexturePackerEditor`,用于把多张纹理打包为一张大图并记录子图布局信息。 - -## 适用场景 -- 临时合并散图。 -- 验证贴图布局。 -- 做非正式生产链路的编辑器打包。 - -## 快速上手 -```csharp -UnityTexturePackerEditor.ShowWindow(); -``` - -## 可调用 API -源码:`Packages/com.alicizax.unity.editor.extension/Editor/TexturePacker/UnityTexturePackEditor.cs` - -### 类型:`UnityTexturePackerEditor` -#### `public static void ShowWindow()` -- 作用:打开 TexturePacker 窗口。 - -### 公开布局信息字段 -- `name` -- `sourcePath` -- `x` -- `y` -- `w` -- `h` -- `sourceW` -- `sourceH` - -### 公开导入备份字段 -- `path` -- `isReadable` -- `type` -- `compression` -- `maxSize` - -## 使用建议 -- 如果项目已有正式 SpriteAtlas 流程,该工具更适合临时处理和验证。 -- 执行前后应注意纹理导入配置恢复。 diff --git a/Client/Assets/Books/EditorExtension/TexturePacker.md.meta b/Client/Assets/Books/EditorExtension/TexturePacker.md.meta deleted file mode 100644 index 00070fd..0000000 --- a/Client/Assets/Books/EditorExtension/TexturePacker.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: fd5b62c9141e9654ba29b499d0888dde -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/EditorExtension/ToolbarExtender.md b/Client/Assets/Books/EditorExtension/ToolbarExtender.md deleted file mode 100644 index b9dad4b..0000000 --- a/Client/Assets/Books/EditorExtension/ToolbarExtender.md +++ /dev/null @@ -1,98 +0,0 @@ -# EditorExtension Toolbar 扩展框架手册 - -## 模块概述 -`Toolbar` 模块用于向 Unity 主工具栏动态注入自定义 `VisualElement` / `IMGUIContainer`,支持自动发现、排序、对齐、推荐样式、分组与用户覆盖配置。 - -## 适用场景 -- 给 Unity 顶部工具栏挂项目级入口。 -- 统一管理开发、调试、资源、场景、构建工具。 -- 让工具栏元素支持分组、显隐与个性化排序。 - -## 快速上手 -```csharp -using Paps.UnityToolbarExtenderUIToolkit; -using UnityEngine.UIElements; - -[MainToolbarElement("DemoToolbar", ToolbarAlign.Right, 100)] -public class DemoToolbarElement : IMGUIContainer -{ - public void InitializeElement() - { - onGUIHandler = () => UnityEngine.GUILayout.Label("Demo"); - } -} -``` - -## 工作机制 -1. `MainToolbar` 定位 Unity 原生 Toolbar 容器。 -2. `MainToolbarAutomaticExtender` 负责刷新与注入。 -3. 特性仓储扫描带 `MainToolbarElementAttribute` 的类型。 -4. 元素实例化后包装为 `MainToolbarElement`。 -5. 控制面板和覆盖配置负责用户态管理。 - -## 可调用 API -### 类型:`MainToolbarElementAttribute` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/Toolbar/MainToolbarElementAttribute.cs` - -- `MainToolbarElementAttribute(string id, ToolbarAlign alignment = ToolbarAlign.Left, int order = 0, bool useRecommendedStyles = true)` -- `Id` -- `Alignment` -- `Order` -- `UseRecommendedStyles` - -### 类型:`MainToolbarElement` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/Toolbar/MainToolbarElement.cs` - -- `Id` -- `VisualElement` -- `Alignment` -- `Order` -- `UseRecommendedStyles` -- 构造函数 `MainToolbarElement(...)` - -### 类型:`MainToolbar` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/Toolbar/MainToolbar.cs` - -- `OnInitialized` -- `OnRefresh` -- `UnityToolbarRoot` -- `LeftContainer` -- `CenterContainer` -- `RightContainer` -- `PlayModeButtonsContainer` -- `IsAvailable` - -### 类型:`MainToolbarAutomaticExtender` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/Toolbar/MainToolbarAutomaticExtender.cs` - -- `OnRefresh` -- `OnAddedCustomContainersToToolbar` - -### 类型:`MainToolbarControlPanelWindow` -源码:`Packages/com.alicizax.unity.editor.extension/Editor/Toolbar/ControlPanelWindow/MainToolbarControlPanelWindow.cs` - -- `OpenWindow()` - -### 类型:`MainToolbarElementController` -- `Id` -- `ControlledVisualElement` -- `HoldsAGroup` -- `HoldsANativeElement` - -### 其他重要公开类型 -- `ToolbarAlign` -- `GroupDefinition` -- `GroupElement` -- `ScriptableGroupDefinition` -- `MainToolbarElementDropdownAttribute` -- `MainToolbarElementOverride` -- `RecommendedStyle` 及派生类 -- `Variable` / `SerializableVariable` 等变量序列化类型 - -## 打开控制面板 -- 菜单:`Tools/Extension/Toolbar/Main Toolbar Control Panel` -- 代码:`MainToolbarControlPanelWindow.OpenWindow();` - -## 注意事项 -- `Id` 应稳定且全局唯一。 -- 自定义元素建议在 `InitializeElement()` 中做依赖判空。 diff --git a/Client/Assets/Books/EditorExtension/ToolbarExtender.md.meta b/Client/Assets/Books/EditorExtension/ToolbarExtender.md.meta deleted file mode 100644 index fe32e3e..0000000 --- a/Client/Assets/Books/EditorExtension/ToolbarExtender.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 7f5ebb19e8045f24c8f0de3a2218fabb -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Core.meta b/Client/Assets/Books/Framework/Core.meta deleted file mode 100644 index 5b5f789..0000000 --- a/Client/Assets/Books/Framework/Core.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: cbb4b30bf931b134094b7d4d196ce68f -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Core/Event.md b/Client/Assets/Books/Framework/Core/Event.md deleted file mode 100644 index a40da97..0000000 --- a/Client/Assets/Books/Framework/Core/Event.md +++ /dev/null @@ -1,182 +0,0 @@ -# Event - -## 模块概述 - -Event 模块提供轻量级、低分配的结构体事件系统,适合模块解耦、状态广播、UI/玩法/流程间通知,以及编辑器内订阅行为监控。 - -设计重点: - -- 使用 `struct` 事件降低 GC 压力 -- 通过泛型容器按事件类型隔离订阅列表 -- 使用调试注册表记录订阅、发布、扩容等行为 - -## 快速开始 - -1. 定义 `struct` 事件并实现 `IEventArgs` -2. 调用 `EventBus.Subscribe()` 订阅 -3. 调用 `EventBus.Publish(in evt)` 发布 -4. 在对象失活或销毁时释放订阅句柄 - -```csharp -using AlicizaX; - -public struct PlayerLevelUpEvent : IEventArgs -{ - public int NewLevel; -} - -public sealed class EventQuickStart -{ - private EventRuntimeHandle _handle; - - public void Initialize() - { - _handle = EventBus.Subscribe(OnLevelUp); - EventBus.Publish(new PlayerLevelUpEvent { NewLevel = 10 }); - } - - private void OnLevelUp(PlayerLevelUpEvent evt) - { - Log.Info($"Level => {evt.NewLevel}"); - } -} -``` - -## 架构说明 - -```text -业务代码 - ├─ EventBus.Subscribe() - ├─ EventBus.Publish() - └─ EventBus.Clear() - ↓ - EventContainer - ↓ - EventRuntimeHandle / 订阅槽 - ↓ - EventDebugRegistry -``` - -## 核心类型说明 - -### `IEventArgs` - -- 所有事件载体的标记接口 -- 事件类型必须是 `struct` - -### `EventBus` - -公开能力: - -- `Subscribe(Action handler)` -- `Publish(in T evt)` -- `GetSubscriberCount()` -- `EnsureCapacity(int capacity)` -- `Clear()` - -### `EventRuntimeHandle` - -- 表示一次已建立的订阅 -- 用于解绑或自动管理订阅生命周期 - -### `EventInitialSize` - -- 用于为某类事件声明初始订阅容量 -- 高频事件建议预热容量 - -## API 参考 - -### `EventBus.Subscribe(Action handler) where T : struct, IEventArgs` - -- 必填参数:`handler` -- 返回值:`EventRuntimeHandle` -- 泛型约束:`T : struct, IEventArgs` -- 说明:订阅某种事件 -- 注意:建议保存返回句柄,便于后续解绑 - -### `EventBus.Publish(in T evt) where T : struct, IEventArgs` - -- 必填参数:`evt` -- 返回值:无 -- 说明:同步发布事件 -- 注意:回调会立即执行,发布链路中不要写过重逻辑 - -### `EventBus.GetSubscriberCount()` - -- 返回值:`int` -- 说明:返回当前事件订阅数量 - -### `EventBus.EnsureCapacity(int capacity)` - -- 必填参数:`capacity` -- 返回值:无 -- 说明:预热内部容器容量 - -### `EventBus.Clear()` - -- 返回值:无 -- 说明:清空某类事件订阅 - -## 完整使用示例 - -```csharp -using AlicizaX; -using UnityEngine; - -public struct CoinChangedEvent : IEventArgs -{ - public int CurrentValue; - public int Delta; -} - -public sealed class EventExample : MonoBehaviour -{ - private EventRuntimeHandle _handle; - private int _coin; - - private void OnEnable() - { - EventBus.EnsureCapacity(8); - _handle = EventBus.Subscribe(OnCoinChanged); - } - - private void Update() - { - if (Input.GetKeyDown(KeyCode.C)) - { - _coin += 100; - EventBus.Publish(new CoinChangedEvent - { - CurrentValue = _coin, - Delta = 100 - }); - } - } - - private void OnDisable() - { - _handle.Dispose(); - } - - private void OnCoinChanged(CoinChangedEvent evt) - { - Debug.Log($"Coin changed: {evt.CurrentValue} (+{evt.Delta})"); - } -} -``` - -## 最佳实践 - -- 事件体尽量小,避免塞入大对象 -- 高频事件预热容量 -- 订阅与释放时机成对出现 - -## 常见错误 - -- 事件类型定义成 `class` -- 只订阅不解绑 - -## 性能注意事项 - -- `Publish` 为同步执行,避免重逻辑 -- 结合编辑器事件监视器检查扩容次数和订阅抖动 diff --git a/Client/Assets/Books/Framework/Core/Event.md.meta b/Client/Assets/Books/Framework/Core/Event.md.meta deleted file mode 100644 index 306742f..0000000 --- a/Client/Assets/Books/Framework/Core/Event.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: c7741236daa2b2949b67f42a855ae3e3 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Core/Foundation.md b/Client/Assets/Books/Framework/Core/Foundation.md deleted file mode 100644 index 5f92fe1..0000000 --- a/Client/Assets/Books/Framework/Core/Foundation.md +++ /dev/null @@ -1,186 +0,0 @@ -# Foundation - -## 模块概述 - -Foundation 是整个框架的运行时基础层,负责: - -- 应用根节点初始化 -- 服务容器与作用域管理 -- 公共异常、内存池、数据结构 -- 全局访问入口与运行时基础配置 -- 模块动态绑定与启动时配置注入 - -如果把整个包看成一棵树,Foundation 就是树根;其他模块都建立在它提供的服务世界之上。 - -## 快速开始 - -### 最少步骤 - -1. 在启动场景挂载 `RootModule` -2. 让各功能模块组件与 `RootModule` 位于同一生命周期体系内 -3. 使用 `GameApp` 或 `AppServices` 获取模块服务 - -### 最小示例 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class FoundationQuickStart : MonoBehaviour -{ - private void Start() - { - RootModule root = GameApp.Base; - root.FrameRate = 120; - root.GameSpeed = 1f; - - Debug.Log($"Has world: {AppServices.HasWorld}"); - } -} -``` - -## 架构说明 - -```text -RootModule - └─ AppServiceRoot - └─ ServiceWorld - ├─ App Scope - ├─ Scene Scope - └─ Gameplay Scope -``` - -### 各部分职责 - -- `RootModule`:全局根节点,负责运行参数和全局清理 -- `AppServiceRoot`:创建 `ServiceWorld` 并驱动 Tick -- `ServiceWorld`:持有多个 `ServiceScope` -- `ServiceScope`:某个作用域下的服务容器 -- `AppServices`:统一静态访问门面 -- `GameApp`:高频模块快捷入口 - -## 核心类型说明 - -### `RootModule` - -关键属性: - -- `FrameRate`:目标帧率 -- `GameSpeed`:时间缩放 -- `IsGamePaused`:是否暂停 -- `RunInBackground`:后台运行 -- `NeverSleep`:禁止休眠 - -关键方法: - -- `PauseGame()` -- `ResumeGame()` -- `ResetNormalGameSpeed()` - -### `AppServices` - -常用成员: - -- `HasWorld` / `HasScene` / `HasGameplay` -- `App` / `Scene` / `Gameplay` -- `EnsureWorld()` / `EnsureScene()` / `EnsureGameplay()` -- `Require()` -- `TryGet(out T service)` -- `Shutdown()` - -### `GameApp` - -公开入口: - -- `Base` -- `Audio` -- `Localization` -- `ObjectPool` -- `Procedure` -- `Resource` -- `Scene` -- `Timer` -- `UI` - -### `ModuleDynamicBind` - -作用: - -- 在非 Editor 环境中读取 `ModuleDynamicBindInfo` -- 对 `ResourceComponent`、`DebuggerComponent`、`LocalizationComponent` 注入启动配置 - -## API 参考 - -### `AppServices.Require() where T : class, IService` - -- 参数:无 -- 返回值:`T` -- 泛型约束:`class, IService` -- 说明:强制获取已注册服务 -- 异常:服务世界未创建或服务未注册时失败 - -### `AppServices.TryGet(out T service) where T : class, IService` - -- 输出参数:`service` -- 返回值:`bool` -- 说明:安全查询服务 - -### `AppServices.EnsureWorld(int appScopeOrder = ServiceDomainOrder.App)` - -- 可选参数:`appScopeOrder` -- 返回值:`ServiceWorld` - -### `RootModule.PauseGame()` - -- 返回值:无 -- 说明:保存当前速度并暂停 - -### `RootModule.ResumeGame()` - -- 返回值:无 -- 说明:恢复暂停前速度 - -## 完整使用示例 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class FoundationExample : MonoBehaviour -{ - private void Start() - { - RootModule root = GameApp.Base; - root.FrameRate = 120; - root.RunInBackground = true; - root.NeverSleep = true; - } - - private void Update() - { - if (Input.GetKeyDown(KeyCode.P)) - { - if (GameApp.Base.IsGamePaused) - GameApp.Base.ResumeGame(); - else - GameApp.Base.PauseGame(); - } - } -} -``` - -## 最佳实践 - -- 只保留一个 `RootModule` -- 首场景统一挂载框架组件 -- 常用服务走 `GameApp`,扩展服务走 `AppServices` - -## 常见错误 - -- 服务未注册就访问 -- 多个 `RootModule` 并存 - -## 性能注意事项 - -- 不要频繁重建服务世界 -- 高频访问模块时优先使用 `GameApp` 的缓存入口 diff --git a/Client/Assets/Books/Framework/Core/Foundation.md.meta b/Client/Assets/Books/Framework/Core/Foundation.md.meta deleted file mode 100644 index 3b4c434..0000000 --- a/Client/Assets/Books/Framework/Core/Foundation.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 91578f5be03b1de4dbac802cab59cb24 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Core/GameObjectPool.md b/Client/Assets/Books/Framework/Core/GameObjectPool.md deleted file mode 100644 index df9cacf..0000000 --- a/Client/Assets/Books/Framework/Core/GameObjectPool.md +++ /dev/null @@ -1,98 +0,0 @@ -# GameObjectPool - -## 模块概述 - -GameObjectPool 面向场景对象与预制体实例复用。它基于 `PoolConfig` 决定某个资源是否入池、如何匹配、如何预热,并可通过 `Resources` 或 AssetBundle/YooAsset 进行加载。 - -## 快速开始 - -1. 场景挂载 `GameObjectPoolManager` -2. 准备 `PoolConfigScriptableObject` -3. 确保 `ResourceComponent` 已注册 -4. 使用 `PreloadAsync` / `GetGameObjectAsync` / `Release` - -## 架构说明 - -```text -GameObjectPoolManager - ├─ PoolConfigScriptableObject - ├─ PoolConfig - ├─ RuntimePrefabPool - └─ IResourceLoader -``` - -## 核心类与接口 - -### `GameObjectPoolManager` - -主要能力: - -- `GetGameObject(...)` -- `GetGameObjectAsync(...)` -- `Preload(...)` -- `PreloadAsync(...)` -- `Release(GameObject gameObject)` -- `ClearAllPools()` -- `ForceCleanup()` - -### `PoolConfig` - -决定: - -- 资源匹配方式 -- 预热数量 -- 清理与过期策略 -- 使用哪种资源加载器 - -## API 参考 - -### `GetGameObject(string assetPath, Transform parent = null)` - -- 必填参数:`assetPath` -- 可选参数:`parent` -- 返回值:`GameObject` -- 异常:初始化未完成时会抛异常 - -### `GetGameObjectAsync(string assetPath, Transform parent = null, CancellationToken cancellationToken = default)` - -- 必填参数:`assetPath` -- 可选参数:`parent` -- 可选参数:`cancellationToken` -- 返回值:`UniTask` - -### `Release(GameObject gameObject)` - -- 必填参数:`gameObject` -- 返回值:无 - -## 完整使用示例 - -```csharp -using AlicizaX; -using Cysharp.Threading.Tasks; -using UnityEngine; - -public sealed class GameObjectPoolExample : MonoBehaviour -{ - [SerializeField] private GameObjectPoolManager poolManager; - - private async UniTaskVoid Start() - { - await poolManager.PreloadAsync("Assets/Bundles/Effects/Fx_Hit.prefab", 3); - - GameObject fx = await poolManager.GetGameObjectAsync("Assets/Bundles/Effects/Fx_Hit.prefab", transform); - await UniTask.Delay(1000); - poolManager.Release(fx); - } -} -``` - -## 最佳实践 - -- 特效、子弹、临时场景装饰物优先走该模块 -- 常见资源在初始化阶段预热 - -## 常见错误 - -- 在初始化完成前调用同步接口 -- 对池对象手动 `Destroy` diff --git a/Client/Assets/Books/Framework/Core/GameObjectPool.md.meta b/Client/Assets/Books/Framework/Core/GameObjectPool.md.meta deleted file mode 100644 index 76a70f4..0000000 --- a/Client/Assets/Books/Framework/Core/GameObjectPool.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: fb63f330ed43037448a8c927045727c5 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Core/ObjectPool.md b/Client/Assets/Books/Framework/Core/ObjectPool.md deleted file mode 100644 index 71121e8..0000000 --- a/Client/Assets/Books/Framework/Core/ObjectPool.md +++ /dev/null @@ -1,204 +0,0 @@ -# ObjectPool - -## 模块概述 - -ObjectPool 是面向纯 C# 对象的通用对象池模块,适合缓存临时数据对象、资源包装对象、UI/资源扩展辅助对象和调试器内部缓存对象。 - -它与 `MemoryPool` 的区别: - -- `MemoryPool` 更偏向单个对象快速申请/释放 -- `ObjectPool` 更偏向带策略的池管理,支持容量、过期、优先级、锁定等行为 - -## 快速开始 - -1. 场景挂载 `ObjectPoolComponent` -2. 定义继承 `ObjectBase` 的对象类型 -3. 使用 `GameApp.ObjectPool.CreatePool()` -4. `Register` / `Spawn` / `Unspawn` - -```csharp -using AlicizaX; -using AlicizaX.ObjectPool; - -public sealed class CacheItem : ObjectBase -{ - public string Key; - - public override void Clear() - { - Key = null; - } -} - -public sealed class ObjectPoolQuickStart -{ - public void Run() - { - var pool = GameApp.ObjectPool.CreatePool(ObjectPoolCreateOptions.Single("Cache")); - CacheItem item = MemoryPool.Acquire(); - item.Key = "A"; - pool.Register(item, false); - - CacheItem spawned = pool.Spawn(); - pool.Unspawn(spawned); - } -} -``` - -## 架构说明 - -```text -ObjectPoolComponent - └─ ObjectPoolService - ├─ IObjectPool - ├─ ObjectPoolBase - └─ ObjectBase -``` - -## 核心类型说明 - -### `IObjectPoolService` - -负责: - -- 创建对象池 -- 查询对象池 -- 销毁对象池 -- 统一释放未使用对象 - -### `IObjectPool` - -常用公开方法: - -- `Register` -- `Spawn` -- `Unspawn` -- `SetLocked` -- `SetPriority` -- `Release` -- `ReleaseAllUnused` - -### `ObjectBase` - -- 池内对象基类 -- 需要正确实现 `Clear()` - -### `ObjectPoolCreateOptions` - -主要参数: - -- `name` -- `allowMultiSpawn` -- `autoReleaseInterval` -- `capacity` -- `expireTime` -- `priority` - -## API 参考 - -### `IObjectPoolService.CreatePool(ObjectPoolCreateOptions options = default) where T : ObjectBase` - -- 可选参数:`options` -- 返回值:`IObjectPool` -- 泛型约束:`T : ObjectBase` - -### `IObjectPool.Register(T obj, bool spawned)` - -- 必填参数:`obj` -- 必填参数:`spawned` -- 返回值:无 - -### `IObjectPool.Spawn()` - -- 返回值:`T` -- 说明:取出一个可用对象;无可用对象时通常返回 `null` - -### `IObjectPool.Unspawn(T obj)` - -- 必填参数:`obj` -- 返回值:无 - -### `IObjectPool.ReleaseAllUnused()` - -- 返回值:无 -- 说明:释放当前池中未使用对象 - -## 完整使用示例 - -```csharp -using AlicizaX; -using AlicizaX.ObjectPool; -using UnityEngine; - -public sealed class DamageTextData : ObjectBase -{ - public int Value; - public Color Color; - - public void Init(int value, Color color) - { - Value = value; - Color = color; - } - - public override void Clear() - { - Value = 0; - Color = default; - } -} - -public sealed class ObjectPoolExample : MonoBehaviour -{ - private IObjectPool _pool; - - private void Start() - { - _pool = GameApp.ObjectPool.CreatePool( - new ObjectPoolCreateOptions( - name: "DamageText", - allowMultiSpawn: false, - autoReleaseInterval: 30f, - capacity: 64, - expireTime: 120f, - priority: 0)); - - for (int i = 0; i < 10; i++) - { - DamageTextData data = MemoryPool.Acquire(); - data.Init(i, Color.red); - _pool.Register(data, false); - } - } - - public void ShowDamage(int damage) - { - DamageTextData data = _pool.Spawn(); - if (data == null) - { - data = MemoryPool.Acquire(); - } - - data.Init(damage, Color.yellow); - Debug.Log($"Damage => {data.Value}"); - _pool.Unspawn(data); - } -} -``` - -## 最佳实践 - -- 对象应保持“可完全重置” -- 通过 `ObjectPoolCreateOptions` 明确池策略 -- 大量短生命周期对象优先考虑对象池 - -## 常见错误 - -- 把 `GameObject` 放入该池 -- `Clear()` 未重置字段 -- 未区分 `Register(..., true/false)` - -## 性能注意事项 - -- 大对象池请配置 `capacity` 和 `expireTime` -- 过多小池会增加管理成本 diff --git a/Client/Assets/Books/Framework/Core/ObjectPool.md.meta b/Client/Assets/Books/Framework/Core/ObjectPool.md.meta deleted file mode 100644 index 884321d..0000000 --- a/Client/Assets/Books/Framework/Core/ObjectPool.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: eaf9c0f1f6ac7e241b6b95a9c1a5b6a4 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Editor.meta b/Client/Assets/Books/Framework/Editor.meta deleted file mode 100644 index 75ab518..0000000 --- a/Client/Assets/Books/Framework/Editor.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: dabe76bff0e82ac49acbce538ac31bc2 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Editor/EditorTools.md b/Client/Assets/Books/Framework/Editor/EditorTools.md deleted file mode 100644 index 5b34841..0000000 --- a/Client/Assets/Books/Framework/Editor/EditorTools.md +++ /dev/null @@ -1,157 +0,0 @@ -# EditorTools - -## 模块概述 - -EditorTools 是框架的编辑器侧配套,覆盖 Inspector、自定义窗口、配置工具与代码生成流程,目的是降低模块接入与排查成本。 - -与 UI 最相关的部分是: - -- `UISettingEditorWindow` -- `UIGenerateQuick` -- `UIScriptGeneratorHelper` -- `UIGenerateConfiguration` - -它们共同组成了 **UIHolder 自动生成工作流**。 - -## 快速开始 - -### UI 绑定工具的基本使用流程 - -1. 打开 UI 生成配置窗口,配置 UI 规则 -2. 把 UI 预制体放在配置的 `UIPrefabRootPath` 目录下 -3. 按命名规范布置需要绑定的节点 -4. 选中 UI 预制体根节点 -5. 执行菜单 `GameObject/UI生成绑定` -6. 等待脚本编译完成 -7. 生成器自动把 `XXXHolder` 挂回预制体,并回填控件字段 - -## UIHolder 自动生成说明 - -### 1. `UIHolder` 的定位 - -在本框架中: - -- `UIHolder` 指的是继承自 `UIHolderObjectBase` 的绑定类 -- 它主要用于保存控件引用和资源标签 -- 它不是主要的业务逻辑承载类 - -推荐方式: - -- **手写 Window / Widget 逻辑** -- **自动生成 Holder 绑定类** - -### 2. 使用哪个工具生成 - -核心入口: - -- 菜单:`GameObject/UI生成绑定` - -对应代码入口位于: - -- `Packages/com.alicizax.unity.framework/Editor/UI/GenerateWindow/UIGenerateQuick.cs:1` - -当你执行该菜单时,生成器会: - -- 检查当前选中的对象是否是 prefab -- 读取 `UIGenerateConfiguration` -- 校验 prefab 是否位于允许的 UI 根路径下 -- 分析节点名并推断组件类型 -- 输出 `UIHolder` 脚本 -- 在脚本编译后自动挂到 prefab 上 -- 自动把字段与节点/组件引用绑定完成 - -### 3. 命名规则如何影响生成 - -生成器会结合: - -- `UIElementRegexConfigs` -- `ComCheckSplitName` -- `ComCheckEndName` -- `ArrayComSplitName` - -来识别节点绑定信息。 - -例如默认配置会把: - -- `Btn#Close@` 识别为按钮字段 -- `Text#Title@` 识别为文本字段 -- `Img#Icon@` 识别为图片字段 - -### 4. 生成结果是什么 - -生成出的 Holder 类通常: - -- 继承 `UIHolderObjectBase` -- 自动带有 `UIResAttribute` -- 自动写入资源路径常量 -- 自动生成字段与公开访问器 - -这使得 UI 逻辑层可以直接通过泛型 Holder 访问控件,而无需再手动查找。 - -## 相关工具与职责 - -### `UISettingEditorWindow` - -作用: - -- 维护 UI 生成配置 -- 配置输出目录、命名空间、Prefab 根路径、资源加载方式 - -### `UIGenerateQuick` - -作用: - -- 提供快速菜单入口 -- 校验当前对象是否可生成 - -### `UIScriptGeneratorHelper` - -作用: - -- 扫描节点 -- 推断字段名和组件类型 -- 生成代码 -- 在脚本重载后自动挂载脚本并绑定字段 - -### `UIGenerateConfiguration` - -作用: - -- 保存生成规则与路径设置 -- 支持不同项目或不同目录使用不同配置 - -## 使用示例 - -```csharp -// 推荐流程: -// 1. 设计 InventoryItem.prefab -// 2. 选中 prefab 根节点 -// 3. 执行 “GameObject/UI生成绑定” -// 4. 得到 InventoryItemHolder : UIHolderObjectBase -// 5. 在业务代码中编写: -// public sealed class InventoryItemWidget : UIWidget { ... } -``` - -## 最佳实践 - -- 把 UI 生成规则纳入项目模板,避免每个模块自行定义一套命名方式 -- Holder 文件视为“生成产物”,不要手工长期维护 -- 如果修改了 prefab 绑定节点,重新执行一次生成工具 -- 逻辑类和生成类分离,减少合并冲突 - -## 常见错误 - -### 预制体不在配置的 UI 根路径下 - -- 现象:执行生成工具无结果 -- 原因:`UIGenerateQuick.CheckCanGenerate` 校验失败 - -### 修改了预制体但没重新生成 Holder - -- 现象:字段缺失或控件引用错误 -- 处理:重新执行 `GameObject/UI生成绑定` - -### 手动改了生成类名称或命名空间 - -- 现象:逻辑类泛型类型失配 -- 处理:尽量不要手改生成文件,必要时同步更新生成配置与逻辑类引用 diff --git a/Client/Assets/Books/Framework/Editor/EditorTools.md.meta b/Client/Assets/Books/Framework/Editor/EditorTools.md.meta deleted file mode 100644 index 80251a8..0000000 --- a/Client/Assets/Books/Framework/Editor/EditorTools.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 0c080bcc065201840afe739351071504 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Plugins.meta b/Client/Assets/Books/Framework/Plugins.meta deleted file mode 100644 index 6e19e92..0000000 --- a/Client/Assets/Books/Framework/Plugins.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 4faf052fe9676814a83fd3638d12e801 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Plugins/Plugins.md b/Client/Assets/Books/Framework/Plugins/Plugins.md deleted file mode 100644 index ecc35af..0000000 --- a/Client/Assets/Books/Framework/Plugins/Plugins.md +++ /dev/null @@ -1,54 +0,0 @@ -# Plugins - -## 模块概述 - -`Plugins` 目录保存框架依赖的第三方程序集与源码生成插件,它们补充了框架运行时能力、兼容层与自动注册逻辑。 - -## 快速开始 - -通常不需要业务层主动初始化本模块。开发者需要做的是: - -1. 确认 Unity 正常导入 DLL -2. 不随意删除或移动 `Plugins` 目录文件 -3. 在 UI / Event 自动注册异常时优先检查生成器 DLL 是否生效 - -## 架构说明 - -```text -Plugins - ├─ XLog.dll - ├─ SharpZipLib - ├─ System.Memory / Buffers / Unsafe - ├─ EventSourceGenerator.dll - └─ UISourceGenerator.dll -``` - -## 主要内容 - -- `XLog.dll`:日志能力支持 -- `ICSharpCode.SharpZipLib.dll`:压缩/解压相关能力 -- `System.Buffers.dll`、`System.Memory.dll`、`System.Runtime.CompilerServices.Unsafe.dll`:底层兼容依赖 -- `EventSourceGenerator.dll`:事件源码生成插件 -- `UISourceGenerator.dll`:UI 源码生成插件 - -## API / 作用说明 - -### `EventSourceGenerator.dll` - -- 作用:辅助事件相关代码生成 - -### `UISourceGenerator.dll` - -- 作用:辅助 UI 元数据与资源绑定注册 - -## 完整使用示例 - -```csharp -// 本模块通常不直接由业务代码调用。 -// 若出现 UI/Event 自动注册异常,请先检查对应 DLL 在 Unity 中是否正常导入。 -``` - -## 最佳实践 - -- 锁定插件版本 -- 升级 Unity 后优先验证生成器与 IL2CPP 打包链路 diff --git a/Client/Assets/Books/Framework/Plugins/Plugins.md.meta b/Client/Assets/Books/Framework/Plugins/Plugins.md.meta deleted file mode 100644 index 711c776..0000000 --- a/Client/Assets/Books/Framework/Plugins/Plugins.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 2052075fd72f78349bc745789185c70d -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/QuickStart.md b/Client/Assets/Books/Framework/QuickStart.md deleted file mode 100644 index 06f378b..0000000 --- a/Client/Assets/Books/Framework/QuickStart.md +++ /dev/null @@ -1,134 +0,0 @@ -# Aliciza X Framework 快速入口 - -## 1. 框架简介 - -`com.alicizax.unity.framework` 是一套面向 Unity 项目的模块化基础框架,围绕 **根节点统一启动、服务容器统一获取、运行时模块按需组合** 的设计思想构建。 -框架通过 `RootModule` 建立全局运行时入口,通过 `AppServices` / `GameApp` 暴露常用服务访问点,再由 UI、资源、音频、本地化、场景、流程、池化、定时器等模块分别承载具体业务能力。 - -### 核心设计理念 - -- **组合优先**:各模块以组件或服务形式注册,按项目需要启用。 -- **职责分层**:区分 `Core / Runtime / Editor / Plugins`,运行时与编辑器逻辑边界清晰。 -- **统一入口**:通过 `GameApp`、服务定位与根模块生命周期减少业务侧样板代码。 -- **面向工程化**:覆盖资源加载、UI 绑定、流程切换、调试监视、池化复用等常见项目基础设施。 - -## 2. 推荐阅读顺序 - -1. [Foundation](./Core/Foundation.md) -2. [Resource](./Runtime/Resource.md) -3. [Timer](./Runtime/Timer.md) -4. [UI](./Runtime/UI.md) -5. 其余业务模块 - -## 3. 模块目录 - -### Core - -- [Foundation](./Core/Foundation.md):根模块、服务容器、通用基础设施与核心抽象。 -- [Event](./Core/Event.md):结构体事件总线、事件订阅与调试监视。 -- [ObjectPool](./Core/ObjectPool.md):通用对象池与可复用实例管理。 -- [GameObjectPool](./Core/GameObjectPool.md):面向预制体/场景对象的 GameObject 级池化系统。 - -### Runtime - -- [Audio](./Runtime/Audio.md):音频播放、分组音量、播放句柄与运行时控制。 -- [Debugger](./Runtime/Debugger.md):运行时调试面板、监控入口与调试扩展。 -- [Localization](./Runtime/Localization.md):本地化表、语言切换与文本刷新机制。 -- [Procedure](./Runtime/Procedure.md):流程/状态节点切换与启动流程组织。 -- [Resource](./Runtime/Resource.md):资源系统封装、包初始化、资源加载与实例化。 -- [Scene](./Runtime/Scene.md):场景加载、场景域切换与场景生命周期协作。 -- [Timer](./Runtime/Timer.md):统一定时器服务、延时/循环/取消与时间驱动逻辑。 -- [UI](./Runtime/UI.md):UI 窗口、Widget、Holder 绑定、层级管理与动画过渡。 - -### Editor - -- [EditorTools](./Editor/EditorTools.md):Inspector、事件监视器、UI 绑定生成器与本地化编辑工具。 - -### Plugins - -- [Plugins](./Plugins/Plugins.md):第三方程序集、代码生成器与插件职责说明。 - -## 4. 整体协作关系 - -```text -RootModule -└── AppServiceRoot - └── AppServices / GameApp - ├── Resource - ├── Timer - ├── UI - ├── Audio - ├── Localization - ├── Procedure - ├── ObjectPool / GameObjectPool - └── Scene -``` - -### 常见依赖关系 - -- `UI` 通常依赖 `Resource` 加载界面资源,并依赖 `Timer` 驱动过渡或延迟行为。 -- `Scene` 常与 `Resource` 协作处理场景包加载,与 `Procedure` 协作组织关卡切换。 -- `Localization` 常与 `UI` 或 `UX` 文本组件协作,负责语言刷新。 -- `GameObjectPool` 常与 `Resource` 协作完成预制体异步加载与回收复用。 - -## 5. 最小接入步骤 - -### 场景基础组件 - -推荐在启动场景中至少挂载: - -- `RootModule` -- `ResourceComponent` -- `TimerComponent` -- `UIComponent` - -### 启动顺序建议 - -```text -RootModule -> ResourceComponent -> TimerComponent -> UIComponent -> 其他业务模块 -``` - -### 最小示例 - -```csharp -using AlicizaX; -using AlicizaX.Resource.Runtime; -using Cysharp.Threading.Tasks; -using UnityEngine; - -public sealed class BootstrapExample : MonoBehaviour -{ - private async void Start() - { - IResourceService resourceService = GameApp.Resource; - await resourceService.InitPackageAsync(); - await GameApp.UI.ShowUI(); - } -} -``` - -## 6. 常见问题 - -### `GameApp.xxx` 为空 - -- 对应模块组件尚未挂载或未完成初始化。 -- `RootModule` 尚未建立根服务容器。 -- 获取时机早于模块注册阶段。 - -### UI 无法打开 - -- `UIComponent` 未配置 `UIRoot`。 -- UI 资源路径或 `UIResAttribute` 配置错误。 -- UI 绑定代码未生成,或 `UIWidget` 泛型未引用正确的 Holder 类型。 - -### 资源加载失败 - -- 资源包尚未初始化。 -- 资源路径与包内地址不一致。 -- 运行环境缺少对应包或依赖资源。 - -## 7. 下一步 - -- 从 [Foundation](./Core/Foundation.md) 理解根模块与服务注册方式。 -- 从 [Resource](./Runtime/Resource.md) 理解资源系统初始化与加载链路。 -- 从 [UI](./Runtime/UI.md) 理解 UIHolder 自动生成、`UIWidget` 绑定与窗口生命周期。 diff --git a/Client/Assets/Books/Framework/QuickStart.md.meta b/Client/Assets/Books/Framework/QuickStart.md.meta deleted file mode 100644 index 1de7699..0000000 --- a/Client/Assets/Books/Framework/QuickStart.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: edb42bd58f70a29419fef9babc8b6152 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Runtime.meta b/Client/Assets/Books/Framework/Runtime.meta deleted file mode 100644 index 95b786c..0000000 --- a/Client/Assets/Books/Framework/Runtime.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 6296361d31a52a045948fe1d9a074f6e -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Runtime/Audio.md b/Client/Assets/Books/Framework/Runtime/Audio.md deleted file mode 100644 index f063340..0000000 --- a/Client/Assets/Books/Framework/Runtime/Audio.md +++ /dev/null @@ -1,94 +0,0 @@ -# Audio - -## 模块概述 - -Audio 模块负责统一音频分类、音量控制、音频池与播放行为。它把音频拆分为音乐、音效、UI 音效、语音等类别,并支持使用 `AudioMixer` 做混音控制。 - -## 快速开始 - -1. 场景挂载 `AudioComponent` -2. 配置 `AudioMixer`、实例根节点与 `AudioGroupConfig` -3. 通过 `GameApp.Audio` 播放音频 - -```csharp -using AlicizaX; -using AlicizaX.Audio.Runtime; - -public sealed class AudioQuickStart -{ - public void PlayClick() - { - GameApp.Audio.Play(AudioType.UISound, "Audio/UI/Click"); - } -} -``` - -## 架构说明 - -```text -AudioComponent - └─ AudioService - ├─ AudioCategory - ├─ AudioAgent - ├─ AudioGroupConfig - └─ AudioClipPool -``` - -## 核心类与接口 - -### `IAudioService` - -主要属性: - -- `Volume` -- `Enable` -- `MusicVolume` / `SoundVolume` / `UISoundVolume` / `VoiceVolume` -- `MusicEnable` / `SoundEnable` / `UISoundEnable` / `VoiceEnable` -- `AudioMixer` -- `InstanceRoot` - -主要方法: - -- `Initialize(...)` -- `Play(...)` -- `Stop(...)` -- `StopAll(...)` -- `PutInAudioPool(...)` -- `CleanSoundPool()` - -## API 参考 - -### `Initialize(AudioGroupConfig[] audioGroupConfigs, Transform instanceRoot = null, AudioMixer audioMixer = null)` - -- 必填参数:`audioGroupConfigs` -- 可选参数:`instanceRoot` -- 可选参数:`audioMixer` -- 返回值:无 - -### `Play(AudioType type, string path, bool bLoop = false, float volume = 1.0f, bool bAsync = false, bool bInPool = false)` - -- 必填参数:`type` -- 必填参数:`path` -- 其余均为可选参数 -- 返回值:`AudioAgent` - -## 完整使用示例 - -```csharp -using AlicizaX; -using AlicizaX.Audio.Runtime; - -public sealed class AudioExample -{ - public void PlayBgm() - { - GameApp.Audio.MusicVolume = 0.8f; - GameApp.Audio.Play(AudioType.Music, "Audio/Bgm/MainTheme", true, 1f, true, true); - } -} -``` - -## 最佳实践 - -- 常用短音效建议预热 -- 背景音乐和 UI 音效分混音组管理 diff --git a/Client/Assets/Books/Framework/Runtime/Audio.md.meta b/Client/Assets/Books/Framework/Runtime/Audio.md.meta deleted file mode 100644 index 7d37de8..0000000 --- a/Client/Assets/Books/Framework/Runtime/Audio.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: bb1c0a7757fb6764392d1e5256e350ed -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Runtime/Debugger.md b/Client/Assets/Books/Framework/Runtime/Debugger.md deleted file mode 100644 index 6c19b27..0000000 --- a/Client/Assets/Books/Framework/Runtime/Debugger.md +++ /dev/null @@ -1,68 +0,0 @@ -# Debugger - -## 模块概述 - -Debugger 模块提供运行时调试面板,可查看日志、FPS、系统信息、场景信息、对象池、运行时内存等。调试窗口采用树状注册方式,支持扩展自定义页面。 - -## 快速开始 - -1. 场景挂载 `DebuggerComponent` -2. 选择激活策略 `DebuggerActiveWindowType` -3. 通过 `RegisterDebuggerWindow` 扩展自定义页面 - -## 架构说明 - -```text -DebuggerComponent - └─ DebuggerService - ├─ IDebuggerWindowGroup - └─ IDebuggerWindow -``` - -## 核心类与接口 - -### `IDebuggerService` - -公开能力: - -- `ActiveWindow` -- `DebuggerWindowRoot` -- `RegisterDebuggerWindow(...)` -- `UnregisterDebuggerWindow(...)` -- `GetDebuggerWindow(...)` -- `SelectDebuggerWindow(...)` - -## API 参考 - -### `RegisterDebuggerWindow(string path, IDebuggerWindow debuggerWindow, params object[] args)` - -- 必填参数:`path` -- 必填参数:`debuggerWindow` -- 可选参数:`args` -- 返回值:无 - -### `UnregisterDebuggerWindow(string path)` - -- 返回值:`bool` - -## 完整使用示例 - -```csharp -using AlicizaX.Debugger.Runtime; -using UnityEngine; - -public sealed class SimpleDebuggerWindow : IDebuggerWindow -{ - public void Initialize(params object[] args) { } - public void Shutdown() { } - public void OnEnter() { } - public void OnLeave() { } - public void OnUpdate(float elapseSeconds, float realElapseSeconds) { } - public void OnDraw() => GUILayout.Label("Custom debug page"); -} -``` - -## 最佳实践 - -- 自定义窗口只放诊断信息 -- 发布环境建议仅开发版开启 diff --git a/Client/Assets/Books/Framework/Runtime/Debugger.md.meta b/Client/Assets/Books/Framework/Runtime/Debugger.md.meta deleted file mode 100644 index c7707df..0000000 --- a/Client/Assets/Books/Framework/Runtime/Debugger.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 622e9f4d6de5d6f4d90bec501dcc2592 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Runtime/Localization.md b/Client/Assets/Books/Framework/Runtime/Localization.md deleted file mode 100644 index 233d707..0000000 --- a/Client/Assets/Books/Framework/Runtime/Localization.md +++ /dev/null @@ -1,97 +0,0 @@ -# Localization - -## 模块概述 - -Localization 模块负责语言管理、本地化表装载、键值查询与格式化输出。它支持启动时加载内置表与资源表,也支持运行时异步切换语言。 - -## 快速开始 - -1. 挂载 `LocalizationComponent` -2. 设置默认语言 -3. 配置启动表或资源路径 -4. 使用 `GameApp.Localization.GetString()` - -```csharp -using AlicizaX; - -public sealed class LocalizationQuickStart -{ - public string GetTitle() - { - return GameApp.Localization.GetString("UI_Title"); - } -} -``` - -## 架构说明 - -```text -LocalizationComponent - └─ LocalizationService - ├─ GameLocaizationTable - ├─ LocalizationLanguage - └─ LocalizationChangeEvent -``` - -## 核心类与接口 - -### `ILocalizationService` - -公开能力: - -- `Language` -- `Initialize(string language)` -- `ChangedLanguage(string language)` -- `SwitchLanguageAsync(string language, CancellationToken cancellationToken = default)` -- `GetString(...)` -- `TryGetRawString(...)` -- `CoverAddLocalizationConfig(...)` -- `IncreAddLocalizationConfig(...)` -- `ReloadLocalizationConfig(...)` - -## API 参考 - -### `Initialize(string language)` - -- 必填参数:`language` -- 返回值:无 - -### `SwitchLanguageAsync(string language, CancellationToken cancellationToken = default)` - -- 必填参数:`language` -- 可选参数:`cancellationToken` -- 返回值:`UniTask` - -### `GetString(string key, params object[] args)` - -- 必填参数:`key` -- 可选参数:`args` -- 返回值:`string` - -## 完整使用示例 - -```csharp -using AlicizaX; -using Cysharp.Threading.Tasks; -using UnityEngine; - -public sealed class LocalizationExample : MonoBehaviour -{ - private async void Start() - { - Debug.Log(GameApp.Localization.GetString("UI_Start")); - await GameApp.Localization.SwitchLanguageAsync("English"); - Debug.Log(GameApp.Localization.GetString("UI_Start")); - } -} -``` - -## 最佳实践 - -- 统一 Key 命名规则 -- 语言切换事件中只做刷新,不做重资源初始化 - -## 常见错误 - -- Key 不存在却未做兜底 -- 资源服务未注册时依赖资源路径表 diff --git a/Client/Assets/Books/Framework/Runtime/Localization.md.meta b/Client/Assets/Books/Framework/Runtime/Localization.md.meta deleted file mode 100644 index 680e024..0000000 --- a/Client/Assets/Books/Framework/Runtime/Localization.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 5e7853f21507379449bb237975fb20ae -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Runtime/Procedure.md b/Client/Assets/Books/Framework/Runtime/Procedure.md deleted file mode 100644 index 96dce62..0000000 --- a/Client/Assets/Books/Framework/Runtime/Procedure.md +++ /dev/null @@ -1,103 +0,0 @@ -# Procedure - -## 模块概述 - -Procedure 模块用于组织游戏主流程,如启动、登录、主城、战斗、结算等状态切换。它以 `ProcedureBase` 为模板方法基类,把生命周期拆成初始化、进入、离开、更新、销毁。 - -## 快速开始 - -1. 挂载 `ProcedureComponent` -2. 定义多个继承 `ProcedureBase` 的流程 -3. 调用 `InitializeProcedure` -4. 通过 `SwitchProcedure()` 切换 - -## 架构说明 - -```text -ProcedureComponent - └─ ProcedureService - ├─ IProcedure - └─ ProcedureBase -``` - -## 核心类与接口 - -### `IProcedureService` - -公开能力: - -- `CurrentProcedureType` -- `InitializeProcedure(...)` -- `ClearAllProcedures()` -- `ContainsProcedure(Type procedureType)` -- `TrySwitchProcedure(Type procedureType)` - -### `ProcedureBase` - -生命周期模板: - -- `OnInit()` -- `OnEnter()` -- `OnLeave()` -- `OnUpdate()` -- `OnDestroy()` - -## API 参考 - -### `InitializeProcedure(IEnumerable availableProcedures, Type defaultProcedureType)` - -- 必填参数:`availableProcedures` -- 必填参数:`defaultProcedureType` -- 返回值:无 - -### `TrySwitchProcedure(Type procedureType)` - -- 必填参数:`procedureType` -- 返回值:`bool` - -### `CurrentProcedureType` - -- 返回值:`Type` - -## 完整使用示例 - -```csharp -using System; -using AlicizaX; - -public sealed class BootProcedure : ProcedureBase -{ - protected override void OnEnter() - { - SwitchProcedure(); - } -} - -public sealed class LoginProcedure : ProcedureBase -{ - protected override void OnEnter() - { - Log.Info("Enter LoginProcedure"); - } -} - -public sealed class ProcedureExample -{ - public void Initialize() - { - GameApp.Procedure.InitializeProcedure( - new IProcedure[] { new BootProcedure(), new LoginProcedure() }, - typeof(BootProcedure)); - } -} -``` - -## 最佳实践 - -- 一个流程只负责一个清晰状态 -- 初始化逻辑放 `OnInit`,进入/退出逻辑放 `OnEnter` / `OnLeave` - -## 常见错误 - -- 未初始化就切换流程 -- 把长时间并行任务塞入流程本身 diff --git a/Client/Assets/Books/Framework/Runtime/Procedure.md.meta b/Client/Assets/Books/Framework/Runtime/Procedure.md.meta deleted file mode 100644 index 1a1377b..0000000 --- a/Client/Assets/Books/Framework/Runtime/Procedure.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 8e26efa856cdeb8458b10c02c655dad6 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Runtime/Resource.md b/Client/Assets/Books/Framework/Runtime/Resource.md deleted file mode 100644 index 9744dba..0000000 --- a/Client/Assets/Books/Framework/Runtime/Resource.md +++ /dev/null @@ -1,673 +0,0 @@ -# Resource - -## 模块概述 - -Resource 模块基于 YooAsset 封装了资源包初始化、版本管理、资源查询、同步/异步加载、实例化、缓存回收与低内存处理能力。 - -它是多个模块的底层依赖: - -- `UI`:加载窗口和 Widget 预制体 -- `Audio`:加载音频资源 -- `Localization`:加载语言表 -- `GameObjectPool`:加载池对象原始 prefab -- `Scene`:主场景与附加场景的资源加载链路 - -从当前实现来看,`ResourceService` 还额外提供了: - -- 多包管理 -- 同一路径并发加载合并 -- 资源对象池缓存 -- 低内存时统一回收入口 -- 下载、版本、清单更新能力 - -## 快速开始 - -### 最少步骤 - -1. 在场景中挂载 `ResourceComponent` -2. 配置 `PlayMode`、默认包名和解密服务名 -3. 游戏启动时调用 `InitPackageAsync` -4. 通过 `GameApp.Resource` 或 `AppServices.Require()` 加载资源 - -### 最小示例 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class ResourceQuickStart : MonoBehaviour -{ - private async void Start() - { - await GameApp.Resource.InitPackageAsync(); - - Texture2D icon = await GameApp.Resource.LoadAssetAsync("UI/Common/Icon"); - Debug.Log(icon != null); - } -} -``` - -## 架构说明 - -```text -ResourceComponent - └─ ResourceService - ├─ YooAsset PackageMap - ├─ AssetInfo Cache - ├─ AssetObject Pool - ├─ Loading Operation Map - ├─ Download / Version / Manifest - └─ ResourceExtComponent / AssetsReference -``` - -### 关键协作关系 - -- `ResourceComponent`:负责注册服务、配置运行模式并接管低内存回收节奏 -- `ResourceService`:负责包初始化、加载、缓存和释放 -- `AssetsReference`:负责把实例对象与源资源句柄关联起来,在对象销毁时自动释放源资源引用 -- `ResourceExtComponent`:负责追踪资源绑定目标并分帧回收无引用资源 - -### 运行模式 - -当前实现支持以下 `PlayMode`: - -- `EditorSimulateMode` -- `OfflinePlayMode` -- `HostPlayMode` -- `WebPlayMode` - -不同模式决定初始化参数: - -- **EditorSimulateMode**:编辑器模拟构建目录 -- **OfflinePlayMode**:本地内置资源 -- **HostPlayMode**:内置资源 + 缓存 + 远端下载 -- **WebPlayMode**:Web 环境资源拉取方式 - -### 并发加载合并机制 - -`ResourceService` 内部使用 `_assetLoadingOperations` 合并同一路径的并发加载请求。 - -这意味着: - -- 多个系统同时加载同一个资源时,不会重复发起多次底层加载 -- 后来的请求会等待第一条加载链路完成,再复用结果 - -这对 UI 和公共图集资源特别有价值。 - -## 核心类与接口 - -### `IResourceService` - -该接口同时覆盖: - -- 包初始化 -- 包版本与清单管理 -- 资源存在性检查 -- 同步/异步加载 -- 场景对象实例化 -- 缓存清理 -- 低内存回收 - -### `ResourceComponent` - -它是 Resource 模块在场景中的入口组件,主要职责: - -- 注册 `ResourceService` -- 配置默认包名 `PackageName` -- 设置运行模式 `PlayMode` -- 设置解密服务名 `decryptionServices` -- 设置资源对象池参数 -- 接管 `Application.lowMemory` 事件 - -### `ResourceService` - -从当前实现看,`ResourceService` 维护以下关键状态: - -- `DefaultPackageName` -- `PlayMode` -- `PackageMap` -- `_assetInfoMap` -- `_assetLoadingOperations` -- `_assetPool` - -### `AssetsReference` - -这是实例对象与源资源句柄的桥接组件。 - -作用: - -- 当你通过 `LoadGameObject` / `LoadGameObjectAsync` 实例化资源时 -- 框架会在实例对象上自动挂一个 `AssetsReference` -- 当该实例对象销毁时,对应源资源引用会自动 `UnloadAsset` - -这就是为什么: - -- `LoadGameObject` 返回的实例对象通常不需要你手动 `UnloadAsset` - -### `ResourceExtComponent` - -主要用于: - -- 追踪已绑定给 UI、Sprite 或其他目标对象的资源 -- 自动清理已经失去引用的资源包装对象 -- 以分帧方式做回收,降低一次性遍历成本 - -## API 参考 - -以下内容按使用频率分组说明。 - -### 一、初始化与运行配置 - -#### `void Initialize()` - -- 参数:无 -- 返回值:无 -- 说明:初始化 YooAsset、默认包与资产池 -- 备注:通常由 `ResourceComponent.Start()` 自动调用 - -#### `UniTask InitPackageAsync(string packageName = "", string hostServerURL = "", string fallbackHostServerURL = "")` - -- 可选参数:`packageName` -- 可选参数:`hostServerURL` -- 可选参数:`fallbackHostServerURL` -- 返回值:`UniTask` - -说明: - -- 初始化指定资源包 -- 如果 `packageName` 为空,则使用 `DefaultPackageName` -- 在 Host/Web 模式下通常需要传入远端地址 - -异常与边界: - -- 如果同一个包正在初始化或已成功初始化,会记录错误并直接返回失败结果 -- 初始化失败时任务会抛出异常 - -#### `string DefaultPackageName { get; set; }` - -- 说明:默认资源包名 -- 推荐:整个项目统一默认包名约定 - -#### `EPlayMode PlayMode { get; set; }` - -- 说明:资源系统运行模式 -- 注意:Editor 与真机配置往往不同 - -#### `string DecryptionServices { get; set; }` - -- 说明:解密服务类型名 -- 注意:该值应是可被反射创建的类型全名 - -#### `bool AutoUnloadBundleWhenUnused { get; set; }` - -- 说明:初始化参数中的自动卸载选项 - -### 二、版本与下载管理 - -#### `string GetPackageVersion(string customPackageName = "")` - -- 可选参数:`customPackageName` -- 返回值:包版本字符串 - -#### `RequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks = false, int timeout = 60, string customPackageName = "")` - -- 可选参数:`appendTimeTicks` -- 可选参数:`timeout` -- 可选参数:`customPackageName` -- 返回值:`RequestPackageVersionOperation` - -#### `UpdatePackageManifestOperation UpdatePackageManifestAsync(string packageVersion, int timeout = 60, string customPackageName = "")` - -- 必填参数:`packageVersion` -- 可选参数:`timeout` -- 可选参数:`customPackageName` -- 返回值:`UpdatePackageManifestOperation` - -#### `ResourceDownloaderOperation CreateResourceDownloader(string customPackageName = "")` - -- 可选参数:`customPackageName` -- 返回值:`ResourceDownloaderOperation` - -说明: - -- 会使用当前 `DownloadingMaxNum` 和 `FailedTryAgain` - -### 三、资源查询 - -#### `HasAssetResult HasAsset(string location, string packageName = "")` - -- 必填参数:`location` -- 可选参数:`packageName` -- 返回值:`HasAssetResult` - -说明: - -- 用于判断资源是否存在、是否在本地、是否需要远端下载 - -注意: - -- 如果 `location` 为空会抛异常 - -#### `bool CheckLocationValid(string location, string packageName = "")` - -- 必填参数:`location` -- 可选参数:`packageName` -- 返回值:`bool` - -#### `AssetInfo GetAssetInfo(string location, string packageName = "")` - -- 必填参数:`location` -- 可选参数:`packageName` -- 返回值:`AssetInfo` - -说明: - -- 内部带有 `_assetInfoMap` 缓存 -- 同一路径重复获取成本较低 - -### 四、同步加载 - -#### `T LoadAsset(string location, string packageName = "") where T : UnityEngine.Object` - -- 必填参数:`location` -- 可选参数:`packageName` -- 返回值:`T` -- 泛型约束:`T : UnityEngine.Object` - -说明: - -- 同步加载资源 -- 首次加载会进入资源池缓存 -- 重复获取相同资源时会优先从 `_assetPool` 中复用 - -#### `GameObject LoadGameObject(string location, Transform parent = null, string packageName = "")` - -- 必填参数:`location` -- 可选参数:`parent` -- 可选参数:`packageName` -- 返回值:`GameObject` - -说明: - -- 同步加载 prefab 并实例化 -- 实例对象会自动挂 `AssetsReference` -- 销毁实例时会自动释放源资源引用 - -### 五、异步加载 - -#### `UniTask LoadAssetAsync(string location, CancellationToken cancellationToken = default, string packageName = "") where T : UnityEngine.Object` - -- 必填参数:`location` -- 可选参数:`cancellationToken` -- 可选参数:`packageName` -- 返回值:`UniTask` - -#### `UniTask LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default, string packageName = "")` - -- 必填参数:`location` -- 可选参数:`parent` -- 可选参数:`cancellationToken` -- 可选参数:`packageName` -- 返回值:`UniTask` - -### 六、回调式加载 - -#### `UniTask LoadAssetAsync(string location, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = "")` - -#### `UniTask LoadAssetAsync(string location, Type assetType, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = "")` - -`LoadAssetCallbacks` 中的回调签名: - -- 成功:`(string assetName, object asset, float duration, object userData)` -- 失败:`(string assetName, LoadResourceStatus status, string errorMessage, object userData)` -- 进度:`(string assetName, float progress, object userData)` - -适合: - -- 旧式回调链 -- 想同时观察进度与错误 -- 不方便直接 `await` 的场景 - -### 七、句柄接口 - -#### `AssetHandle LoadAssetSyncHandle(string location, string packageName = "") where T : UnityEngine.Object` - -#### `AssetHandle LoadAssetAsyncHandle(string location, string packageName = "") where T : UnityEngine.Object` - -适合: - -- 需要直接管理底层句柄 -- 需要和 YooAsset 句柄 API 配合 - -### 八、释放与回收 - -#### `void UnloadAsset(object asset)` - -- 必填参数:`asset` -- 返回值:无 - -说明: - -- 本质上是把资源对象从 `_assetPool` 中 `Unspawn` - -#### `void UnloadUnusedAssets()` - -- 返回值:无 - -说明: - -- 释放资源对象池中未使用的对象 -- 并对所有已成功初始化的包执行 `UnloadUnusedAssetsAsync` - -#### `void ForceUnloadAllAssets()` - -- 返回值:无 - -说明: - -- 强制卸载所有资源 -- WebGL 下会直接警告并退出 - -#### `void ForceUnloadUnusedAssets(bool performGCCollect)` - -- 必填参数:`performGCCollect` -- 返回值:无 - -说明: - -- 委托给 `ResourceComponent` 控制实际的系统资源回收时机 - -## 常见用法 - -### 1. 初始化默认包 - -```csharp -using AlicizaX; -using AlicizaX.Resource.Runtime; -using UnityEngine; - -public sealed class ResourceInitExample : MonoBehaviour -{ - private async void Start() - { - bool ok = await GameApp.Resource.InitPackageAsync(); - Debug.Log($"Init default package success: {ok}"); - } -} -``` - -### 2. 初始化远端包 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class HostModeInitExample : MonoBehaviour -{ - private async void Start() - { - await GameApp.Resource.InitPackageAsync( - packageName: "DefaultPackage", - hostServerURL: "https://cdn.example.com/game", - fallbackHostServerURL: "https://backup.example.com/game"); - } -} -``` - -### 3. 同步加载配置资源 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class SyncLoadExample : MonoBehaviour -{ - private void Start() - { - TextAsset config = GameApp.Resource.LoadAsset("Config/GameBalance"); - Debug.Log(config != null ? config.text : "Config missing"); - } -} -``` - -### 4. 异步加载图片资源 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class AsyncLoadExample : MonoBehaviour -{ - private async void Start() - { - Sprite sprite = await GameApp.Resource.LoadAssetAsync("UI/Common/Atlas/Icon_Star"); - Debug.Log(sprite != null); - } -} -``` - -### 5. 加载并实例化 GameObject - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class InstantiateExample : MonoBehaviour -{ - private GameObject _hero; - - private async void Start() - { - _hero = await GameApp.Resource.LoadGameObjectAsync("Character/Hero.prefab", transform); - } - - private void OnDestroy() - { - if (_hero != null) - { - Destroy(_hero); - } - } -} -``` - -说明: - -- 这里不需要对 `_hero` 再调用 `UnloadAsset` -- 销毁实例时 `AssetsReference` 会自动释放源资源引用 - -### 6. 使用回调式加载并监听进度 - -```csharp -using AlicizaX; -using AlicizaX.Resource.Runtime; -using UnityEngine; - -public sealed class CallbackLoadExample : MonoBehaviour -{ - private async void Start() - { - var callbacks = new LoadAssetCallbacks( - (assetName, asset, duration, userData) => - { - Debug.Log($"Load success: {assetName}, duration: {duration:F3}s"); - }, - (assetName, status, errorMessage, userData) => - { - Debug.LogError($"Load failed: {assetName}, {status}, {errorMessage}"); - }, - (assetName, progress, userData) => - { - Debug.Log($"Progress {assetName}: {progress:P0}"); - }); - - await GameApp.Resource.LoadAssetAsync( - "UI/Common/Icon", - priority: 0, - loadAssetCallbacks: callbacks, - userData: null); - } -} -``` - -### 7. 下载资源版本并更新清单 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class UpdateManifestExample : MonoBehaviour -{ - private async void Start() - { - var versionOp = GameApp.Resource.RequestPackageVersionAsync(); - await versionOp.ToUniTask(); - - if (versionOp.Status == YooAsset.EOperationStatus.Succeed) - { - string version = versionOp.PackageVersion; - var manifestOp = GameApp.Resource.UpdatePackageManifestAsync(version); - await manifestOp.ToUniTask(); - Debug.Log($"Manifest updated to: {version}"); - } - } -} -``` - -### 8. 创建下载器 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class DownloaderExample : MonoBehaviour -{ - private async void Start() - { - var downloader = GameApp.Resource.CreateResourceDownloader(); - downloader.BeginDownload(); - await downloader.ToUniTask(); - Debug.Log($"Download status: {downloader.Status}"); - } -} -``` - -## 运行行为细节 - -### 1. 重复加载同一路径会优先复用缓存 - -当前实现通过 `_assetPool` 保存 `AssetObject`,同一路径的资源会复用池内对象。 - -好处: - -- 减少重复加载 -- 降低频繁生成/销毁底层句柄的开销 - -### 2. 并发同路径加载会合并请求 - -如果同一路径资源正在加载中: - -- 后续请求不会重复发起底层加载 -- 会等待首个请求完成后再复用结果 - -### 3. `LoadGameObject` 与 `LoadAsset` 的释放语义不同 - -#### `LoadAsset` - -- 返回的是资源对象本身 -- 如有需要可以显式 `UnloadAsset(asset)` - -#### `LoadGameObject` - -- 返回的是实例化后的场景对象 -- 源 prefab 资源句柄通过 `AssetsReference` 绑定到实例对象 -- 通常直接 `Destroy(instance)` 即可 - -### 4. 低内存行为 - -触发 `OnLowMemory()` 时: - -- 会调用 `_forceUnloadUnusedAssetsAction` -- 实际清理由 `ResourceComponent` 驱动 - -### 5. `AssetInfo` 有内部缓存 - -多次 `GetAssetInfo` 会命中 `_assetInfoMap` - -适合: - -- 频繁查询资源合法性 -- UI 打开前做路径校验 - -### 6. 失败行为 - -以下情况通常会导致异常或错误日志: - -- 资源路径为空 -- 包不存在 -- 资源定位无效 -- 初始化失败 - -## 最佳实践 - -- 统一资源路径规范,避免大小写和目录命名混乱 -- 默认优先异步加载 -- 把“包初始化 / 版本更新 / 资源下载 / 资源使用”分成不同阶段处理 -- UI、音频、图集等高复用资源尽量走缓存复用 -- 对需要长期驻留的资源,业务层要明确生命周期,不要频繁反复加载 - -## 常见错误 - -### 1. 包未初始化就加载资源 - -现象: - -- 资源查找失败 -- 返回空对象或直接异常 - -规避: - -- 确保启动流程中先执行 `InitPackageAsync` - -### 2. 默认包名与实际包名不一致 - -现象: - -- 路径正确但查不到资源 - -规避: - -- 统一 `DefaultPackageName` -- 多包场景显式传入 `packageName` - -### 3. 手动 `UnloadAsset` 已实例化场景对象 - -现象: - -- 语义不清晰 -- 可能导致资源生命周期混乱 - -规避: - -- `LoadGameObject` 返回的实例优先 `Destroy` - -### 4. 解密服务类型名错误 - -现象: - -- 初始化时反射失败 - -规避: - -- `DecryptionServices` 必须是可反射创建的完整类型名 - -## 性能注意事项 - -- 避免在主线程大量同步加载 -- 常用资源优先缓存复用 -- 合理设置: - - `AssetAutoReleaseInterval` - - `AssetCapacity` - - `AssetExpireTime` - - `AssetPriority` -- 高并发异步加载时,优先复用路径和包配置,发挥内部合并机制优势 diff --git a/Client/Assets/Books/Framework/Runtime/Resource.md.meta b/Client/Assets/Books/Framework/Runtime/Resource.md.meta deleted file mode 100644 index 80a1d7e..0000000 --- a/Client/Assets/Books/Framework/Runtime/Resource.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: df8519e09d84ea34b8ffd2c4b93c21f9 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Runtime/Scene.md b/Client/Assets/Books/Framework/Runtime/Scene.md deleted file mode 100644 index f486e1c..0000000 --- a/Client/Assets/Books/Framework/Runtime/Scene.md +++ /dev/null @@ -1,477 +0,0 @@ -# Scene - -## 模块概述 - -Scene 模块负责: - -- 主场景加载 -- Additive 子场景加载 -- 延迟激活与解除挂起 -- 子场景卸载 -- 主场景状态追踪 -- 场景作用域(Scene Scope)建立与重置 - -它不是 Unity 默认 `SceneManager` 的简单封装,而是把场景切换与框架服务作用域绑定在一起。 - -这意味着: - -- **加载主场景时,会重置 Scene Scope** -- **加载 Additive 子场景时,不会重置主场景状态** -- **主场景和子场景在框架内是两套不同语义** - -## 快速开始 - -### 最少步骤 - -1. 在场景中挂载 `SceneComponent` -2. 确保 `ResourceComponent` 已可用 -3. 通过 `GameApp.Scene.LoadSceneAsync(...)` 加载场景 -4. 如需卸载 Additive 场景,调用 `UnloadAsync(...)` - -### 最小示例 - -```csharp -using AlicizaX; -using UnityEngine.SceneManagement; - -public sealed class SceneQuickStart -{ - public async void Load() - { - await GameApp.Scene.LoadSceneAsync("Scene/Battle", LoadSceneMode.Single); - } -} -``` - -## 架构说明 - -```text -SceneComponent - └─ SceneService - └─ SceneDomainStateService - ├─ CurrentMainSceneName - ├─ CurrentMainSceneHandle - ├─ SubScene Map - └─ Handling Scene Set -``` - -### 关键协作关系 - -- `SceneComponent`:注册 `SceneService` 并确保 Scene Scope 存在 -- `SceneService`:处理加载、激活、卸载与状态切换 -- `SceneDomainStateService`:记录当前主场景、子场景和处理中场景 -- `IResourceService`:主场景切换完成后触发资源回收 - -### 主场景与子场景的区别 - -#### 主场景(Main Scene) - -- 通过 `LoadSceneMode.Single` 加载 -- 加载前会重置 Scene Scope -- 加载完成后更新 `CurrentMainSceneName` -- 切换完成后会触发一次资源回收 - -#### 子场景(Sub Scene / Additive) - -- 通过 `LoadSceneMode.Additive` 加载 -- 记录在 `_subScenes` 字典中 -- 可通过 `UnloadAsync(location)` 卸载 - -## 核心类与接口 - -### `ISceneService` - -公开能力: - -- `CurrentMainSceneName` -- `LoadSceneAsync(...)` -- `LoadScene(...)` -- `ActivateScene(string location)` -- `UnSuspend(string location)` -- `IsMainScene(string location)` -- `UnloadAsync(string location, Action progressCallBack = null)` -- `Unload(string location, Action callBack = null, Action progressCallBack = null)` -- `IsContainScene(string location)` - -### `ISceneStateService` - -偏状态查询接口,主要用于: - -- 查询当前主场景 -- 查询某个场景是否已记录在当前 Scene Scope 中 - -### `SceneDomainStateService` - -当前实现中负责维护: - -- `CurrentMainSceneName` -- `CurrentMainSceneHandle` -- `_subScenes` -- `_handlingScenes` - -用途: - -- 避免同一场景重复并发加载/卸载 -- 为 `IsContainScene` / `IsMainScene` 提供判断基础 - -## API 参考 - -### 一、场景加载 - -#### `UniTask LoadSceneAsync(string location, LoadSceneMode sceneMode = LoadSceneMode.Single, bool suspendLoad = false, uint priority = 100, bool gcCollect = true, Action progressCallBack = null)` - -- 必填参数:`location` -- 可选参数:`sceneMode` -- 可选参数:`suspendLoad` -- 可选参数:`priority` -- 可选参数:`gcCollect` -- 可选参数:`progressCallBack` -- 返回值:`UniTask` - -参数说明: - -- `location`:场景资源定位地址 -- `sceneMode`:`Single` 或 `Additive` -- `suspendLoad`:是否挂起加载后的激活 -- `priority`:加载优先级 -- `gcCollect`:主场景切换后是否执行资源回收 -- `progressCallBack`:加载进度回调 - -行为说明: - -- `Single`:会重置当前 Scene Scope -- `Additive`:会作为子场景注册 -- 如果同一场景正在处理,当前实现会记录错误并返回默认值 - -#### `void LoadScene(string location, LoadSceneMode sceneMode = LoadSceneMode.Single, bool suspendLoad = false, uint priority = 100, Action callBack = null, bool gcCollect = true, Action progressCallBack = null)` - -- 注意:这个方法**不是同步加载** -- 本质上仍是异步加载,只是使用回调而不是 `await` - -推荐: - -- 新代码优先使用 `LoadSceneAsync` - -### 二、场景激活与挂起 - -#### `bool ActivateScene(string location)` - -- 必填参数:`location` -- 返回值:`bool` - -说明: - -- 对应场景已被挂起时,尝试激活它 -- 可用于 `suspendLoad = true` 的场景 - -#### `bool UnSuspend(string location)` - -- 必填参数:`location` -- 返回值:`bool` - -说明: - -- 解除场景挂起 -- 语义接近 `ActivateScene` - -### 三、场景查询 - -#### `bool IsMainScene(string location)` - -- 必填参数:`location` -- 返回值:`bool` - -说明: - -- 判断给定场景是否为当前主场景 -- 内部结合 `SceneDomainStateService` 与 `SceneManager.GetActiveScene()` 做判断 - -#### `bool IsContainScene(string location)` - -- 必填参数:`location` -- 返回值:`bool` - -说明: - -- 判断当前主场景或子场景列表中是否包含该场景 - -#### `string CurrentMainSceneName` - -- 返回值:主场景名 - -### 四、场景卸载 - -#### `UniTask UnloadAsync(string location, Action progressCallBack = null)` - -- 必填参数:`location` -- 可选参数:`progressCallBack` -- 返回值:`UniTask` - -说明: - -- 用于卸载 Additive 子场景 -- 当前实现**不用于直接卸载主场景** - -#### `void Unload(string location, Action callBack = null, Action progressCallBack = null)` - -- 必填参数:`location` -- 可选参数:`callBack` -- 可选参数:`progressCallBack` -- 返回值:无 - -说明: - -- 回调式异步卸载 - -## 常见用法 - -### 1. 加载主场景 - -```csharp -using AlicizaX; -using UnityEngine.SceneManagement; - -public sealed class LoadMainSceneExample -{ - public async void GoBattle() - { - await GameApp.Scene.LoadSceneAsync("Scene/Battle", LoadSceneMode.Single); - } -} -``` - -### 2. Additive 加载子场景 - -```csharp -using AlicizaX; -using UnityEngine.SceneManagement; - -public sealed class AdditiveSceneExample -{ - public async void OpenSubScene() - { - await GameApp.Scene.LoadSceneAsync("Scene/PhotoRoom", LoadSceneMode.Additive); - } -} -``` - -### 3. 卸载 Additive 子场景 - -```csharp -using AlicizaX; - -public sealed class UnloadSubSceneExample -{ - public async void CloseSubScene() - { - if (GameApp.Scene.IsContainScene("Scene/PhotoRoom")) - { - bool ok = await GameApp.Scene.UnloadAsync("Scene/PhotoRoom"); - UnityEngine.Debug.Log($"Unload result: {ok}"); - } - } -} -``` - -### 4. 带进度的场景加载 - -```csharp -using AlicizaX; -using UnityEngine; -using UnityEngine.SceneManagement; - -public sealed class SceneProgressExample -{ - public async void LoadWithProgress() - { - await GameApp.Scene.LoadSceneAsync( - "Scene/Battle", - LoadSceneMode.Single, - suspendLoad: false, - priority: 100, - gcCollect: true, - progressCallBack: progress => - { - Debug.Log($"Scene progress: {progress:P0}"); - }); - } -} -``` - -### 5. 挂起加载后手动激活 - -```csharp -using AlicizaX; -using UnityEngine.SceneManagement; - -public sealed class SuspendLoadExample -{ - public async void LoadThenActivate() - { - await GameApp.Scene.LoadSceneAsync( - "Scene/Battle", - LoadSceneMode.Single, - suspendLoad: true); - - GameApp.Scene.ActivateScene("Scene/Battle"); - } -} -``` - -### 6. 使用回调式加载 - -```csharp -using AlicizaX; -using UnityEngine; -using UnityEngine.SceneManagement; - -public sealed class SceneCallbackExample -{ - public void LoadLobby() - { - GameApp.Scene.LoadScene( - "Scene/Lobby", - LoadSceneMode.Single, - suspendLoad: false, - priority: 100, - callBack: scene => - { - Debug.Log($"Loaded scene: {scene.name}"); - }, - gcCollect: true, - progressCallBack: progress => - { - Debug.Log($"Loading: {progress:P0}"); - }); - } -} -``` - -### 7. 查询当前主场景 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class SceneStateExample : MonoBehaviour -{ - private void Update() - { - Debug.Log($"Main Scene: {GameApp.Scene.CurrentMainSceneName}"); - } -} -``` - -## 运行行为细节 - -### 1. 主场景加载会重置 Scene Scope - -这是本模块最重要的设计点之一。 - -当调用: - -```csharp -GameApp.Scene.LoadSceneAsync("Scene/Battle", LoadSceneMode.Single) -``` - -内部会: - -1. `Context.ResetScene()` -2. 重新注册 `SceneDomainStateService` -3. 标记新主场景进入加载中 - -这意味着: - -- 旧的 Scene Scope 服务会被重建 -- 与旧主场景强绑定的场景级服务也应重新初始化 - -### 2. Additive 场景不会重置主场景作用域 - -使用 `LoadSceneMode.Additive` 时: - -- 场景会被加入 `_subScenes` -- 主场景状态保留 -- 适合加载摄影间、剧情副场景、临时房间等 - -### 3. `UnloadAsync` 只对 Additive 子场景有效 - -当前实现中: - -- 只有 `_subScenes` 中登记的场景才会走卸载逻辑 -- 主场景切换依赖新的 `LoadScene(Single)`,而不是单独 `Unload` 主场景 - -### 4. 同一场景并发处理会被拦截 - -`SceneDomainStateService` 会使用 `_handlingScenes` 记录“正在加载/卸载”的场景。 - -效果: - -- 避免同一路径重复加载或重复卸载 -- 减少状态错乱 - -### 5. 主场景加载完成后会触发资源回收 - -当主场景切换完成后,会调用: - -```csharp -Context.Require().ForceUnloadUnusedAssets(gcCollect); -``` - -因此: - -- 场景切换是资源回收的重要时间点 -- `gcCollect` 参数会影响切场景后的回收强度 - -### 6. `LoadScene` 方法名容易误导 - -虽然名字像“同步加载”,但当前实现中: - -- `LoadScene(...)` 仍然是异步加载 -- 区别只是它通过回调返回结果,而不是 `await` - -## 最佳实践 - -- 主场景切换统一交给流程层管理 -- Additive 场景只用于临时叠加内容,不要滥用为主流程状态切换 -- 若需要加载转场动画,可用 `suspendLoad = true` + 手动激活 -- 场景切换后如有场景级服务初始化,放在新的 Scene Scope 生命周期里完成 - -## 常见错误 - -### 1. 试图用 `UnloadAsync` 卸载主场景 - -现象: - -- 返回 `false` 或警告 - -正确方式: - -- 通过加载新的 `Single` 主场景来替换 - -### 2. 把 `LoadScene(...)` 当同步函数使用 - -现象: - -- 加载还没完成就执行后续依赖逻辑 - -规避: - -- 优先使用 `LoadSceneAsync(...)` -- 或把后续逻辑写入回调中 - -### 3. 重复 Additive 加载同一场景 - -现象: - -- 异步版可能直接抛异常 -- 回调版会记录警告 - -规避: - -- 在加载前先用 `IsContainScene(location)` 做检查 - -## 性能注意事项 - -- 场景切换本身是重量级操作,不要把短生命周期面板式内容做成 Additive 场景 -- 进度回调每帧执行,UI 刷新时应尽量轻量 -- 主场景切换后伴随资源回收,切场景阶段要预估回收开销和 GC 波动 diff --git a/Client/Assets/Books/Framework/Runtime/Scene.md.meta b/Client/Assets/Books/Framework/Runtime/Scene.md.meta deleted file mode 100644 index 5d3f83c..0000000 --- a/Client/Assets/Books/Framework/Runtime/Scene.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 13866105ba2b78a4688dbb55ad947045 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Runtime/Timer.md b/Client/Assets/Books/Framework/Runtime/Timer.md deleted file mode 100644 index 2fe83b5..0000000 --- a/Client/Assets/Books/Framework/Runtime/Timer.md +++ /dev/null @@ -1,672 +0,0 @@ -# Timer - -## 模块概述 - -Timer 模块提供统一定时器服务,适合处理: - -- 延时执行 -- 循环轮询 -- UI 倒计时 -- 技能延时结算 -- 超时控制 -- 轻量轮询逻辑 - -它由 `TimerComponent` 注册 `TimerService`,并通过 `GameApp.Timer` 或 `AppServices.Require()` 暴露给业务层。 - -从实现上看,`TimerService` 使用时间轮来管理定时器,重点优化的是: - -- 大量定时器的调度效率 -- 低 GC 的回调执行 -- 支持缩放时间和非缩放时间两套时间基准 - -## 快速开始 - -### 最少步骤 - -1. 在场景中挂载 `TimerComponent` -2. 调用 `GameApp.Timer.AddTimer(...)` -3. 保存返回的 `timerId` -4. 在对象销毁或逻辑结束时调用 `RemoveTimer(timerId)` - -### 最小示例 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class TimerQuickStart : MonoBehaviour -{ - private int _timerId; - - private void Start() - { - _timerId = GameApp.Timer.AddTimer(OnTick, 1f, true); - } - - private void OnDestroy() - { - GameApp.Timer.RemoveTimer(_timerId); - } - - private void OnTick() - { - Debug.Log("Tick"); - } -} -``` - -## 架构说明 - -```text -TimerComponent - └─ TimerService - ├─ TimerHandler - ├─ TimerHandlerNoArgs - ├─ TimerGenericInvokerCache - └─ HierarchicalTimeWheel -``` - -### 关键协作关系 - -- `TimerComponent`:负责在场景中注册 `TimerService` -- `TimerService`:真正执行定时调度 -- `ITimerService`:业务层使用的统一接口 -- `GameApp.Timer`:高频调用入口 - -### 时间基准 - -Timer 模块支持两种时间基准: - -- **缩放时间**:使用 `Time.time` -- **非缩放时间**:使用 `Time.unscaledTime` - -这由 `isUnscaled` 参数控制: - -- `false`:受 `Time.timeScale` 影响 -- `true`:不受 `Time.timeScale` 影响 - -## 核心类与接口 - -### `ITimerService` - -公开能力: - -- `AddTimer(TimerHandler callback, float time, bool isLoop = false, bool isUnscaled = false, params object[] args)` -- `AddTimer(TimerHandlerNoArgs callback, float time, bool isLoop = false, bool isUnscaled = false)` -- `AddTimer(Action callback, T arg, float time, bool isLoop = false, bool isUnscaled = false)` -- `Stop(int timerId)` -- `Resume(int timerId)` -- `IsRunning(int timerId)` -- `GetLeftTime(int timerId)` -- `Restart(int timerId)` -- `RemoveTimer(int timerId)` -- `RemoveAllTimer()` - -### `TimerHandler` - -定义: - -```csharp -public delegate void TimerHandler(params object[] args); -``` - -适合: - -- 参数数量不固定 -- 通用回调 -- 快速搭建原型 - -### `TimerHandlerNoArgs` - -定义: - -```csharp -public delegate void TimerHandlerNoArgs(); -``` - -适合: - -- 无参延时执行 -- 最常见的循环 tick - -### 泛型 `AddTimer` - -适合: - -- 单参数且类型明确的回调 -- 希望避免 `object[]` 拆装箱和手动转换 - -## API 参考 - -### `int AddTimer(TimerHandler callback, float time, bool isLoop = false, bool isUnscaled = false, params object[] args)` - -- 必填参数:`callback` -- 必填参数:`time` -- 可选参数:`isLoop` -- 可选参数:`isUnscaled` -- 可选参数:`args` -- 返回值:`timerId` - -说明: - -- 注册一个支持参数数组的定时器 -- `time` 为延迟秒数 -- `isLoop = true` 时会按相同间隔循环触发 - -适合: - -- 参数个数可变 -- 临时逻辑 -- 不想额外声明泛型回调的场景 - -### `int AddTimer(TimerHandlerNoArgs callback, float time, bool isLoop = false, bool isUnscaled = false)` - -- 必填参数:`callback` -- 必填参数:`time` -- 可选参数:`isLoop` -- 可选参数:`isUnscaled` -- 返回值:`timerId` - -说明: - -- 注册无参定时器 -- 是最常用、最简洁的用法 - -### `int AddTimer(Action callback, T arg, float time, bool isLoop = false, bool isUnscaled = false)` - -- 必填参数:`callback` -- 必填参数:`arg` -- 必填参数:`time` -- 可选参数:`isLoop` -- 可选参数:`isUnscaled` -- 返回值:`timerId` -- 泛型约束:无额外约束 - -说明: - -- 注册单参数强类型定时器 -- 比 `object[] args` 更清晰、更安全 - -### `void Stop(int timerId)` - -- 必填参数:`timerId` -- 返回值:无 - -说明: - -- 把指定定时器标记为停止运行 -- 对无效 `timerId` 为安全无操作 - -### `void Resume(int timerId)` - -- 必填参数:`timerId` -- 返回值:无 - -说明: - -- 恢复一个已停止的定时器 -- 对无效 `timerId` 为安全无操作 - -### `bool IsRunning(int timerId)` - -- 必填参数:`timerId` -- 返回值:`bool` - -说明: - -- 返回该定时器当前是否处于运行状态 -- 对无效 `timerId` 返回 `false` - -### `float GetLeftTime(int timerId)` - -- 必填参数:`timerId` -- 返回值:剩余秒数 - -说明: - -- 返回定时器剩余触发时间 -- 对无效 `timerId` 返回 `0` - -### `void Restart(int timerId)` - -- 必填参数:`timerId` -- 返回值:无 - -说明: - -- 重新调度该定时器 -- 对无效 `timerId` 为安全无操作 - -### `void RemoveTimer(int timerId)` - -- 必填参数:`timerId` -- 返回值:无 - -说明: - -- 从系统中移除指定定时器 -- 是最推荐的结束方式 - -### `void RemoveAllTimer()` - -- 返回值:无 - -说明: - -- 清空当前全部定时器 -- 通常只建议在服务销毁、场景彻底重置或特殊测试环境下使用 - -## 常见用法 - -### 1. 一次性延时执行 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class DelayExample : MonoBehaviour -{ - private int _delayTimer; - - private void Start() - { - _delayTimer = GameApp.Timer.AddTimer(OnDelayFinish, 2f); - } - - private void OnDestroy() - { - GameApp.Timer.RemoveTimer(_delayTimer); - } - - private void OnDelayFinish() - { - Debug.Log("2 seconds later"); - } -} -``` - -### 2. 循环计时器 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class LoopTimerExample : MonoBehaviour -{ - private int _loopTimer; - private int _counter; - - private void Start() - { - _loopTimer = GameApp.Timer.AddTimer(OnLoop, 0.5f, true); - } - - private void OnDestroy() - { - GameApp.Timer.RemoveTimer(_loopTimer); - } - - private void OnLoop() - { - _counter++; - Debug.Log($"Loop count: {_counter}"); - - if (_counter >= 5) - { - GameApp.Timer.RemoveTimer(_loopTimer); - } - } -} -``` - -### 3. 带参数的定时器 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class TimerArgsExample : MonoBehaviour -{ - private int _timerId; - - private void Start() - { - _timerId = GameApp.Timer.AddTimer(OnRewardDelay, 3f, false, false, "Gold", 100); - } - - private void OnDestroy() - { - GameApp.Timer.RemoveTimer(_timerId); - } - - private void OnRewardDelay(params object[] args) - { - string rewardType = (string)args[0]; - int amount = (int)args[1]; - Debug.Log($"Reward => {rewardType}, amount => {amount}"); - } -} -``` - -### 4. 泛型参数定时器 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class GenericTimerExample : MonoBehaviour -{ - private int _timerId; - - private void Start() - { - _timerId = GameApp.Timer.AddTimer(OnDamageDelay, 200, 1.5f); - } - - private void OnDestroy() - { - GameApp.Timer.RemoveTimer(_timerId); - } - - private void OnDamageDelay(int damage) - { - Debug.Log($"Delayed damage: {damage}"); - } -} -``` - -### 5. 不受暂停影响的 UI 倒计时 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class UnscaledCountdownExample : MonoBehaviour -{ - private int _timerId; - private float _left = 5f; - - private void Start() - { - _timerId = GameApp.Timer.AddTimer(OnTick, 1f, true, true); - } - - private void OnDestroy() - { - GameApp.Timer.RemoveTimer(_timerId); - } - - private void OnTick() - { - _left -= 1f; - Debug.Log($"Countdown: {_left}"); - - if (_left <= 0f) - { - GameApp.Timer.RemoveTimer(_timerId); - } - } -} -``` - -### 6. 查询剩余时间 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class LeftTimeExample : MonoBehaviour -{ - private int _timerId; - - private void Start() - { - _timerId = GameApp.Timer.AddTimer(OnFinish, 10f); - } - - private void Update() - { - float left = GameApp.Timer.GetLeftTime(_timerId); - Debug.Log($"Left: {left:F2}s"); - } - - private void OnDestroy() - { - GameApp.Timer.RemoveTimer(_timerId); - } - - private void OnFinish() - { - Debug.Log("Finished"); - } -} -``` - -### 7. 暂停、恢复与重启 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class PauseResumeTimerExample : MonoBehaviour -{ - private int _timerId; - - private void Start() - { - _timerId = GameApp.Timer.AddTimer(OnTick, 1f, true); - } - - private void Update() - { - if (Input.GetKeyDown(KeyCode.S)) - { - GameApp.Timer.Stop(_timerId); - } - - if (Input.GetKeyDown(KeyCode.R)) - { - GameApp.Timer.Resume(_timerId); - } - - if (Input.GetKeyDown(KeyCode.T)) - { - GameApp.Timer.Restart(_timerId); - } - } - - private void OnDestroy() - { - GameApp.Timer.RemoveTimer(_timerId); - } - - private void OnTick() - { - Debug.Log("Running timer"); - } -} -``` - -### 8. 组件生命周期绑定 - -```csharp -using AlicizaX; -using UnityEngine; - -public sealed class SafeTimerOwner : MonoBehaviour -{ - private int _timerId = -1; - - private void OnEnable() - { - _timerId = GameApp.Timer.AddTimer(OnHeartbeat, 2f, true); - } - - private void OnDisable() - { - if (_timerId > 0) - { - GameApp.Timer.RemoveTimer(_timerId); - _timerId = -1; - } - } - - private void OnHeartbeat() - { - Debug.Log("Heartbeat"); - } -} -``` - -## 运行行为细节 - -这一部分基于当前 `TimerService` 实现整理,适合开发时理解边界行为。 - -### 1. 无效 `timerId` 的行为 - -以下方法对无效 `timerId` 都是安全的: - -- `Stop` -- `Resume` -- `Restart` -- `RemoveTimer` - -对应返回值行为: - -- `IsRunning` 返回 `false` -- `GetLeftTime` 返回 `0` - -### 2. 非循环定时器会在触发后自动移除 - -一次性定时器执行回调后,不需要手动调用 `RemoveTimer` - -但如果组件生命周期不确定,仍建议在 `OnDestroy` / `OnDisable` 中做防守式移除。 - -### 3. 回调异常会被捕获 - -`TimerService` 内部会捕获回调异常并记录日志,不会因为单个定时器异常直接打断整个调度链。 - -### 4. `Stop` / `Resume` 的语义更像“运行标记” - -源码层面: - -- `Stop(timerId)` 只是把定时器标记为 `IsRunning = false` -- `Resume(timerId)` 只是把它重新标记为 `true` - -注意点: - -- 如果定时器已经到达触发时刻,但当时处于 `Stop` 状态,那么该次调度不会执行 -- 对循环定时器来说,如果它在应触发的那一刻是停止状态,也不会自动重新挂回时间轮 - -因此更稳妥的经验是: - -- **短暂停顿并在触发前恢复**:可以用 `Stop` / `Resume` -- **需要明确重新开始计时**:优先用 `Restart` - -### 5. `Restart` 对循环定时器更直观 - -当前实现里: - -- 循环定时器的 `Interval = time` -- 非循环定时器的 `Interval = 0` - -这意味着: - -- 对循环定时器调用 `Restart`,会从当前时刻重新按原间隔开始计时 -- 对非循环定时器调用 `Restart`,会因为内部间隔是 `0`,变成“下一次 Tick 几乎立刻触发” - -所以建议: - -- **循环定时器**:可以使用 `Restart` -- **一次性定时器**:如果要重新延时,直接重新创建一个新的 timer 更清晰 - -## 最佳实践 - -### 推荐做法 - -- 把 `timerId` 与对象生命周期绑定 -- UI 倒计时优先用 `isUnscaled = true` -- 对单参数回调优先使用泛型重载 -- 对复杂业务优先在回调中触发业务方法,而不是把整段逻辑都堆进匿名函数 - -### 推荐封装方式 - -如果你的项目里大量使用定时器,建议封装一层: - -- `StartOnce(float delay, Action action)` -- `StartLoop(float interval, Action action)` -- `StopAndClear(ref int timerId)` - -这样可以减少重复样板代码和漏删问题。 - -## 常见错误 - -### 1. 循环定时器不移除 - -现象: - -- 组件销毁后仍继续运行 - -规避: - -- 在 `OnDisable` 或 `OnDestroy` 中移除 - -### 2. 暂停界面仍使用缩放时间 - -现象: - -- 游戏暂停后倒计时也停住 - -规避: - -- UI 倒计时使用 `isUnscaled = true` - -### 3. 在非循环定时器上依赖 `Restart` - -现象: - -- 行为不像“重新开始原延时”,而是几乎立即触发 - -规避: - -- 直接重新创建一次性定时器 - -### 4. `object[] args` 中频繁装箱拆箱 - -现象: - -- 代码可读性差 -- 更容易写错类型转换 - -规避: - -- 单参数时优先用 `AddTimer` - -## 性能注意事项 - -- 少量长生命周期定时器成本很低 -- 大量高频定时器建议业务上合并 -- 能用泛型单参数回调时,优先别用 `object[]` -- 大量短周期循环定时器应谨慎使用,优先考虑合并成统一更新器 - -## 适用场景建议 - -### 适合使用 Timer 模块 - -- 秒级倒计时 -- UI 展示延迟 -- 技能或状态延后执行 -- 轻量循环任务 - -### 不适合使用 Timer 模块 - -- 每帧复杂逻辑 -- 高频实时物理计算 -- 长链路异步流程编排 - -这些场景更适合: - -- `Update` -- `Coroutine` -- `UniTask` -- 专门的状态机/调度器 diff --git a/Client/Assets/Books/Framework/Runtime/Timer.md.meta b/Client/Assets/Books/Framework/Runtime/Timer.md.meta deleted file mode 100644 index 8d0f3b5..0000000 --- a/Client/Assets/Books/Framework/Runtime/Timer.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 6a5da693652014446867c2397a69fd27 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/Framework/Runtime/UI.md b/Client/Assets/Books/Framework/Runtime/UI.md deleted file mode 100644 index 2721b5b..0000000 --- a/Client/Assets/Books/Framework/Runtime/UI.md +++ /dev/null @@ -1,552 +0,0 @@ -# UI - -## 模块概述 - -UI 模块负责窗口创建、资源定位、层级管理、显示/关闭、缓存、过渡动画与界面绑定代码协作。 - -本模块里最容易误解的一点是: - -- `UIHolderObjectBase`(通常简称 **UIHolder**)不是推荐手写的业务类 -- 项目中的大多数 `UIHolder` 都应通过 **UI 绑定工具自动生成** -- 业务代码通常只编写 `UIWindow`、`UIWidget`、`UITabWindow` 这类逻辑类 - -换句话说: - -- **UIHolder 负责“控件引用和预制体桥接”** -- **UI 逻辑类负责“界面行为和生命周期”** - -## 快速开始 - -1. 在场景中挂载 `UIComponent` -2. 为 `UIComponent.uiRoot` 指定 UI 根预制体 -3. 在 `UISettingEditorWindow` 中配置 UI 生成规则 -4. 选中 UI 预制体根节点,执行 `GameObject/UI生成绑定` -5. 让生成器自动生成 `UIHolder` 类并挂回预制体 -6. 在逻辑类中使用 `UIWindow` 或 `UIWidget`,其中 `T` 就是生成出来的 `UIHolder` 类型 - -## 架构说明 - -```text -UIComponent - └─ UIService - ├─ UIBase - ├─ UIWindow - ├─ UIWidget - ├─ UITabWindow - ├─ UIHolderObjectBase - ├─ UIMetaRegistry - ├─ UIResRegistry - └─ UIHolderFactory -``` - -### 角色分工 - -- `UIBase`:UI 逻辑生命周期基类 -- `UIWindow`:顶层窗口逻辑 -- `UIWidget`:挂在窗口或其他 Widget 下的子部件逻辑 -- `UITabWindow`:支持 Tab 页懒加载与切换的窗口基类 -- `UIHolderObjectBase`:预制体绑定脚本基类,负责暴露控件引用、`RectTransform`、转场播放器 -- `UIHolderFactory`:根据注册信息加载预制体并创建对应 Holder - -### `UIBase`、`UIHolderObjectBase`、`UIWidget` 的关系 - -```text -UI 预制体 - └─ 挂载生成的 XXXHolder : UIHolderObjectBase - ↑ -UIWidget / UIWindow - ↑ - 通过泛型参数 T 访问 baseui -``` - -说明: - -- `UIHolderObjectBase` 挂在预制体上,持有控件引用 -- `UIWindow` / `UIWidget` 的泛型参数 `T` 指向该 Holder 类型 -- 在逻辑类内部可以通过 `baseui` 访问生成好的控件字段 - -## UIHolder 的作用 - -`UIHolderObjectBase` 的职责不是写业务逻辑,而是充当: - -- **控件引用容器**:保存按钮、文本、图片、节点等引用 -- **预制体桥接层**:让 UI 逻辑层不直接依赖层级查找 -- **生命周期事件承载层**:暴露 `OnWindowInitEvent`、`OnWindowAfterShowEvent` 等事件 -- **转场入口**:自动寻找并驱动 `IUITransitionPlayer` - -因此推荐做法是: - -- 业务不手写具体 `XXXHolder` -- 由 UI 绑定工具从预制体结构自动生成 - -## UIHolder 自动生成工作流 - -### 1. 配置生成规则 - -先在编辑器中配置 `UIGenerateConfiguration`,核心配置包括: - -- `UIPrefabRootPath`:UI 预制体根目录 -- `GenerateHolderCodePath`:生成代码输出目录 -- `NameSpace`:生成类所在命名空间 -- `LoadType`:`Resources` 或 `AssetBundle` - -这些配置通常通过 `UISettingEditorWindow` 维护。 - -### 2. 按命名规则搭建 UI 预制体 - -生成器会扫描 UI 节点名和组件类型。例如默认规则会识别: - -- `Btn` → 按钮组件 -- `Text` → `TextMeshProUGUI` -- `Img` → `Image` -- `Tf` → `Transform` -- `Rect` → `RectTransform` - -例如: - -- `Btn#Close@` -- `Text#Title@` -- `Img#Icon@` - -生成器会根据这些节点名推断字段名和字段类型。 - -### 3. 选中 UI 预制体根节点 - -支持两种常见操作方式: - -- 在 Project 中选中 prefab 资源 -- 或在 Prefab Mode 中编辑当前预制体 - -### 4. 执行绑定工具 - -菜单入口: - -- `GameObject/UI生成绑定` - -执行后生成器会: - -1. 读取当前 UI 生成配置 -2. 校验预制体路径是否位于配置的 UI 根目录 -3. 扫描可绑定节点 -4. 生成 `XXXHolder` 代码文件 -5. 脚本编译后自动把生成的 `XXXHolder` 挂到目标预制体根节点 -6. 自动回填对应字段引用 - -这意味着: - -- **正常情况下不需要手动创建 Holder 脚本** -- **也不需要手工把字段拖到 Inspector** - -### 5. 生成代码的结果 - -生成的 `UIHolder` 类本质上: - -- 继承自 `UIHolderObjectBase` -- 带有 `UIResAttribute` -- 包含自动生成的控件字段 - -形态类似: - -```csharp -using AlicizaX.UI.Runtime; - -[UIRes(InventoryItemHolder.ResTag, EUIResLoadType.AssetBundle)] -public class InventoryItemHolder : UIHolderObjectBase -{ - public const string ResTag = "UI/Inventory/InventoryItem.prefab"; - - [UnityEngine.SerializeField] private UnityEngine.UI.Button uiBtnClose; - [UnityEngine.SerializeField] private TMPro.TextMeshProUGUI uiTextTitle; - - public UnityEngine.UI.Button BtnClose => uiBtnClose; - public TMPro.TextMeshProUGUI TextTitle => uiTextTitle; -} -``` - -> 上面是示意结构;实际字段名由生成规则决定。 - -## 在 `UIWidget` 中如何引用生成的 UIHolder - -关键点: - -- `T` 就是生成工具输出的 Holder 类型 -- 逻辑类不需要自己声明控件字段 -- 通过 `baseui` 访问自动生成的 Holder 成员 - -例如: - -```csharp -using AlicizaX.UI.Runtime; -using UnityEngine; - -public sealed class InventoryItemWidget : UIWidget -{ - protected override void OnInitialize() - { - baseui.BtnClose.onClick.AddListener(OnClickClose); - } - - protected override void OnOpen() - { - baseui.TextTitle.text = "Potion"; - } - - private void OnClickClose() - { - Close(); - Destroy(); - } -} -``` - -这里的含义是: - -- `InventoryItemHolder` 由 UI 绑定工具生成 -- `InventoryItemWidget` 是手写业务逻辑 -- `UIWidget` 把逻辑和绑定类关联起来 - -## 核心类与接口 - -### `IUIService` - -负责: - -- 初始化 UI 根节点 -- 打开/关闭窗口 -- 查询已打开窗口 -- 获取层级根节点 -- 注入 `ITimerService` - -### `UIBase` - -关键生命周期: - -- `OnInitialize()` -- `OnOpen()` -- `OnClose()` -- `OnDestroy()` -- `OnUpdate()` - -以及对应异步版本: - -- `OnInitializeAsync()` -- `OnOpenAsync()` -- `OnCloseAsync()` - -并提供: - -- `CreateWidgetAsync()` -- `CreateWidgetSync()` -- `RemoveWidget(UIBase widget)` - -### `UIWindow` - -适合顶层窗口,通常用于: - -- 主界面 -- 设置页 -- 背包页 -- 弹窗 - -常用能力: - -- `CloseSelf()` -- 强制关闭 -- 打开后顶层排序与层级遮挡处理 - -### `UIWidget` - -适合子部件,通常用于: - -- 列表项 -- 面板块 -- 详情条目 -- 页签子页面 - -公开方法: - -- `Open(params object[] userDatas)` -- `Close()` -- `Destroy()` - -### `UITabWindow` - -用于页签式窗口,支持: - -- 预注册 Tab -- 按需懒加载 -- `SwitchTab(int index, params object[] userDatas)` - -### `UIHolderObjectBase` - -核心成员: - -- `Target` -- `RectTransform` -- `Visible` -- `OnWindowInitEvent` -- `OnWindowBeforeShowEvent` -- `OnWindowAfterShowEvent` -- `OnWindowBeforeClosedEvent` -- `OnWindowAfterClosedEvent` -- `OnWindowDestroyEvent` - -### `UIHolderFactory` - -`UIHolderFactory` 是 UI 资源实例化与 Holder 绑定的桥梁,作用是: - -- 根据 `UIResRegistry` 中登记的资源信息定位 UI 预制体 -- 调用 `IResourceService` 或 `Resources` 加载 UI 资源 -- 实例化预制体并获取对应的 `UIHolderObjectBase` -- 把生成的 Holder 绑定到 `UIWindow` / `UIWidget` 对应的逻辑实例上 - -你通常**不会在业务层频繁直接调用它**,因为: - -- 打开窗口时,`UIService` 会在内部调用 `UIHolderFactory` -- 创建 Widget 时,`UIBase.CreateWidgetAsync()` / `CreateWidgetSync()` 也会在内部调用它 - -可以把它理解为: - -```text -UI 逻辑类 - -> UIService / UIBase - -> UIHolderFactory - -> 加载预制体 - -> 找到生成的 XXXHolder - -> 绑定到 UIWindow / UIWidget -``` - -#### 典型作用场景 - -1. `ShowUI()` - - `UIService` 找到 `InventoryWindow` 对应的元数据 - - `UIHolderFactory` 根据 `InventoryWindowHolder` 的 `UIResAttribute` 加载预制体 - - 创建并返回 `InventoryWindowHolder` - - 把 Holder 绑定给 `InventoryWindow` - -2. `CreateWidgetAsync(parent)` - - `UIBase` 创建 `InventoryItemWidget` 的元数据 - - `UIHolderFactory` 加载 `InventoryItemHolder` 对应的 Widget 预制体 - - 把 Holder 绑定给 `InventoryItemWidget` - -#### 直接调用示例 - -虽然业务层通常不需要直接调用,但在工具代码、调试代码或特殊预加载场景下,可以这样使用: - -```csharp -using AlicizaX.UI.Runtime; -using Cysharp.Threading.Tasks; -using UnityEngine; - -public sealed class UIHolderFactoryExample : MonoBehaviour -{ - [SerializeField] private Transform previewRoot; - - private async UniTaskVoid Start() - { - InventoryItemHolder holder = await UIHolderFactory.CreateUIHolderAsync(previewRoot); - if (holder != null) - { - holder.TextName.text = "Preview Item"; - holder.TextCount.text = "99"; - } - } -} -``` - -同步版本示例: - -```csharp -using AlicizaX.UI.Runtime; -using UnityEngine; - -public sealed class UIHolderFactorySyncExample : MonoBehaviour -{ - [SerializeField] private Transform previewRoot; - - private void Start() - { - InventoryItemHolder holder = UIHolderFactory.CreateUIHolderSync(previewRoot); - if (holder != null) - { - holder.TextName.text = "Sync Preview"; - holder.TextCount.text = "1"; - } - } -} -``` - -#### 注意事项 - -- `T` 必须是正确的生成型 `UIHolder`,且继承自 `UIHolderObjectBase` -- 对应 Holder 需要已具备 `UIResAttribute`,通常由绑定工具自动生成 -- 如果资源路径错误、预制体未挂对应 Holder,`UIHolderFactory` 绑定会失败 -- 正常业务打开窗口和创建 Widget 时,优先走 `GameApp.UI.ShowUI()`、`CreateWidgetAsync()`,不建议绕过框架直接大量使用工厂 - -## API 参考 - -### `IUIService.Initialize(Transform root, bool isOrthographic)` - -- 必填参数:`root` -- 必填参数:`isOrthographic` -- 返回值:无 -- 说明:初始化 UI 根、Canvas、Camera 与各层级节点 - -### `UniTask ShowUI(params object[] userDatas) where T : UIBase` - -- 可选参数:`userDatas` -- 返回值:`UniTask` -- 泛型约束:`T : UIBase` -- 说明:异步打开窗口 -- 推荐:默认优先使用该方法 - -### `T ShowUISync(params object[] userDatas) where T : UIBase` - -- 返回值:`T` -- 说明:同步打开窗口 -- 注意:仅在资源已就绪时使用 - -### `void CloseUI(bool force = false) where T : UIBase` - -- 可选参数:`force` -- 返回值:无 -- 说明:关闭指定窗口 - -### `CreateWidgetAsync(Transform parent, bool visible = true) where T : UIBase` - -- 必填参数:`parent` -- 可选参数:`visible` -- 返回值:`UniTask` -- 泛型约束:`T : UIBase` -- 说明:从父 UI 创建 Widget - -### `RemoveWidget(UIBase widget)` - -- 必填参数:`widget` -- 返回值:`UniTask` -- 说明:从父 UI 中移除并销毁 Widget - -## 完整使用示例 - -### 示例 1:窗口逻辑 + 自动生成 Holder - -```csharp -using AlicizaX.UI.Runtime; -using UnityEngine; - -[Window(UILayer.UI, fullScreen: true, cacheTime: 10)] -public sealed class InventoryWindow : UIWindow -{ - protected override async Cysharp.Threading.Tasks.UniTask OnInitializeAsync() - { - baseui.BtnClose.onClick.AddListener(CloseSelf); - - InventoryItemWidget item = await CreateWidgetAsync(baseui.TfContent, false); - item.Open("Potion", 10); - } - - protected override void OnOpen() - { - baseui.TextTitle.text = "Inventory"; - } -} -``` - -说明: - -- `InventoryWindowHolder` 推荐由 UI 绑定工具生成 -- `InventoryWindow` 由业务手写 - -### 示例 2:Widget 使用生成的 Holder - -```csharp -using AlicizaX.UI.Runtime; -using UnityEngine; - -public sealed class InventoryItemWidget : UIWidget -{ - private string _itemName; - private int _count; - - protected override void OnInitialize() - { - baseui.BtnUse.onClick.AddListener(OnClickUse); - } - - protected override void OnOpen() - { - _itemName = (string)UserDatas[0]; - _count = (int)UserDatas[1]; - - baseui.TextName.text = _itemName; - baseui.TextCount.text = _count.ToString(); - } - - protected override void OnClose() - { - baseui.TextName.text = string.Empty; - baseui.TextCount.text = string.Empty; - } - - private void OnClickUse() - { - Debug.Log($"Use item: {_itemName}"); - Close(); - Destroy(); - } -} -``` - -### 示例 3:TabWindow - -```csharp -using AlicizaX.UI.Runtime; - -[Window(UILayer.UI, fullScreen: true)] -public sealed class SettingWindow : UITabWindow -{ - protected override void OnInitialize() - { - InitTabVirtuallyView(baseui.TfTabRoot); - InitTabVirtuallyView(baseui.TfTabRoot); - - baseui.BtnGraphics.onClick.AddListener(() => SwitchTab(0)); - baseui.BtnAudio.onClick.AddListener(() => SwitchTab(1)); - } - - protected override void OnOpen() - { - SwitchTab(0); - } -} -``` - -## 最佳实践 - -- **不要手写大多数 UIHolder**,优先使用自动生成 -- 窗口逻辑类只处理状态和行为,控件引用统一放进 Holder -- `OnInitialize` 做一次性事件绑定,`OnOpen` 做参数刷新 -- 默认使用异步打开,避免首帧阻塞 -- 列表项和重复块优先拆成 `UIWidget` - -## 常见错误 - -### 手工编写 UIHolder 导致与生成器冲突 - -- 现象:字段名、命名空间或资源路径不一致 -- 规避:把 Holder 视为生成产物,由工具维护 - -### 在 `OnOpen` 中重复注册按钮事件 - -- 风险:窗口每次打开都会重复绑定 -- 正确做法:放到 `OnInitialize` - -### 把 `UIWidget` 当顶层窗口直接 `ShowUI` - -- `UIWidget` 应由父 `UIBase` 通过 `CreateWidgetAsync()` 或 `CreateWidgetSync()` 创建 - -## 性能注意事项 - -- 首次打开大窗口优先预热资源或异步显示 -- 使用自动生成 Holder 可以避免大量运行时查找和手工拖引用错误 -- 高频销毁/重建的块状内容优先用 `UIWidget` diff --git a/Client/Assets/Books/Framework/Runtime/UI.md.meta b/Client/Assets/Books/Framework/Runtime/UI.md.meta deleted file mode 100644 index d20010e..0000000 --- a/Client/Assets/Books/Framework/Runtime/UI.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 1fe4c4405e0dfd54cbe0f51ac2fbfe4c -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension.meta b/Client/Assets/Books/UIExtension.meta deleted file mode 100644 index dcaca12..0000000 --- a/Client/Assets/Books/UIExtension.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 6877cc0b0a167be4aacecb525d56cc60 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/InputGlyph.md b/Client/Assets/Books/UIExtension/InputGlyph.md deleted file mode 100644 index cbe016c..0000000 --- a/Client/Assets/Books/UIExtension/InputGlyph.md +++ /dev/null @@ -1,94 +0,0 @@ -# UIExtension InputGlyph 输入图标模块手册 - -## 模块概述 -`InputGlyph` 提供输入设备检测、绑定解析、图标数据库查询、UI Image/TMP 输出和重绑定后自动刷新能力。 - -## 适用场景 -- 显示键盘、手柄按键图标。 -- 文本中嵌入按键提示。 -- 输入重绑定后自动刷新 UI 提示。 - -## 快速上手 -1. 创建 `InputGlyphDatabase` 并放到 `Resources/InputGlyphDatabase.asset`。 -2. 给目标对象挂 `InputGlyph`。 -3. 选择动作来源与输出模式。 - -## 详细使用说明 -- `InputGlyph` 会监听 `InputDeviceWatcher` 与 `InputBindingManager.BindingsChanged`。 -- 输出为 `Image` 时设置 `targetImage.sprite`。 -- 输出为 `Text` 时写入 TMP `` 标签或回退显示文本。 - -## 可调用 API -### 类型:`InputGlyph` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/InputGlyph/InputGlyph.cs` -- 枚举:`ActionSourceMode`、`OutputMode` -- 类型:`DeviceCategoryEvent` - - `category` - - `onMatched` - - `onNotMatched` - -### 类型:`InputGlyphBehaviourBase` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/InputGlyph/InputGlyphBehaviourBase.cs` -- 说明:抽象基类,负责设备变化与绑定变化监听。 - -### 类型:`GlyphService` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/InputGlyph/Core/GlyphService.cs` -- `GetBindingControlPath(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)` -- `GetBindingControlPath(InputActionReference actionReference, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)` -- `TryGetTMPTagForActionPath(InputAction action, string compositePartName, InputDeviceWatcher.InputDeviceCategory device, out string tag, out string displayFallback, InputGlyphDatabase db = null)` -- `TryGetTMPTagForActionPath(InputActionReference actionReference, string compositePartName, InputDeviceWatcher.InputDeviceCategory device, out string tag, out string displayFallback, InputGlyphDatabase db = null)` -- `TryGetUISpriteForActionPath(InputAction action, string compositePartName, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite, InputGlyphDatabase db = null)` -- `TryGetUISpriteForActionPath(InputActionReference actionReference, string compositePartName, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite, InputGlyphDatabase db = null)` -- `TryGetTMPTagForActionPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out string tag, out string displayFallback, InputGlyphDatabase db = null)` -- `TryGetUISpriteForActionPath(string controlPath, InputDeviceWatcher.InputDeviceCategory device, out Sprite sprite, InputGlyphDatabase db = null)` -- `GetDisplayNameFromInputAction(InputAction action, string compositePartName = null, InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)` -- `GetDisplayNameFromControlPath(string controlPath)` -- `TryGetBindingControl(InputAction action, string compositePartName, InputDeviceWatcher.InputDeviceCategory? deviceOverride, out InputBinding binding)` - -### 类型:`InputActionReader` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/InputGlyph/Core/InputActionReader.cs` -- `ReadValue(string actionName)` -- `ReadValue(string actionName)` -- `TryReadValue(string actionName, out T value)` -- `TryReadValue(string actionName, out object value)` -- `TryReadValueOnce(UnityEngine.Object owner, string actionName, out T value)` -- `ReadButton(string actionName)` -- `ReadButtonOnce(UnityEngine.Object owner, string actionName)` -- `ReadButtonOnce(int instanceID, string actionName)` -- `ReadButtonOnce(string key, string actionName)` -- `ReadButtonToggle(UnityEngine.Object owner, string actionName)` -- `ReadButtonToggle(int instanceID, string actionName)` -- `ReadButtonToggle(string key, string actionName)` -- `ResetToggledButton(string key, string actionName)` -- `ResetToggledButton(string actionName)` -- `ResetToggledButtons()` - -### 类型:`InputBindingManager` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/InputGlyph/Core/InputBindingManager.cs` -- 公开字段:`actions`、`debugMode` -- 事件:`OnApply`、`OnRebindPrepare`、`OnRebindStart`、`OnRebindEnd`、`OnRebindConflict`、`BindingsChanged` -- 属性:`ActionMaps`、`PreparedRebinds` -- 方法:`FindBestBindingIndexForKeyboard(...)`、`Action(string actionName)`、`TryGetAction(...)`、`StartRebind(...)`、`CancelRebind()`、`ConfirmApply(...)`、`DiscardPrepared()`、`ResetToDefaultAsync()`、`GetBindingPath(...)` -- 公开嵌套类型:`ActionMap`、`Action`、`Binding`、`BindingPath`、`RebindContext` - -### 类型:`InputDeviceWatcher` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/InputGlyph/Core/InputDeviceWatcher.cs` -- 枚举:`InputDeviceCategory` -- 结构:`DeviceContext` -- 属性:`CurrentCategory`、`CurrentDeviceName`、`CurrentDeviceId`、`CurrentVendorId`、`CurrentProductId`、`CurrentContext` -- 事件:`OnDeviceChanged`、`OnDeviceContextChanged` -- 方法:`Initialize()`、`Dispose()` - -### 类型:`InputGlyphDatabase` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/InputGlyph/Data/InputGlyphDatabase.cs` -- 字段:`tables`、`placeholderSprite` -- 方法:`GetTable(string deviceName)`、`GetTable(InputDeviceWatcher.InputDeviceCategory device)`、`GetPlatformIcon(...)`、`TryGetSprite(...)`、`FindSprite(...)`、`FindEntryByControlPath(...)`、`EditorRefreshCache()`、`EditorNormalizeControlPath(...)` -- 类型:`GlyphEntry`、`DeviceGlyphTable` - -## 示例 -```csharp -if (GlyphService.TryGetUISpriteForActionPath(actionRef, null, InputDeviceWatcher.CurrentCategory, out var sprite)) -{ - image.sprite = sprite; -} -``` diff --git a/Client/Assets/Books/UIExtension/InputGlyph.md.meta b/Client/Assets/Books/UIExtension/InputGlyph.md.meta deleted file mode 100644 index 44c28ee..0000000 --- a/Client/Assets/Books/UIExtension/InputGlyph.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: d9b74be0f15fe854fadca69b14d98883 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/RecyclerView.md b/Client/Assets/Books/UIExtension/RecyclerView.md deleted file mode 100644 index 5334ac6..0000000 --- a/Client/Assets/Books/UIExtension/RecyclerView.md +++ /dev/null @@ -1,2402 +0,0 @@ -# RecyclerView 专业手册 - -> 📌 本文档基于 `Packages/com.alicizax.unity.ui.extension/Runtime/RecyclerView` 的运行时 API 编写,重点说明业务侧可直接调用的能力;文中提到的 `private` / `internal` 成员仅用于解释内部机制,不属于业务层 API。 -> -> ⚠️ `SetAdapter()`、`Reset()`、`Refresh()`、`RequestLayout()` 已收敛为框架内部流程;业务层请通过 `UGList`、`UGListCreateHelper`、`UGList.Data`、`Adapter.SetList()`、`Notify*()` 等正式入口驱动列表更新,不要手动调用这些底层方法。所有 UI 相关调用仍应统一放在 Unity 主线程。 -> -> 💡 `RecyclerView` 的 `LayoutManager` 与 `Scroller` 通过 Inspector 序列化引用配置,业务代码通常只“读取与使用”,不建议在运行中直接替换类型。 - -## 目录 - -- [模块概览](#overview) -- [基础概念与共享示例类型](#basics) -- [容器(RecyclerView)](#container) - - [API 说明](#container-api) - - [初始化与刷新](#container-init) - - [定位与焦点控制](#container-focus) -- [适配器(Adapter / GroupAdapter / LoopAdapter / MixedAdapter)](#adapter) - - [API 说明](#adapter-api) - - [基础数据更新](#adapter-data) - - [多模板、分组与循环](#adapter-advanced) -- [布局(LayoutManager 系列)](#layout) - - [API 说明](#layout-api) - - [线性与网格布局](#layout-basic) - - [分页、圆环与异构长度布局](#layout-advanced) -- [滚动器(Scroller / CircleScroller)](#scroller) - - [API 说明](#scroller-api) - - [程序化滚动](#scroller-programmatic) - - [惯性、吸附与自动播放](#scroller-advanced) -- [视图池(ViewProvider / ObjectPool)](#pool) - - [API 说明](#pool-api) - - [预热与复用](#pool-basic) - - [多模板对象池配置](#pool-mixed) -- [导航(RecyclerNavigation)](#navigation) - - [API 说明](#navigation-api) - - [列表内导航](#navigation-inner) - - [列表入口与焦点恢复](#navigation-entry) -- [业务包装层(UGList / UGListExtensions)](#uglist) - - [API 说明](#uglist-api) - - [快速创建与滚动扩展](#uglist-basic) - - [业务封装示例](#uglist-advanced) -- [场景专题](#scenarios) - - [大量列表项复用(含对象池配置)](#scenario-massive) - - [多模板混排](#scenario-mixed) - - [分页加载(加载态、空态、错误态)](#scenario-paging) - - [轮播与循环滚动(自动播放、手动切换)](#scenario-carousel) - - [手柄/键盘导航结合列表滚动](#scenario-nav-scroll) -- [FAQ](#faq) -- [Anti-patterns](#anti-patterns) -- [性能优化建议](#performance) -- [交付前检查清单](#checklist) - - -## 模块概览 - -`RecyclerView` 是一套面向 Unity UGUI 的高性能列表组件,职责拆分如下: - -- 容器:`RecyclerView` 负责模板、可见区、滚动同步、滚动条、焦点恢复。 -- 适配器:`Adapter` 家族负责数据量、模板选择、视图绑定、局部刷新。 -- 布局:`LayoutManager` 家族负责位置计算、可见区间与索引转换。 -- 滚动器:`Scroller` 家族负责拖拽、滚轮、惯性、吸附与平滑滚动。 -- 视图池:`ViewProvider` + `ObjectPool` 负责创建、回收、预热与统计。 -- 导航:`RecyclerNavigationController` 家族负责手柄/键盘在列表内的可达性。 -- 业务包装层:`UGList` 家族负责降低泛型与注册样板代码。 - -> 💡 一般调用顺序是:准备模板与布局 → 创建业务包装层/适配器包装 → 注册 `ItemRender` → 赋值数据;后续布局计算、池预热与首屏刷新由框架自动完成。 - -> ⚠️ 如果 `Templates` 为空、`LayoutManager` 未配置或 `Content` 解析失败,`RecyclerView` 会在首次运行时抛出错误或记录错误日志。 - - -## 基础概念与共享示例类型 - -本节给出后续示例默认共用的最小类型。除非某个示例额外声明了自己的 `Data` / `Holder` / `Render`,否则都可以直接与以下代码一起编译。 - -### 示例基础类型 - -#### 共享类型代码 -```csharp -using AlicizaX.UI; -using UnityEngine; -using UnityEngine.EventSystems; -using UnityEngine.UI; - -namespace RecyclerViewBookSamples -{ - [System.Serializable] - public sealed class DemoSimpleData : ISimpleViewData - { - public string Title; - public string Subtitle; - public Sprite Icon; - } - - [System.Serializable] - public sealed class DemoMixedData : IMixedViewData - { - public string TemplateName { get; set; } // 必须与模板名完全一致 - public string Title; - public string Subtitle; - public Sprite Icon; - } - - [System.Serializable] - public sealed class DemoGroupData : IGroupViewData - { - public string TemplateName { get; set; } // 组头与普通项共用此字段 - public bool Expanded { get; set; } // 组头展开状态 - public int Type { get; set; } // 分组键 - public string Title; - } - - public sealed class DemoItemHolder : ViewHolder - { - public Text title; - public Text subtitle; - public Image icon; - public Button actionButton; - public GameObject selectedMarker; - } - - public sealed class DemoStateHolder : ViewHolder - { - public Text message; - public Button retryButton; - } - - public sealed class DemoSimpleRender : ItemRender - { - protected override void OnBind(DemoSimpleData data, int index) - { - Holder.title.text = $"{index + 1}. {data.Title}"; // 绑定标题 - Holder.subtitle.text = data.Subtitle; // 绑定副标题 - Holder.icon.sprite = data.Icon; // 绑定图标 - } - - protected override void OnSelectionChanged(bool selected) - { - if (Holder.selectedMarker != null) - { - Holder.selectedMarker.SetActive(selected); // 显示选中态 - } - } - } - - public sealed class DemoMixedRender : ItemRender - { - protected override void OnBind(DemoMixedData data, int index) - { - Holder.title.text = $"{index + 1}. {data.Title}"; // 绑定标题 - Holder.subtitle.text = data.Subtitle; // 绑定副标题 - Holder.icon.sprite = data.Icon; // 绑定图标 - } - } -} -``` - -> 📌 所有示例都默认你已经在 Inspector 中把模板预制体上的 `Text`、`Image`、`Button` 等引用拖好。 - - -## 容器(RecyclerView) - -`RecyclerView` 是系统的中心节点:它管理模板、内容区域、当前适配器、布局、滚动器、滚动条以及焦点导航。 - - -### API 说明 - -#### 类型:`RecyclerView` - -源码位置:`Packages/com.alicizax.unity.ui.extension/Runtime/RecyclerView/RecyclerView.cs` - -**属性** - -| 成员名 | 类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `Direction` | `Direction` | `Direction.Vertical`(未序列化时) | 主滚动方向。 | 与 `LayoutManager`、`Scroller` 的方向必须一致。 | -| `Alignment` | `Alignment` | `Alignment.Left`(未序列化时) | 交叉轴对齐方式。 | `Alignment.Center` 会影响内容偏移计算。 | -| `Spacing` | `Vector2` | `Vector2.zero` | Item 间距。 | 竖向列表通常使用 `y`;横向列表通常使用 `x`。 | -| `Padding` | `Vector2` | `Vector2.zero` | 内容内边距。 | 只影响布局计算,不会自动改模板尺寸。 | -| `Scroll` | `bool` | `false` | 是否启用滚动。 | 关闭后 `ScrollTo*()` 直接失效。 | -| `Snap` | `bool` | `false` | 是否在停下后吸附到最近项。 | 只有 `Scroll == true` 时才会真正生效。 | -| `ScrollSpeed` | `float` | `7f` | 平滑滚动速度。 | 建议保持在 `0.5f ~ 50f`。 | -| `WheelSpeed` | `float` | `30f` | 滚轮速度。 | 建议保持在 `1f ~ 50f`。 | -| `ShowScrollBarOnlyWhenScrollable` | `bool` | `false` | 是否仅在内容溢出时显示滚动条。 | 仅对横向/纵向方向生效。 | -| `Templates` | `ViewHolder[]` | `null` | 视图模板集合。 | 不能为空;多模板时模板名必须唯一。 | -| `Content` | `RectTransform` | `null`,首次访问时尝试取第一个子节点 | 内容容器。 | 第一个子节点必须有 `RectTransform`。 | -| `Scrollbar` | `Scrollbar` | `null` | 关联滚动条。 | 仅在启用滚动条时使用。 | -| `Scroller` | `Scroller` | `null` | 当前滚动器实例。 | 通常由 Inspector 指定为 `Scroller` 或 `CircleScroller`。 | -| `ViewProvider` | `ViewProvider` | 懒加载 | 模板创建/回收提供器。 | 依赖 `Templates` 正常初始化。 | -| `PoolStats` | `string` | `string.Empty` | 当前视图池统计文本。 | 仅用于诊断,格式不保证长期稳定。 | -| `LayoutManager` | `LayoutManager` | `null` | 当前布局实例。 | 必须先配置,否则列表初始化时无法完成绑定。 | -| `NavigationController` | `RecyclerNavigationController` | 懒加载 | 列表导航控制器。 | 仅在导航场景下用到。 | - -**事件** - -| 事件名 | 类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `OnIndexChanged` | `Action` | `null` | 当前逻辑索引变化时触发。 | 对循环列表返回的是“真实索引”。 | -| `OnScrollValueChanged` | `Action` | `null` | 滚动位置变化时触发。 | 高频事件,不要在回调里做重活。 | - -> 💡 业务层如需感知当前焦点项,请监听 `OnIndexChanged`;如需读写业务选中态,请使用 `Adapter.ChoiceIndex`。 - -**方法** - -| 方法 | 返回值 | 默认参数 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `TryFocusIndex(int index, bool smooth = false, ScrollAlignment alignment = ScrollAlignment.Center)` | `bool` | `smooth=false`, `alignment=Center` | 滚动并聚焦目标项。 | 目标索引必须在适配器合法范围内。 | -| `TryFocusEntry(MoveDirection entryDirection)` | `bool` | 无 | 从外部把焦点“送入”列表。 | 空列表返回 `false`。 | -| `GetScrollPosition()` | `float` | 无 | 获取当前滚动偏移。 | 没有滚动器时返回 `0`。 | -| `ScrollTo(int index, bool smooth = false)` | `void` | `smooth=false` | 滚到指定项。 | 依赖 `Scroll == true` 且 `Scroller != null`。 | -| `ScrollToWithAlignment(int index, ScrollAlignment alignment, float offset = 0f, bool smooth = false, float duration = 0.3f)` | `void` | `offset=0f`, `smooth=false`, `duration=0.3f` | 以指定对齐方式滚动到目标项。 | `duration` 仅在 `smooth=true` 时有意义。 | - -#### 参数:`TryFocusIndex(int index, bool smooth = false, ScrollAlignment alignment = ScrollAlignment.Center)` - -| 参数名 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| `index` | `int` | 无 | 目标数据索引。 | -| `smooth` | `bool` | `false` | 是否先平滑滚动到可见区再聚焦。 | -| `alignment` | `ScrollAlignment` | `ScrollAlignment.Center` | 聚焦时目标项的对齐方式。 | - -- 返回值:`bool`,成功定位并设置焦点时为 `true`。 -- 使用限制:空列表、越界索引或没有可聚焦对象都会返回 `false`。 - -#### 参数:`ScrollToWithAlignment(...)` - -| 参数名 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| `index` | `int` | 无 | 目标索引。 | -| `alignment` | `ScrollAlignment` | 无 | 目标项停靠位置。 | -| `offset` | `float` | `0f` | 在对齐结果基础上的附加偏移。 | -| `smooth` | `bool` | `false` | 是否平滑滚动。 | -| `duration` | `float` | `0.3f` | 平滑滚动时长。 | - -- 返回值:无。 -- 使用限制:`Scroll == false`、`Scroller == null` 或目标索引非法时无效果。 - -> ⚠️ `ScrollTo()` / `TryFocusIndex()` 依赖列表已通过 `UGList.Data`、`Adapter.SetList()` 或 `Notify*()` 完成一次正式更新;布局与刷新会随这些入口自动完成,业务层不要额外手动补 `RequestLayout()` / `Refresh()`。 - - -### 初始化与刷新 - -#### 示例 1:基础纵向列表初始化 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class BasicRecyclerViewDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: 绑定 LinearLayoutManager + Scroller - - private UGList list; - - private void Awake() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); // 注册默认模板渲染器 - list.Data = new List - { - new DemoSimpleData { Title = "邮件", Subtitle = "今天 09:00" }, - new DemoSimpleData { Title = "任务", Subtitle = "今天 10:30" }, - new DemoSimpleData { Title = "公告", Subtitle = "今天 14:00" }, - }; - } -} -``` - -#### 示例 2:异步拉取后再刷新容器 -```csharp -using System.Collections; -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class AsyncRecyclerViewDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: 模板与 LayoutManager 已配置 - - private UGList list; - private Adapter adapter; - - private IEnumerator Start() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - adapter = list.Adapter; - - yield return new WaitForSeconds(0.5f); // 模拟网络请求 - - list.Data = new List - { - new DemoSimpleData { Title = "远程数据 A", Subtitle = "加载完成" }, - new DemoSimpleData { Title = "远程数据 B", Subtitle = "加载完成" }, - }; // 赋值后框架自动布局并刷新 - } -} -``` - -#### 示例 3:空数据与安全刷新 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class EmptyStateRecyclerViewDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private UGList list; - - private void OnEnable() - { - list ??= UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - list.Data = new List(); // 空列表也是合法输入 - - bool focused = recyclerView.TryFocusIndex(0); // 空列表会返回 false - Debug.Log($"Focus result: {focused}"); - } -} -``` - - -### 定位与焦点控制 - -#### 示例 1:按钮驱动滚动到指定项 -```csharp -using AlicizaX.UI; -using UnityEngine; -using UnityEngine.UI; - -public sealed class ScrollToIndexDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - [SerializeField] private Button jumpButton; - - private void Awake() - { - jumpButton.onClick.AddListener(() => - { - recyclerView.ScrollToWithAlignment( - index: 10, - alignment: ScrollAlignment.Center, - offset: 0f, - smooth: true, - duration: 0.25f); // 以 0.25 秒平滑滚到中间 - }); - } -} -``` - -#### 示例 2:打开面板时恢复焦点 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class FocusRecoveryDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - [SerializeField] private int lastSelectedIndex = 4; - - private void OnEnable() - { - recyclerView.TryFocusIndex(lastSelectedIndex, true, ScrollAlignment.Center); - } -} -``` - -#### 示例 3:监听焦点索引变化 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class FocusTraceDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private void Awake() - { - recyclerView.OnIndexChanged += index => - Debug.Log($"Focused data index: {index}"); - } -} -``` - - -## 适配器(Adapter / GroupAdapter / LoopAdapter / MixedAdapter) - -适配器负责把数据对象映射为模板名、绑定流程和局部刷新操作,是业务层最常直接扩展的模块。 - - -### API 说明 - -#### 类型:`Adapter`(`where T : ISimpleViewData`) - -源码位置:`Packages/com.alicizax.unity.ui.extension/Runtime/RecyclerView/Adapter/Adapter.cs` - -**构造与状态** - -| 成员名 | 类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `Adapter(RecyclerView recyclerView)` | 构造函数 | `list = new List()` | 创建空适配器。 | `recyclerView` 不能为空。 | -| `Adapter(RecyclerView recyclerView, List list)` | 构造函数 | `list ?? new List()` | 创建带初始数据的适配器。 | 传入 `null` 会被替换为空列表。 | -| `ChoiceIndex` | `int` | `-1` | 当前选择索引。 | 超出范围会被自动收敛。 | - -**查询与绑定** - -| 方法 | 返回值 | 默认参数 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `GetItemCount()` | `int` | 无 | 当前布局项数量。 | 普通列表等于数据量;循环列表可能不是。 | -| `GetRealCount()` | `int` | 无 | 真实数据量。 | 循环列表场景优先用它。 | -| `GetViewName(int index)` | `string` | 无 | 返回模板名。 | 单模板默认返回空字符串。 | -| `OnBindViewHolder(ViewHolder viewHolder, int index)` | `void` | 无 | 绑定 Holder。 | 必须先注册与模板匹配的 `ItemRender`。 | -| `OnRecycleViewHolder(ViewHolder viewHolder)` | `void` | 无 | 回收前清理绑定。 | 业务层通常无需手动调用。 | -| `GetData(int index)` | `T` | 无 | 返回数据项。 | 越界返回 `default`。 | - -**刷新通知** - -| 方法 | 返回值 | 默认参数 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `NotifyDataChanged()` | `void` | 无 | 全量刷新。 | 代价最高。 | -| `SetList(List list)` | `void` | 无 | 替换整个数据源。 | 会触发 `RecyclerView.Reset()`。 | -| `NotifyItemChanged(int index, bool relayout = false)` | `void` | `relayout=false` | 刷新单项。 | 仅当项尺寸不变时推荐 `relayout=false`。 | -| `NotifyItemRangeChanged(int index, int count, bool relayout = false)` | `void` | `relayout=false` | 刷新区间。 | `count <= 0` 直接返回。 | -| `NotifyItemInserted(int index)` | `void` | 无 | 通知插入。 | 当前实现会重新请求布局并刷新。 | -| `NotifyItemRangeInserted(int index, int count)` | `void` | 无 | 通知批量插入。 | `count <= 0` 直接返回。 | -| `NotifyItemRemoved(int index)` | `void` | 无 | 通知删除。 | 当前实现会重新请求布局并刷新。 | -| `NotifyItemRangeRemoved(int index, int count)` | `void` | 无 | 通知批量删除。 | `count <= 0` 直接返回。 | - -**渲染器注册** - -| 方法 | 返回值 | 默认参数 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `RegisterItemRender(string viewName = "")` | `void` | `viewName=""` | 以泛型注册渲染器。 | 空字符串表示默认模板。 | -| `RegisterItemRender(Type itemRenderType, string viewName = "")` | `void` | `viewName=""` | 以运行时类型注册渲染器。 | 类型必须继承 `ItemRenderBase`。 | -| `UnregisterItemRender(string viewName = "")` | `bool` | `viewName=""` | 注销渲染器。 | 注销后相关缓存会被释放。 | -| `ClearItemRenderRegistrations()` | `void` | 无 | 清空所有注册。 | 适合切换整套模板时使用。 | - -**集合操作** - -| 方法 | 返回值 | 默认参数 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `Add(T item)` | `void` | 无 | 末尾追加。 | 自动触发插入通知。 | -| `AddRange(IEnumerable collection)` | `void` | 无 | 批量追加。 | `collection == null` 时忽略。 | -| `Insert(int index, T item)` | `void` | 无 | 指定位置插入。 | 越界由 `List` 自己抛错。 | -| `InsertRange(int index, IEnumerable collection)` | `void` | 无 | 批量插入。 | `collection == null` 时忽略。 | -| `Remove(T item)` | `void` | 无 | 删除首个匹配项。 | 找不到时等价于 `RemoveAt(-1)`,最终无效果。 | -| `RemoveAt(int index)` | `void` | 无 | 删除指定索引。 | 越界直接返回。 | -| `RemoveRange(int index, int count)` | `void` | 无 | 删除区间。 | 参数必须满足 `List.RemoveRange` 要求。 | -| `RemoveAll(Predicate match)` | `void` | 无 | 条件删除。 | 始终触发全量刷新。 | -| `Clear()` | `void` | 无 | 清空列表。 | 空列表时直接返回。 | -| `Reverse()` / `Reverse(int index, int count)` | `void` | 无 | 反转顺序。 | 最终触发全量刷新。 | -| `Sort(Comparison comparison)` | `void` | 无 | 排序。 | 最终触发全量刷新。 | - -#### 类型:`GroupAdapter`(`where TData : IGroupViewData, new()`) - -| 方法/成员 | 返回值 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `GroupAdapter(RecyclerView recyclerView, string groupViewName)` | 构造函数 | 无 | 指定组头模板名。 | `NotifyDataChanged()` 前必须传非空模板名。 | -| `Expand(int index)` | `void` | 无 | 展开组头后的子项。 | `index` 必须指向组头。 | -| `Collapse(int index)` | `void` | 无 | 收起某组。 | 不会改原始 `list`,只改展示列表。 | -| `Activate(int index)` | `void` | 无 | 组头时切换展开;普通项时切换选中。 | 常用于点击事件。 | - -#### 类型:`LoopAdapter` - -| 方法/成员 | 返回值 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `GetItemCount()` | `int` | 无 | 有数据时返回 `int.MaxValue`。 | 只适合循环/轮播场景。 | -| `GetRealCount()` | `int` | 无 | 返回真实数据量。 | 业务逻辑应优先使用此值。 | -| `OnBindViewHolder(...)` | `void` | 无 | 绑定时对索引做取模。 | 数据为空时不绑定。 | - -#### 类型:`MixedAdapter`(`where TData : IMixedViewData`) - -| 方法/成员 | 返回值 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `GetViewName(int index)` | `string` | 无 | 返回 `TemplateName`。 | 模板名必须与 `Templates[].name` 一致。 | - -#### 参数:`RegisterItemRender(Type itemRenderType, string viewName = "")` - -| 参数名 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| `itemRenderType` | `Type` | 无 | 渲染器运行时类型。 | -| `viewName` | `string` | `""` | 关联模板名;空字符串表示默认模板。 | - -- 返回值:无。 -- 使用限制:类型必须可实例化,且继承自 `ItemRenderBase`。 - -#### 参数:`NotifyItemChanged(int index, bool relayout = false)` - -| 参数名 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| `index` | `int` | 无 | 目标数据索引。 | -| `relayout` | `bool` | `false` | 是否重新计算布局。 | - -- 返回值:无。 -- 使用限制:如果文本高度、图片尺寸、模板类型发生变化,应该传 `relayout=true`。 - - -### 基础数据更新 - -#### 示例 1:通过 `UGList.Adapter` 进行增删改 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class AdapterCrudDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private UGList list; - private Adapter adapter; - - private void Start() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - list.Data = new List(); - adapter = list.Adapter; - - adapter.Add(new DemoSimpleData { Title = "第一条", Subtitle = "Add()" }); // 自动触发插入 - adapter.Add(new DemoSimpleData { Title = "第二条", Subtitle = "Add()" }); - adapter.Insert(1, new DemoSimpleData { Title = "插入项", Subtitle = "Insert()" }); - } -} -``` - -#### 示例 2:差量刷新单项与区间 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class AdapterPartialRefreshDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private UGList list; - private Adapter adapter; - private readonly List data = new(); - - private void Start() - { - data.Add(new DemoSimpleData { Title = "HP", Subtitle = "100" }); - data.Add(new DemoSimpleData { Title = "MP", Subtitle = "60" }); - data.Add(new DemoSimpleData { Title = "SP", Subtitle = "30" }); - - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - list.Data = data; - adapter = list.Adapter; - } - - public void UpdateHud() - { - data[0].Subtitle = "95"; - adapter.NotifyItemChanged(0); // 尺寸不变,只重绑可见项 - - data[1].Subtitle = "58"; - data[2].Subtitle = "28"; - adapter.NotifyItemRangeChanged(1, 2); // 批量局部刷新 - } -} -``` - -#### 示例 3:尺寸变化时强制重布局 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class AdapterRelayoutDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private UGList list; - private Adapter adapter; - private readonly List data = new(); - - private void Start() - { - data.Add(new DemoSimpleData { Title = "短文本", Subtitle = "1 行" }); - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - list.Data = data; - adapter = list.Adapter; - } - - public void ExpandText() - { - data[0].Subtitle = "这一条文本被拉长后可能导致高度变化,因此需要 relayout=true。"; - adapter.NotifyItemChanged(0, relayout: true); // 尺寸变化时必须重布局 - } -} -``` - - -### 多模板、分组与循环 - -#### 示例 1:多模板注册与绑定 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class DemoLargeHolder : ViewHolder -{ - public UnityEngine.UI.Text title; - public UnityEngine.UI.Text subtitle; -} - -public sealed class DemoLargeRender : ItemRender -{ - protected override void OnBind(DemoMixedData data, int index) - { - Holder.title.text = $"[L] {data.Title}"; // 大卡片模板 - Holder.subtitle.text = data.Subtitle; - } -} - -public sealed class MixedAdapterDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: Templates 含 DemoItemHolder 与 DemoLargeHolder - - private UGMixedList list; - - private void Start() - { - list = UGListCreateHelper.CreateMixed(recyclerView); - list.RegisterItemRender("SmallItem"); // 绑定小模板 - list.RegisterItemRender("LargeItem"); // 绑定大模板 - - list.Data = new List - { - new DemoMixedData { TemplateName = "LargeItem", Title = "头图", Subtitle = "大卡片" }, - new DemoMixedData { TemplateName = "SmallItem", Title = "正文 A", Subtitle = "小卡片" }, - new DemoMixedData { TemplateName = "SmallItem", Title = "正文 B", Subtitle = "小卡片" }, - }; - } -} -``` - -#### 示例 2:分组展开/收起 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class GroupHeaderRender : ItemRender -{ - protected override void OnBind(DemoGroupData data, int index) - { - Holder.title.text = data.Expanded ? $"▼ {data.Title}" : $"▶ {data.Title}"; - Holder.subtitle.text = $"Type = {data.Type}"; - } -} - -public sealed class GroupItemRender : ItemRender -{ - protected override void OnBind(DemoGroupData data, int index) - { - Holder.title.text = data.Title; - Holder.subtitle.text = $"Group {data.Type}"; - } -} - -public sealed class GroupAdapterDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private UGGroupList list; - - private void Start() - { - list = UGListCreateHelper.CreateGroup(recyclerView, "GroupHeader"); - list.RegisterItemRender("GroupHeader"); - list.RegisterItemRender("GroupItem"); - - list.Data = new List - { - new DemoGroupData { TemplateName = "GroupItem", Type = 1, Title = "剑" }, - new DemoGroupData { TemplateName = "GroupItem", Type = 1, Title = "盾" }, - new DemoGroupData { TemplateName = "GroupItem", Type = 2, Title = "药水" }, - }; - } - - public void ToggleFirstGroup() - { - list.Adapter.Activate(0); // 组头位置会切换 Expanded - } -} -``` - -#### 示例 3:循环适配器的虚拟索引 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class LoopAdapterDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private UGLoopList list; - private int virtualIndex; - - private void Start() - { - list = UGListCreateHelper.CreateLoop(recyclerView); - list.RegisterItemRender(); - list.Data = new List - { - new DemoSimpleData { Title = "Banner A" }, - new DemoSimpleData { Title = "Banner B" }, - new DemoSimpleData { Title = "Banner C" }, - }; - - virtualIndex = list.Adapter.GetRealCount() * 100; // 从中间起步,便于双向循环 - recyclerView.ScrollToWithAlignment(virtualIndex, ScrollAlignment.Center); - } - - public void MoveNext() - { - virtualIndex += 1; // 维护虚拟索引,而不是只用真实索引 - recyclerView.ScrollToWithAlignment(virtualIndex, ScrollAlignment.Center, 0f, true, 0.2f); - } -} -``` - - -## 布局(LayoutManager 系列) - -布局器只负责“算位置”,不负责数据绑定。它决定内容尺寸、可见区间、索引到坐标的映射以及吸附基准。 - - -### API 说明 - -#### 类型:`LayoutManager` - -源码位置:`Packages/com.alicizax.unity.ui.extension/Runtime/RecyclerView/Layout/LayoutManager.cs` - -**核心属性** - -| 成员名 | 类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `ViewportSize` | `Vector2` | 运行时计算 | 视口尺寸。 | 只有执行过 `SetContentSize()` 后才可靠。 | -| `ContentSize` | `Vector2` | 运行时计算 | 内容总尺寸。 | 滚动条与滚动范围都依赖此值。 | -| `ContentOffset` | `Vector2` | 运行时计算 | 内容对齐偏移。 | 与 `Alignment` 有关。 | -| `ViewportOffset` | `Vector2` | 运行时计算 | 视口偏移。 | 分页/居中场景常用。 | -| `Adapter` | `IAdapter` | `null` | 当前适配器。 | 由容器内部绑定流程注入。 | -| `ViewProvider` | `ViewProvider` | `null` | 当前视图提供器。 | 由容器内部绑定流程注入。 | -| `RecyclerView` | `RecyclerView` | `null` | 所属列表。 | 由容器内部绑定流程注入。 | -| `Direction` | `Direction` | `Direction.Vertical` | 主轴方向。 | 应与容器一致。 | -| `Alignment` | `Alignment` | `Alignment.Left` | 对齐方式。 | `Center` 会改变偏移计算。 | -| `Spacing` | `Vector2` | `Vector2.zero` | 间距。 | 按方向使用对应轴。 | -| `Padding` | `Vector2` | `Vector2.zero` | 内边距。 | 直接参与内容尺寸计算。 | -| `Unit` | `int` | `1` | 每次创建/回收的步进量。 | 网格通常等于一行或一列的元素数。 | -| `ScrollPosition` | `float` | 运行时读取 | 当前滚动位置。 | 只读,来自 `RecyclerView.GetScrollPosition()`。 | - -**通用方法** - -| 方法 | 返回值 | 默认参数 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `SetContentSize()` | `void` | 无 | 计算视口、内容、偏移。 | 由容器内部布局流程调用。 | -| `UpdateLayout()` | `void` | 无 | 重新布局当前可见 Holder。 | 仅操作已创建视图。 | -| `Layout(ViewHolder viewHolder, int index)` | `void` | 无 | 将指定 Holder 摆放到目标位置。 | 默认按 `CalculatePosition()` 放置。 | -| `CalculateContentSize()` | `Vector2` | 无 | 计算内容总尺寸。 | 由具体布局实现。 | -| `CalculatePosition(int index)` | `Vector2` | 无 | 计算指定索引位置。 | 由具体布局实现。 | -| `CalculateContentOffset()` | `Vector2` | 无 | 计算内容偏移。 | 与对齐方式强相关。 | -| `CalculateViewportOffset()` | `Vector2` | 无 | 计算视口偏移。 | 分页与居中布局更明显。 | -| `GetStartIndex()` / `GetEndIndex()` | `int` | 无 | 当前滚动位置的可见区间。 | `end < start` 时表示当前无需创建项。 | -| `IndexToPosition(int index)` | `float` | 无 | 索引转滚动位置。 | 吸附与 `ScrollTo` 都依赖它。 | -| `PositionToIndex(float position)` | `int` | 无 | 滚动位置转最近索引。 | 吸附时用来找最近项。 | -| `DoItemAnimation()` | `void` | 无 | 执行布局相关动画。 | `PageLayoutManager`、`CircleLayoutManager` 会覆写。 | - -#### 类型:布局派生类 - -| 类型 | 主要特性 | 默认值 | 使用限制 | -| --- | --- | --- | --- | -| `LinearLayoutManager` | 等尺寸线性布局。 | 无额外字段 | 适合单列/单行列表。 | -| `GridLayoutManager` | 网格布局。 | `cellCount = 1` | `cellCount` 通过 Inspector 配置。 | -| `PageLayoutManager` | 分页式线性布局,带缩放动画。 | `minScale = 0.9f` | 常与 `Snap` 联用。 | -| `CircleLayoutManager` | 圆环布局。 | `circleDirection = Positive` | `intervalAngle` 在运行时按项数重算。 | -| `MixedLayoutManager` | 支持不同模板尺寸的异构布局。 | 内部缓存为空 | 最适合多模板或变高度列表。 | - -#### 参数:`IndexToPosition(int index)` - -| 参数名 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| `index` | `int` | 无 | 目标布局索引。 | - -- 返回值:`float`,滚动器目标位置。 -- 使用限制:越界时通常被实现类收敛到合法范围,但业务层仍应传递有效索引。 - - -### 线性与网格布局 - -#### 示例 1:线性列表 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class LinearLayoutDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: LayoutManager = LinearLayoutManager - - private UGList list; - - private void Start() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - list.Data = new List - { - new DemoSimpleData { Title = "线性列表 1" }, - new DemoSimpleData { Title = "线性列表 2" }, - new DemoSimpleData { Title = "线性列表 3" }, - }; - } -} -``` - -#### 示例 2:网格背包 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class GridInventoryDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: LayoutManager = GridLayoutManager, cellCount = 4 - - private UGList list; - - private void Start() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - - var items = new List(); - for (int i = 0; i < 20; i++) - { - items.Add(new DemoSimpleData { Title = $"格子 {i + 1}", Subtitle = $"Index={i}" }); - } - - list.Data = items; - } -} -``` - -#### 示例 3:空网格的边界结果 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class EmptyGridDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private void Start() - { - var list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - list.Data = new List(); // 空网格 - } -} -``` - - -### 分页、圆环与异构长度布局 - -#### 示例 1:分页卡片 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class PageLayoutDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: LayoutManager = PageLayoutManager, Snap = true - - private UGList list; - - private void Start() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - list.Data = new List - { - new DemoSimpleData { Title = "Page 1" }, - new DemoSimpleData { Title = "Page 2" }, - new DemoSimpleData { Title = "Page 3" }, - }; - } -} -``` - -#### 示例 2:圆环菜单 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class CircleMenuDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: LayoutManager = CircleLayoutManager, Scroller = CircleScroller - - private UGList list; - - private void Start() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - list.Data = new List - { - new DemoSimpleData { Title = "装备" }, - new DemoSimpleData { Title = "技能" }, - new DemoSimpleData { Title = "任务" }, - new DemoSimpleData { Title = "地图" }, - }; - } -} -``` - -#### 示例 3:异构长度列表 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class MixedLayoutDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: LayoutManager = MixedLayoutManager - - private UGMixedList list; - - private void Start() - { - list = UGListCreateHelper.CreateMixed(recyclerView); - list.RegisterItemRender("SmallItem"); - list.RegisterItemRender("LargeItem"); // 两个模板尺寸不同 - - list.Data = new List - { - new DemoMixedData { TemplateName = "LargeItem", Title = "长卡片" }, - new DemoMixedData { TemplateName = "SmallItem", Title = "短卡片 A" }, - new DemoMixedData { TemplateName = "SmallItem", Title = "短卡片 B" }, - }; - } -} -``` - - -## 滚动器(Scroller / CircleScroller) - -滚动器负责手势输入与位移变化;布局负责“每个位移意味着什么”。二者解耦后,滚动和显示方式可以灵活组合。 - - -### API 说明 - -#### 类型:`Scroller` - -源码位置:`Packages/com.alicizax.unity.ui.extension/Runtime/RecyclerView/Scroller/Scroller.cs` - -**属性** - -| 成员名 | 类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `Position` | `float` | `0f` | 当前滚动位置。 | 直接赋值不会自动刷新 UI,通常由 `RecyclerView` 驱动。 | -| `Velocity` | `float` | 只读 | 当前速度。 | 仅用于诊断或自定义效果。 | -| `Direction` | `Direction` | `Direction.Vertical` | 滚动方向。 | 应与容器方向一致。 | -| `ContentSize` | `Vector2` | `Vector2.zero` | 内容尺寸。 | 由容器内部布局流程同步更新。 | -| `ViewSize` | `Vector2` | `Vector2.zero` | 视口尺寸。 | 由容器内部布局流程同步更新。 | -| `ScrollSpeed` | `float` | `1f` | `MoveTo` 速度。 | 外部通常通过 `RecyclerView.ScrollSpeed` 配置。 | -| `WheelSpeed` | `float` | `30f` | 滚轮速度。 | 过大容易导致过冲。 | -| `Snap` | `bool` | `false` | 是否启用吸附模式。 | 吸附由容器在停止时完成。 | -| `MaxPosition` | `float` | 只读 | 最大合法滚动位置。 | 由内容和视口尺寸计算。 | -| `ViewLength` | `float` | 只读 | 视口主轴长度。 | 用于边缘阻尼。 | -| `OnValueChanged` | `ScrollerEvent` | 新实例 | 位移变化事件。 | 高频事件。 | -| `OnMoveStoped` | `MoveStopEvent` | 新实例 | 惯性/动画停止事件。 | 拼写保留源码 `Stoped`。 | -| `OnDragging` | `DraggingEvent` | 新实例 | 拖拽开始/结束事件。 | 参数为是否正在拖拽。 | -| `dragStopTime` | `float` | `0f` | 最近一次拖拽时间戳。 | 通常仅供调试。 | - -**方法** - -| 方法 | 返回值 | 默认参数 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `ScrollTo(float position, bool smooth = false)` | `void` | `smooth=false` | 滚到目标位置。 | 不主动夹取范围,推荐经由 `RecyclerView` 调用。 | -| `ScrollToDuration(float position, float duration)` | `void` | 无 | 按固定时长滚动。 | `duration <= 0` 时立即到位。 | -| `ScrollToRatio(float ratio)` | `void` | 无 | 以归一化滚动条值滚动。 | 通常由滚动条驱动。 | -| `OnBeginDrag(...)` / `OnDrag(...)` / `OnEndDrag(...)` | `void` | 无 | 处理拖拽输入。 | 通常由 Unity 事件系统自动调用。 | -| `OnScroll(PointerEventData eventData)` | `void` | 无 | 处理鼠标滚轮。 | 横向列表读取 `scrollDelta.x`。 | - -#### 类型:`CircleScroller` - -| 方法/成员 | 返回值 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `GetDelta(PointerEventData eventData)` | `float` | 无 | 根据环形中心计算拖拽方向。 | 仅适合圆环布局。 | -| `Elastic()` | `void` | 无 | 被覆写为空实现。 | 圆环一般不需要边界回弹。 | - -#### 参数:`ScrollTo(float position, bool smooth = false)` - -| 参数名 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| `position` | `float` | 无 | 目标滚动位置。 | -| `smooth` | `bool` | `false` | 是否使用平滑补间。 | - -- 返回值:无。 -- 使用限制:该方法不会主动把 `position` 夹到 `0 ~ MaxPosition`,需要上层自行保证。 - - -### 程序化滚动 - -#### 示例 1:立即滚动与平滑滚动 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class ProgrammaticScrollDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - public void JumpToTop() - { - recyclerView.ScrollTo(0, smooth: false); // 立即跳到顶部 - } - - public void SmoothToMiddle() - { - recyclerView.ScrollToWithAlignment(15, ScrollAlignment.Center, 0f, true, 0.35f); - } -} -``` - -#### 示例 2:按固定时长滚动 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class TimedScrollDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - public void ScrollByDuration() - { - if (recyclerView.Scroller == null) - { - return; - } - - float target = Mathf.Min(recyclerView.Scroller.MaxPosition, 320f); - recyclerView.Scroller.ScrollToDuration(target, 0.5f); // 0.5 秒到位 - } -} -``` - -#### 示例 3:无滚动能力时的防御式调用 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class ScrollGuardDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - public void TryScroll() - { - if (!recyclerView.Scroll || recyclerView.Scroller == null) - { - Debug.Log("Scroll disabled, ignore request."); // 避免空滚动器导致误判 - return; - } - - recyclerView.ScrollToWithAlignment(2, ScrollAlignment.Start); - } -} -``` - - -### 惯性、吸附与自动播放 - -#### 示例 1:分页列表的吸附体验 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class SnapPageDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: Snap = true, LayoutManager = PageLayoutManager - - private void Awake() - { - recyclerView.Scroll = true; // 启用滚动 - recyclerView.Snap = true; // 停止时自动吸附最近项 - recyclerView.ScrollSpeed = 10f; // 提高分页吸附速度 - } -} -``` - -#### 示例 2:自动播放轮播 -```csharp -using System.Collections; -using AlicizaX.UI; -using UnityEngine; - -public sealed class AutoPlayDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private int nextIndex; - private Coroutine autoPlayCoroutine; - - private void OnEnable() - { - autoPlayCoroutine = StartCoroutine(AutoPlay()); - } - - private void OnDisable() - { - if (autoPlayCoroutine != null) - { - StopCoroutine(autoPlayCoroutine); // 面板关闭时停止自动滚动 - } - } - - private IEnumerator AutoPlay() - { - while (true) - { - yield return new WaitForSeconds(3f); - recyclerView.ScrollToWithAlignment(nextIndex, ScrollAlignment.Center, 0f, true, 0.25f); - nextIndex += 1; // 循环列表可持续递增 - } - } -} -``` - -#### 示例 3:手动上一页 / 下一页 -```csharp -using AlicizaX.UI; -using UnityEngine; -using UnityEngine.UI; - -public sealed class PagerButtonsDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - [SerializeField] private Button prevButton; - [SerializeField] private Button nextButton; - private int currentIndex; - - private void Awake() - { - recyclerView.OnIndexChanged += index => currentIndex = Mathf.Max(index, 0); - - prevButton.onClick.AddListener(() => - { - recyclerView.ScrollToWithAlignment(Mathf.Max(currentIndex - 1, 0), ScrollAlignment.Center, 0f, true, 0.2f); - }); - - nextButton.onClick.AddListener(() => - { - recyclerView.ScrollToWithAlignment(currentIndex + 1, ScrollAlignment.Center, 0f, true, 0.2f); - }); - } -} -``` - - -## 视图池(ViewProvider / ObjectPool) - -视图池是 `RecyclerView` 性能表现的核心:它让“大列表”只保留可见区对象,其他对象进入可复用池而不是销毁重建。 - - -### API 说明 - -#### 类型:`ViewProvider` - -源码位置:`Packages/com.alicizax.unity.ui.extension/Runtime/RecyclerView/ViewProvider/ViewProvider.cs` - -| 方法/成员 | 返回值/类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `Adapter` | `IAdapter` | `null` | 当前适配器。 | 由容器注入。 | -| `LayoutManager` | `LayoutManager` | `null` | 当前布局器。 | 由容器注入。 | -| `ViewHolders` | `IReadOnlyList` | 空集合 | 当前可见 Holder 集。 | 只读视图。 | -| `PoolStats` | `string` | 抽象成员 | 对象池统计。 | 用于诊断。 | -| `GetTemplate(string viewName)` | `ViewHolder` | 无 | 获取模板。 | 多模板时名字必须命中。 | -| `GetTemplates()` | `ViewHolder[]` | 无 | 获取所有模板。 | 模板不能为空。 | -| `Allocate(string viewName)` | `ViewHolder` | 无 | 从池中取出一个 Holder。 | 通常不手动调用。 | -| `Free(string viewName, ViewHolder viewHolder)` | `void` | 无 | 归还 Holder。 | 通常不手动调用。 | -| `Reset()` | `void` | 无 | 清空可见项并销毁池。 | 常用于彻底重建。 | -| `PreparePool()` | `void` | 无 | 依据可见区预热池。 | 由容器内部布局流程触发。 | -| `CreateViewHolder(int index)` | `void` | 无 | 创建一个 `Unit` 批次的 Holder。 | `index` 为布局起点。 | -| `RemoveViewHolder(int index)` | `void` | 无 | 回收一个 `Unit` 批次的 Holder。 | `index` 为布局起点。 | -| `GetViewHolder(int index)` | `ViewHolder` | 无 | 根据布局索引查找 Holder。 | 不可见时返回 `null`。 | -| `GetViewHolderByDataIndex(int dataIndex)` | `ViewHolder` | 无 | 根据数据索引查找 Holder。 | 循环列表可能存在多个映射。 | -| `TryGetViewHoldersByDataIndex(...)` | `bool` | 无 | 获取某数据索引对应的所有 Holder。 | 适合循环列表。 | -| `CalculateViewSize(int index)` | `Vector2` | 无 | 按模板估算尺寸。 | 依赖 `GetViewName(index)` 正确。 | - -#### 类型:`SimpleViewProvider` - -| 成员 | 类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| 内部池 | `ObjectPool` | `initialSize = 0`, `maxSize = 1` | 单模板池。 | 实际容量会在 `PreparePool()` 中扩容。 | -| `PoolStats` | `string` | 实时统计 | 命中、未命中、销毁、活跃等信息。 | 文本格式仅用于日志。 | - -#### 类型:`MixedViewProvider` - -| 成员 | 类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| 内部池 | `MixedObjectPool` | 每类型默认上限 `10` | 多模板池。 | 模板名必须唯一。 | -| `PoolStats` | `string` | 实时统计 | 当前命中、未命中、销毁信息。 | 细粒度活跃数需看 `MixedObjectPool`。 | - -#### 类型:`ObjectPool` - -源码位置:`Packages/com.alicizax.unity.ui.extension/Runtime/RecyclerView/ObjectPool/ObjectPool.cs` - -| 方法/成员 | 返回值/类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `ObjectPool(IObjectFactory factory)` | 构造函数 | `maxSize = Environment.ProcessorCount * 2` | 用 CPU 数量推导上限。 | 初始大小为 `0`。 | -| `ObjectPool(IObjectFactory factory, int maxSize)` | 构造函数 | `initialSize = 0` | 指定最大池容量。 | `maxSize` 需大于等于 `0`。 | -| `ObjectPool(IObjectFactory factory, int initialSize, int maxSize)` | 构造函数 | 无 | 指定预热数量与上限。 | `maxSize < initialSize` 会抛异常。 | -| `Allocate()` | `T` | 无 | 取对象。 | 空池时会创建新对象。 | -| `Free(T obj)` | `void` | 无 | 归还对象。 | 超过上限时会销毁。 | -| `EnsureCapacity(int value)` | `void` | 无 | 调大池上限。 | `value <= 0` 抛异常。 | -| `Warm(int count)` | `void` | 无 | 预热到目标数量。 | 实际不会超过 `maxSize`。 | -| `Dispose()` | `void` | 无 | 销毁所有空闲对象。 | 活跃对象不受影响。 | -| `MaxSize` | `int` | 构造决定 | 当前最大容量。 | 可被 `EnsureCapacity()` 提高。 | -| `ActiveCount` / `InactiveCount` | `int` | `0` | 活跃 / 闲置数量。 | 用于评估是否需要调容量。 | -| `HitCount` / `MissCount` / `DestroyCount` | `int` | `0` | 命中、未命中、销毁统计。 | 用于调优。 | -| `PeakActive` | `int` | `0` | 历史峰值活跃数。 | 可作为容量基线。 | - -#### 类型:`MixedObjectPool` - -| 方法/成员 | 返回值/类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `MixedObjectPool(IMixedObjectFactory factory)` | 构造函数 | `defaultMaxSizePerType = 10` | 使用默认每类型容量。 | `factory` 不能为空。 | -| `MixedObjectPool(IMixedObjectFactory factory, int defaultMaxSizePerType)` | 构造函数 | 无 | 显式指定每类型默认容量。 | `defaultMaxSizePerType <= 0` 抛异常。 | -| `Allocate(string typeName)` | `T` | 无 | 取指定模板类型对象。 | `typeName` 必须存在。 | -| `Free(string typeName, T obj)` | `void` | 无 | 归还指定类型对象。 | 超出该类型上限时会销毁。 | -| `SetMaxSize(string typeName, int value)` | `void` | 无 | 设置单类型上限。 | 不会自动创建对象。 | -| `EnsureCapacity(string typeName, int value)` | `void` | 无 | 单类型扩容。 | `value <= 0` 抛异常。 | -| `Warm(string typeName, int count)` | `void` | 无 | 单类型预热。 | 实际不会超过该类型上限。 | -| `GetActiveCount(string typeName)` | `int` | `0` | 当前活跃数。 | 适合按模板调优。 | -| `GetPeakActiveCount(string typeName)` | `int` | `0` | 历史峰值活跃数。 | 可用于回写容量。 | - - -### 预热与复用 - -#### 示例 1:依赖内建 `PreparePool()` 自动预热 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class BuiltinPoolWarmDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private void Start() - { - var list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - - var data = new List(); - for (int i = 0; i < 200; i++) - { - data.Add(new DemoSimpleData { Title = $"行 {i + 1}" }); - } - - list.Data = data; - Debug.Log(recyclerView.PoolStats); // 输出当前池统计 - } -} -``` - -#### 示例 2:直接使用 `ObjectPool` 自定义容量 -```csharp -using AlicizaX.UI; -using UnityEngine; -using UnityEngine.UI; - -public sealed class DemoPoolHolder : ViewHolder -{ - public Text title; -} - -public sealed class DemoPoolFactory : IObjectFactory -{ - private readonly DemoPoolHolder template; - private readonly Transform parent; - - public DemoPoolFactory(DemoPoolHolder template, Transform parent) - { - this.template = template; - this.parent = parent; - } - - public DemoPoolHolder Create() - { - return Object.Instantiate(template, parent); // 池空时创建 - } - - public void Reset(DemoPoolHolder obj) - { - obj.gameObject.SetActive(false); // 归还时隐藏 - } - - public bool Validate(DemoPoolHolder obj) - { - return obj != null; // 被销毁对象不再回池 - } - - public void Destroy(DemoPoolHolder obj) - { - Object.Destroy(obj.gameObject); // 超上限时销毁 - } -} - -public sealed class ObjectPoolDemo : MonoBehaviour -{ - [SerializeField] private DemoPoolHolder template; - [SerializeField] private Transform parent; - - private ObjectPool pool; - - private void Awake() - { - pool = new ObjectPool(new DemoPoolFactory(template, parent), initialSize: 8, maxSize: 32); - pool.Warm(16); // 预热到 16 个 - pool.EnsureCapacity(48); // 动态扩容到 48 - } - - private void Start() - { - DemoPoolHolder holder = pool.Allocate(); - holder.title.text = "From pool"; - pool.Free(holder); // 归还后进入闲置栈 - - Debug.Log($"hit={pool.HitCount}, miss={pool.MissCount}, inactive={pool.InactiveCount}"); - } -} -``` - -#### 示例 3:校验失败时自动销毁 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class PoolValidateDemo : MonoBehaviour -{ - [SerializeField] private DemoPoolHolder template; - [SerializeField] private Transform parent; - - private ObjectPool pool; - - private void Start() - { - pool = new ObjectPool(new DemoPoolFactory(template, parent), 1, 2); - - DemoPoolHolder holder = pool.Allocate(); - Destroy(holder.gameObject); // 模拟外部错误销毁 - pool.Free(holder); // Validate 失败后不会重新入池 - } -} -``` - - -### 多模板对象池配置 - -#### 示例 1:按模板类型分别预热 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using UnityEngine; - -public sealed class MixedPoolDemo : MonoBehaviour -{ - [SerializeField] private DemoPoolHolder smallTemplate; - [SerializeField] private DemoPoolHolder largeTemplate; - [SerializeField] private Transform parent; - - private MixedObjectPool pool; - - private void Awake() - { - var templates = new Dictionary - { - ["SmallItem"] = smallTemplate, - ["LargeItem"] = largeTemplate, - }; - - pool = new MixedObjectPool( - new UnityMixedComponentFactory(templates, parent), - defaultMaxSizePerType: 6); - - pool.EnsureCapacity("SmallItem", 24); // 小卡片出现频率更高 - pool.EnsureCapacity("LargeItem", 8); // 大卡片数量更少 - pool.Warm("SmallItem", 16); - pool.Warm("LargeItem", 4); - } -} -``` - -#### 示例 2:根据峰值活跃数回写容量 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class MixedPoolTuningDemo : MonoBehaviour -{ - public void PrintRecommendation(MixedObjectPool pool) - { - int peak = pool.GetPeakActiveCount("SmallItem"); - int recommended = peak + 2; // 峰值 + buffer - Debug.Log($"Recommended SmallItem capacity = {recommended}"); - } -} -``` - - -## 导航(RecyclerNavigation) - -导航模块负责把 `EventSystem` 的移动事件转成“列表内跳转 + 必要滚动 + 焦点恢复”。 - - -### API 说明 - -#### 类型:`RecyclerNavigationController` - -源码位置:`Packages/com.alicizax.unity.ui.extension/Runtime/RecyclerView/Navigation/RecyclerNavigationController.cs` - -| 方法/成员 | 返回值 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `RecyclerNavigationController(RecyclerView recyclerView)` | 构造函数 | 无 | 创建导航控制器。 | 一般由 `RecyclerView.NavigationController` 懒加载。 | -| `TryMove(ViewHolder currentHolder, MoveDirection direction, RecyclerNavigationOptions options)` | `bool` | 无 | 尝试从当前项移动到下一个项。 | `currentHolder` 不能为空,空列表返回 `false`。 | - -#### 类型:`RecyclerNavigationBridge` - -| 成员/方法 | 类型/返回值 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `defaultEntryDirection` | `MoveDirection` | `MoveDirection.Down` | 列表首次接收焦点时的进入方向。 | Inspector 配置。 | -| `OnSelect(...)` | `void` | 无 | 当桥节点被选中时尝试进入列表。 | 挂在 `RecyclerView` 同节点上。 | -| `OnMove(...)` | `void` | 无 | 当桥节点收到方向键时尝试进入列表。 | 失败后才回落到原 `Selectable` 行为。 | -| `OnSubmit(...)` | `void` | 无 | 提交键进入列表。 | 常用于确认键。 | - -#### 类型:`RecyclerNavigationOptions` - -| 成员 | 类型 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `Wrap` | `bool` | 构造决定 | 是否允许首尾环绕。 | 对非循环列表通常应关闭。 | -| `SmoothScroll` | `bool` | 构造决定 | 导航时是否平滑滚动。 | 开启后焦点会等待滚动完成。 | -| `Alignment` | `ScrollAlignment` | 构造决定 | 导航触发滚动时的对齐方式。 | 常用 `Center`。 | -| `Clamped` | `RecyclerNavigationOptions` | `wrap=false, smooth=false, alignment=Center` | 不环绕方案。 | 静态只读。 | -| `Circular` | `RecyclerNavigationOptions` | `wrap=true, smooth=false, alignment=Center` | 允许环绕方案。 | 静态只读。 | - -#### 类型:`ItemInteractionProxy` / `RecyclerItemSelectable` - -| 成员/方法 | 类型/返回值 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `GetSelectable()` | `Selectable` | 无 | 返回真正参与导航的焦点锚点。 | 若节点上没有 `Selectable` 会自动补 `RecyclerItemSelectable`。 | -| `Bind(IItemInteractionHost)` | `void` | 无 | 绑定交互宿主。 | 由 `ItemRender` 内部驱动。 | -| `Clear()` | `void` | 无 | 清理交互宿主与状态。 | 回收时自动调用。 | - - -### 列表内导航 - -#### 示例 1:ItemRender 启用点击、移动与提交 -```csharp -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; -using UnityEngine.EventSystems; - -public sealed class NavigableItemRender : ItemRender -{ - public override ItemInteractionFlags InteractionFlags => - ItemInteractionFlags.PointerClick | - ItemInteractionFlags.Select | - ItemInteractionFlags.Move | - ItemInteractionFlags.Submit; - - protected override void OnBind(DemoSimpleData data, int index) - { - Holder.title.text = data.Title; - Holder.subtitle.text = $"可导航项 {index}"; - } - - protected override void OnPointerClick(PointerEventData eventData) - { - Debug.Log($"Clicked {CurrentIndex}"); // 点击时选择当前项 - } - - protected override void OnSubmit(BaseEventData eventData) - { - Debug.Log($"Submit {CurrentIndex}"); // 回车 / A 键确认 - } -} -``` - -#### 示例 2:主动驱动导航器移动 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class ManualNavigationDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - private int currentIndex; - - private void Awake() - { - recyclerView.OnIndexChanged += index => currentIndex = Mathf.Max(index, 0); - } - - private void Update() - { - if (!Input.GetKeyDown(KeyCode.RightArrow)) - { - return; - } - - var current = EventSystem.current?.currentSelectedGameObject?.GetComponentInParent(); - if (current == null) - { - recyclerView.TryFocusIndex(currentIndex, false, ScrollAlignment.Center); - return; - } - - recyclerView.NavigationController.TryMove(current, MoveDirection.Right, RecyclerNavigationOptions.Clamped); - } -} -``` - -#### 示例 3:环绕导航与钳制导航 -```csharp -using AlicizaX.UI; -using RecyclerViewBookSamples; - -public sealed class CircularNavigableRender : ItemRender -{ - public override ItemInteractionFlags InteractionFlags => - ItemInteractionFlags.Select | ItemInteractionFlags.Move; - - protected override RecyclerNavigationOptions NavigationOptions => - RecyclerNavigationOptions.Circular; // 到尾部后回到头部 - - protected override void OnBind(DemoSimpleData data, int index) - { - Holder.title.text = data.Title; - } -} -``` - - -### 列表入口与焦点恢复 - -#### 示例 1:外部按钮把焦点送入列表 -```csharp -using AlicizaX.UI; -using UnityEngine; -using UnityEngine.EventSystems; -using UnityEngine.UI; - -public sealed class EntryBridgeDemo : MonoBehaviour -{ - [SerializeField] private Button entryButton; - [SerializeField] private RecyclerView recyclerView; - - private void Awake() - { - entryButton.onClick.AddListener(() => - { - EventSystem.current.SetSelectedGameObject(recyclerView.gameObject); // 选中桥节点 - recyclerView.TryFocusEntry(MoveDirection.Down); // 然后进入列表 - }); - } -} -``` - -#### 示例 2:重新打开面板时恢复上次项 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class NavigationRestoreDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private int lastIndex; - - private void Awake() - { - recyclerView.OnIndexChanged += index => lastIndex = index; // 记录上次选中索引 - } - - private void OnEnable() - { - recyclerView.TryFocusIndex(lastIndex, smooth: true); // 平滑恢复焦点 - } -} -``` - -#### 示例 3:空列表时避免错误进入 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class EmptyNavigationGuardDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - public void TryEnter() - { - if (!recyclerView.TryFocusEntry(MoveDirection.Down)) - { - Debug.Log("List is empty or not ready."); // 空列表不强行进入 - } - } -} -``` - - -## 业务包装层(UGList / UGListExtensions) - -`UGList` 家族是官方提供的“业务友好包装层”,适合绝大多数游戏页面。它把适配器创建、内部绑定流程和常见滚动操作做了简化。 - - -### API 说明 - -#### 类型:`UGListBase` - -源码位置:`Packages/com.alicizax.unity.ui.extension/Runtime/RecyclerView/UGList.cs` - -| 成员/方法 | 类型/返回值 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `RecyclerView` | `RecyclerView` | 构造注入 | 关联容器。 | 只读。 | -| `Adapter` | `TAdapter` | 构造注入 | 关联适配器。 | 只读。 | -| `Data` | `List` | `null` | 当前数据集合。 | 赋值时会调用 `_adapter.SetList()`。 | -| `RegisterItemRender(string viewName = "")` | `void` | `viewName=""` | 注册渲染器。 | 空字符串表示默认模板。 | -| `RegisterItemRender(Type itemRenderType, string viewName = "")` | `void` | `viewName=""` | 运行时注册渲染器。 | 类型需继承 `ItemRenderBase`。 | -| `UnregisterItemRender(string viewName = "")` | `bool` | `viewName=""` | 注销渲染器。 | 成功返回 `true`。 | -| `ClearItemRenderRegistrations()` | `void` | 无 | 清空注册。 | 适合切换整套模板。 | - -#### 类型:具体包装类 - -| 类型 | 适配器类型 | 说明 | 使用限制 | -| --- | --- | --- | --- | -| `UGList` | `Adapter` | 单模板普通列表。 | `TData : ISimpleViewData` | -| `UGGroupList` | `GroupAdapter` | 分组列表。 | `TData : class, IGroupViewData, new()` | -| `UGLoopList` | `LoopAdapter` | 循环列表。 | `TData : ISimpleViewData, new()` | -| `UGMixedList` | `MixedAdapter` | 多模板列表。 | `TData : IMixedViewData` | - -#### 类型:`UGListCreateHelper` - -| 方法 | 返回值 | 默认参数 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `Create(RecyclerView recyclerView)` | `UGList` | 无 | 创建普通列表包装。 | `TData : ISimpleViewData` | -| `CreateGroup(RecyclerView recyclerView, string groupViewName)` | `UGGroupList` | 无 | 创建分组列表包装。 | 必须提供组头模板名。 | -| `CreateLoop(RecyclerView recyclerView)` | `UGLoopList` | 无 | 创建循环列表包装。 | `TData : ISimpleViewData, new()` | -| `CreateMixed(RecyclerView recyclerView)` | `UGMixedList` | 无 | 创建多模板列表包装。 | `TData : IMixedViewData` | - -#### 类型:`UGListExtensions` - -源码位置:`Packages/com.alicizax.unity.ui.extension/Runtime/RecyclerView/UGListExtensions.cs` - -| 成员/方法 | 返回值 | 默认值 | 说明 | 使用限制 | -| --- | --- | --- | --- | --- | -| `DebugScrollTo` | `bool` | `false` | 输出滚动定位日志。 | 仅用于调试。 | -| `ScrollTo(...)` | `void` | `alignment=Start, offset=0f, smooth=false, duration=0.3f` | 包装 `RecyclerView.ScrollToWithAlignment()`。 | `ugList.RecyclerView` 为空时打印警告。 | -| `ScrollToStart(...)` | `void` | `offset=0f, smooth=false, duration=0.3f` | 滚到起始端。 | 同上。 | -| `ScrollToCenter(...)` | `void` | `offset=0f, smooth=false, duration=0.3f` | 滚到中间。 | 同上。 | -| `ScrollToEnd(...)` | `void` | `offset=0f, smooth=false, duration=0.3f` | 滚到末端。 | 同上。 | - - -### 快速创建与滚动扩展 - -#### 示例 1:`UGList` 最简创建 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class QuickUgListDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private UGList list; - - private void Start() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - list.Data = new List - { - new DemoSimpleData { Title = "快速创建 1" }, - new DemoSimpleData { Title = "快速创建 2" }, - }; - } -} -``` - -#### 示例 2:使用扩展方法滚动 -```csharp -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class UgListExtensionsDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private UGList list; - - private void Start() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - } - - public void CenterItem(int index) - { - list.ScrollToCenter(index, offset: 0f, smooth: true, duration: 0.2f); // 使用扩展方法 - } -} -``` - -#### 示例 3:打开滚动定位调试日志 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class UgListDebugDemo : MonoBehaviour -{ - private void Awake() - { - UGListExtensions.DebugScrollTo = true; // 打开定位日志 - } -} -``` - - -### 业务封装示例 - -#### 示例 1:业务侧封装统一入口 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class MailListFacade -{ - private readonly UGList list; - - public MailListFacade(RecyclerView recyclerView) - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - } - - public void SetMails(List mails) - { - list.Data = mails; // 统一数据入口 - } - - public void FocusFirstUnread() - { - list.RecyclerView.TryFocusIndex(0, true, ScrollAlignment.Center); - } -} -``` - -#### 示例 2:封装“刷新 + 保留焦点” -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; - -public sealed class FocusAwareFacade -{ - private readonly UGList list; - private int lastIndex; - - public FocusAwareFacade(RecyclerView recyclerView) - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - recyclerView.OnIndexChanged += index => lastIndex = index; - } - - public void Replace(List data) - { - list.Data = data; - list.RecyclerView.TryFocusIndex(lastIndex, false); // 数据刷新后恢复焦点 - } -} -``` - - -## 场景专题 - -本节给出更贴近业务的完整场景脚本,重点覆盖性能与交互要求较高的页面。 - - -### 大量列表项复用(含对象池配置) - -#### 示例 1:一万条数据的大列表 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class MassiveListDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: LinearLayoutManager + Scroller - - private UGList list; - - private void Start() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - - var rows = new List(10000); - for (int i = 0; i < 10000; i++) - { - rows.Add(new DemoSimpleData - { - Title = $"战利品 {i + 1}", - Subtitle = $"ID = {100000 + i}", - }); - } - - list.Data = rows; - Debug.Log(recyclerView.PoolStats); // 观察命中、未命中和活跃数 - } -} -``` - -#### 示例 2:手工调对象池容量 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class MassiveListPoolConfigDemo : MonoBehaviour -{ - [SerializeField] private DemoPoolHolder rowTemplate; - [SerializeField] private Transform poolRoot; - - private ObjectPool pool; - - private void Awake() - { - pool = new ObjectPool( - new DemoPoolFactory(rowTemplate, poolRoot), - initialSize: 24, - maxSize: 96); // 明确给出大列表池容量 - - pool.Warm(48); // 提前创建一部分 - } - - private void Start() - { - Debug.Log($"active={pool.ActiveCount}, inactive={pool.InactiveCount}, peak={pool.PeakActive}"); - } -} -``` - -> 💡 大列表调优的首要目标不是“把所有项都放进池里”,而是让“可见项 + 一屏缓冲”命中率足够高。 - - -### 多模板混排 - -#### 示例 1:信息流混排 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class FeedCardHolder : ViewHolder -{ - public UnityEngine.UI.Text title; -} - -public sealed class FeedCardRender : ItemRender -{ - protected override void OnBind(DemoMixedData data, int index) - { - Holder.title.text = $"[Card] {data.Title}"; - } -} - -public sealed class MixedFeedDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: MixedLayoutManager + 2 templates - - private UGMixedList list; - - private void Start() - { - list = UGListCreateHelper.CreateMixed(recyclerView); - list.RegisterItemRender("FeedText"); - list.RegisterItemRender("FeedCard"); - - list.Data = new List - { - new DemoMixedData { TemplateName = "FeedCard", Title = "头图 Banner" }, - new DemoMixedData { TemplateName = "FeedText", Title = "正文 1", Subtitle = "文本块" }, - new DemoMixedData { TemplateName = "FeedText", Title = "正文 2", Subtitle = "文本块" }, - new DemoMixedData { TemplateName = "FeedCard", Title = "推荐卡片" }, - }; - } -} -``` - -#### 示例 2:未知模板名的保护策略 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class MixedTemplateGuardDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - public void BindSafely(List rows) - { - foreach (DemoMixedData row in rows) - { - if (string.IsNullOrEmpty(row.TemplateName)) - { - row.TemplateName = "FeedText"; // 回退到一个可用模板 - } - } - - var list = UGListCreateHelper.CreateMixed(recyclerView); - list.RegisterItemRender("FeedText"); - list.Data = rows; - } -} -``` - -> ⚠️ `MixedViewProvider.GetTemplate()` 找不到模板名时会抛 `KeyNotFoundException`;不要把兜底逻辑留给运行时异常。 - - -### 分页加载(加载态、空态、错误态) - -#### 示例 1:统一状态流 -```csharp -using System; -using System.Collections; -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public abstract class PagedEntryData : IMixedViewData -{ - public string TemplateName { get; set; } -} - -public sealed class PagedContentData : PagedEntryData -{ - public string Title; -} - -public sealed class PagedStateData : PagedEntryData -{ - public string Message; - public Action Retry; -} - -public sealed class PagedContentRender : ItemRender -{ - protected override void OnBind(PagedContentData data, int index) - { - Holder.title.text = data.Title; - Holder.subtitle.text = $"Item {index}"; - } -} - -public sealed class PagedStateRender : ItemRender -{ - private Action retry; - - protected override void OnHolderAttached() - { - base.OnHolderAttached(); - Holder.retryButton.onClick.AddListener(OnRetryClicked); // 只注册一次 - } - - protected override void OnHolderDetached() - { - Holder.retryButton.onClick.RemoveListener(OnRetryClicked); - base.OnHolderDetached(); - } - - protected override void OnBind(PagedStateData data, int index) - { - retry = data.Retry; - Holder.message.text = data.Message; - Holder.retryButton.gameObject.SetActive(data.Retry != null); // 错误态显示重试按钮 - } - - protected override void OnClear() - { - retry = null; // 回收时清掉业务引用 - } - - private void OnRetryClicked() - { - retry?.Invoke(); - } -} - -public sealed class PagedLoadingDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: 需有 ContentItem / LoadingItem / EmptyItem / ErrorItem 模板 - - private UGMixedList list; - private int page = 1; - - private void Start() - { - list = UGListCreateHelper.CreateMixed(recyclerView); - list.RegisterItemRender("ContentItem"); - list.RegisterItemRender("LoadingItem"); - list.RegisterItemRender("EmptyItem"); - list.RegisterItemRender("ErrorItem"); - StartCoroutine(LoadPage()); - } - - private IEnumerator LoadPage() - { - ShowLoading(); - yield return new WaitForSeconds(0.5f); // 模拟网络 - - if (page == 1) - { - ShowContent(new List - { - new PagedContentData { TemplateName = "ContentItem", Title = "第一页-1" }, - new PagedContentData { TemplateName = "ContentItem", Title = "第一页-2" }, - }); - page++; - yield break; - } - - ShowEmpty(); // 第二次请求模拟空态 - } - - private void ShowLoading() - { - list.Data = new List - { - new PagedStateData { TemplateName = "LoadingItem", Message = "正在加载..." }, - }; - } - - private void ShowContent(List rows) - { - list.Data = rows; - } - - private void ShowEmpty() - { - list.Data = new List - { - new PagedStateData { TemplateName = "EmptyItem", Message = "暂无数据" }, - }; - } - - public void ShowError() - { - list.Data = new List - { - new PagedStateData - { - TemplateName = "ErrorItem", - Message = "加载失败,点击重试", - Retry = () => StartCoroutine(LoadPage()), // 错误态回调 - }, - }; - } -} -``` - -#### 示例 2:滚动到底部加载下一页 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class PagedLoadMoreDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private bool isLoading; - - private void Awake() - { - recyclerView.OnScrollValueChanged += TryLoadMore; // 监听滚动变化 - } - - private void TryLoadMore() - { - if (isLoading || recyclerView.Scroller == null) - { - return; - } - - float remain = recyclerView.Scroller.MaxPosition - recyclerView.GetScrollPosition(); - if (remain > 120f) - { - return; // 距离底部还远,不加载 - } - - isLoading = true; - Debug.Log("Reach bottom, load next page."); - } -} -``` - - -### 轮播与循环滚动(自动播放、手动切换) - -#### 示例 1:循环 Banner -```csharp -using System.Collections; -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; -using UnityEngine.UI; - -public sealed class BannerCarouselDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; // Inspector: PageLayoutManager + Snap = true - [SerializeField] private Button prevButton; - [SerializeField] private Button nextButton; - - private UGLoopList list; - private int virtualIndex; - private Coroutine autoPlay; - - private void Start() - { - list = UGListCreateHelper.CreateLoop(recyclerView); - list.RegisterItemRender(); - list.Data = new List - { - new DemoSimpleData { Title = "Banner A" }, - new DemoSimpleData { Title = "Banner B" }, - new DemoSimpleData { Title = "Banner C" }, - }; - - virtualIndex = list.Adapter.GetRealCount() * 100; // 从中间起始,保证可双向滚 - recyclerView.ScrollToWithAlignment(virtualIndex, ScrollAlignment.Center); - - prevButton.onClick.AddListener(() => Move(-1)); // 手动上一张 - nextButton.onClick.AddListener(() => Move(1)); // 手动下一张 - autoPlay = StartCoroutine(AutoPlay()); - } - - private void OnDisable() - { - if (autoPlay != null) - { - StopCoroutine(autoPlay); - } - } - - private IEnumerator AutoPlay() - { - while (true) - { - yield return new WaitForSeconds(3f); - Move(1); // 自动播放 - } - } - - private void Move(int step) - { - virtualIndex += step; - recyclerView.ScrollToWithAlignment(virtualIndex, ScrollAlignment.Center, 0f, true, 0.25f); - } -} -``` - -#### 示例 2:非循环分页的边界保护 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class FiniteCarouselDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - [SerializeField] private int itemCount = 5; - private int currentIndex; - - private void Awake() - { - recyclerView.OnIndexChanged += index => currentIndex = Mathf.Max(index, 0); - } - - public void Next() - { - int target = Mathf.Min(currentIndex + 1, itemCount - 1); - recyclerView.ScrollToWithAlignment(target, ScrollAlignment.Center, 0f, true, 0.2f); - } - - public void Prev() - { - int target = Mathf.Max(currentIndex - 1, 0); - recyclerView.ScrollToWithAlignment(target, ScrollAlignment.Center, 0f, true, 0.2f); - } -} -``` - - -### 手柄/键盘导航结合列表滚动 - -#### 示例 1:打开面板自动聚焦第一项 -```csharp -using System.Collections.Generic; -using AlicizaX.UI; -using RecyclerViewBookSamples; -using UnityEngine; - -public sealed class GamepadListDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - - private UGList list; - - private void Start() - { - list = UGListCreateHelper.Create(recyclerView); - list.RegisterItemRender(); - list.Data = new List - { - new DemoSimpleData { Title = "图像" }, - new DemoSimpleData { Title = "声音" }, - new DemoSimpleData { Title = "操作" }, - }; - recyclerView.TryFocusIndex(0, false, ScrollAlignment.Start); // 初次进入聚焦第一项 - } -} -``` - -#### 示例 2:导航时自动把目标项滚进可见区 -```csharp -using AlicizaX.UI; -using UnityEngine; - -public sealed class NavigationScrollSyncDemo : MonoBehaviour -{ - [SerializeField] private RecyclerView recyclerView; - private int currentIndex; - - private void Awake() - { - recyclerView.OnIndexChanged += index => currentIndex = Mathf.Max(index, 0); - } - - private void Update() - { - if (Input.GetKeyDown(KeyCode.DownArrow)) - { - recyclerView.TryFocusIndex(currentIndex + 1, smooth: true, alignment: ScrollAlignment.End); - } - - if (Input.GetKeyDown(KeyCode.UpArrow)) - { - recyclerView.TryFocusIndex(currentIndex - 1, smooth: true, alignment: ScrollAlignment.Start); - } - } -} -``` - -> 📌 `TryFocusIndex(..., smooth: true)` 的意义不只是“滚动”,还包括“滚动结束后恢复焦点”。 - - -## FAQ - -### 常见问题 - -#### Q1:为什么我已经赋值了数据,但列表里什么都没有? - -- 最常见原因是没有通过 `UGList.Data`、`Adapter.SetList()` 或 `Notify*()` 等正式入口触发列表更新。 -- 其次是 `Templates` 为空、`LayoutManager` 未配置或模板名与 `TemplateName` 不匹配。 -- 如果是多模板列表,还要确认已经为每个模板注册了 `ItemRender`。 - -#### Q2:为什么 `NotifyItemChanged()` 后界面没变? - -- 只会重绑“当前可见”的 Holder;不可见项会等滚动到可见区后再更新。 -- 如果该项高度发生变化,需要用 `NotifyItemChanged(index, true)`。 - -#### Q3:为什么循环列表里业务索引和 `Holder.Index` 不一样? - -- `OnIndexChanged` 回调参数与 `Adapter.ChoiceIndex` 代表的是业务层关注的“真实索引”。 -- `Holder.Index` 是布局层的“虚拟索引”。 -- `LoopAdapter` 绑定时会对虚拟索引取模。 - -#### Q4:为什么混排列表报模板找不到? - -- `MixedAdapter.GetViewName()` 直接返回 `TemplateName`。 -- `MixedViewProvider` 用模板对象的 `name` 做键。 -- 任何大小写、空格或重命名不一致都会导致失败。 - -#### Q5:为什么滚动条不显示? - -- `showScrollBar` 或 `Scroll` 可能未打开。 -- `ShowScrollBarOnlyWhenScrollable = true` 时,内容未超过视口就不会显示。 -- `Direction.Custom` 不支持当前实现的溢出检测。 - -#### Q6:为什么分页吸附不准确? - -- `PageLayoutManager` 最适合固定尺寸页面。 -- 项尺寸变化大时应换 `MixedLayoutManager`,并自己定义滚动定位策略。 - -#### Q7:为什么切换页面后焦点丢了? - -- 视图复用会销毁/回收之前的 `Selectable`。 -- 应该在 `OnEnable()` 或刷新完成后调用 `TryFocusIndex()`。 - -#### Q8:为什么对象池命中率低? - -- 预热量不足。 -- 列表每次都被 `Reset()`。 -- 使用了全量刷新替代差量刷新。 - - -## Anti-patterns - -### 常见反模式与替代方案 - -| 反模式 | 问题 | 正确做法 | -| --- | --- | --- | -| 每次数据变化都调用 `NotifyDataChanged()` | 频繁清空并重建可见项,浪费池命中率。 | 优先使用 `NotifyItemChanged()` / `NotifyItemRangeChanged()`。 | -| 多模板列表里把 `TemplateName` 写成业务文案 | 模板名一旦翻译或重命名就失效。 | 用固定常量,如 `const string FeedCard = "FeedCard";`。 | -| 在 `OnBind()` 里重复注册按钮事件 | Holder 被复用后会出现重复回调。 | 在 `OnHolderAttached()` 注册,在 `OnHolderDetached()` 解绑。 | -| 直接改 `Scroller.Position` 期待界面自动更新 | 位置变了但布局未刷新。 | 优先调用 `RecyclerView.ScrollTo*()`。 | -| 列表数据尚未通过正式入口完成更新就调用 `TryFocusIndex()` | 焦点目标尚未生成。 | 先写入 `Data` / `SetList()` / `Notify*()`,必要时延后到下一帧或 `OnEnable()`。 | -| 使用 `LoopAdapter` 却仍以真实索引做“下一页” | 会反复跳回前几项。 | 维护独立的虚拟索引。 | -| 每次打开页面都重新 new 包装层与数据容器 | 造成 GC 抖动和池统计失真。 | 页面级持有 `UGList` / `Adapter`,只替换数据。 | -| 把“加载中/空态/错误态”放在列表外部多套 UI | 逻辑分裂、切换难维护。 | 用 `MixedAdapter` 把状态也当作一种 Item。 | - - -## 性能优化建议 - -### 建议清单 - -- 视图池预热:首屏数据尽量一次性设置到位,内部布局流程会自动触发 `PreparePool()` 准备“可见项 + 缓冲项”。 -- 优先差量刷新:文本、数值、开关状态变化优先用 `NotifyItemChanged()` / `NotifyItemRangeChanged()`。 -- 避免全量刷新:`NotifyDataChanged()` 只用于排序、模板切换、尺寸大量变化等无法局部更新的情况。 -- 复用业务包装层:面板级缓存 `UGList` / `Adapter`,不要每次打开都重建。 -- 控制模板数量:多模板会降低池命中率;能合并的样式尽量合并。 -- 固定 Item 尺寸优先:能用 `LinearLayoutManager` / `GridLayoutManager` 时不要上来就用 `MixedLayoutManager`。 -- 减少 `OnBind()` 重活:不要在 `OnBind()` 里做同步大图加载、复杂字符串拼接或深层级查找。 -- 缓存引用:`Holder` 上的 `Text`、`Image`、`Button` 在模板初始化时就拖好,避免运行时 `GetComponent()`。 -- 记录峰值活跃数:观察 `PoolStats`、`PeakActive`、`GetPeakActiveCount()`,按峰值 + buffer 设置池容量。 -- 滚动事件节流:`OnScrollValueChanged` 是高频回调,尽量只做阈值判断,不做昂贵计算。 -- 大量分页时保留数据容器:分页追加优先 `AddRange()`,避免整页复制新 `List`。 -- 减少 `Reset()`:`SetList()` 会触发 `RecyclerView.Reset()`;如果只是局部追加,直接对适配器做增量操作更稳。 - -> 💡 一个常见经验值:池容量先按“单屏可见数 + 1 屏缓冲”估,再通过 `PeakActive` 微调,而不是拍脑袋设置超大容量。 - -> ⚠️ 如果列表项高度经常变化,却还持续使用 `NotifyItemChanged(index, false)`,最终会出现错位、重叠或滚动条比例异常。 - - -## 交付前检查清单 - -- `Templates` 已配置,且多模板名称与 `TemplateName` 完全一致。 -- `LayoutManager` 与 `Scroller` 已在 Inspector 上正确绑定。 -- 已为所有模板注册对应 `ItemRender`。 -- 数据赋值后已通过 `Data` / `SetList()` / `Notify*()` 触发内部布局与刷新流程。 -- 差量更新优先使用 `NotifyItemChanged()` / `NotifyItemRangeChanged()`。 -- 焦点型页面已验证 `TryFocusIndex()`、`TryFocusEntry()` 与关闭/重开流程。 -- 大列表页面已检查 `PoolStats` / `PeakActive` / `GetPeakActiveCount()`。 -- 加载态、空态、错误态已验证,且不会因模板缺失报错。 -- 循环列表已使用虚拟索引,不直接拿真实索引做连续滚动。 -- `OnBind()` 中没有重复注册事件,也没有未释放的临时引用。 diff --git a/Client/Assets/Books/UIExtension/RecyclerView.md.meta b/Client/Assets/Books/UIExtension/RecyclerView.md.meta deleted file mode 100644 index 665cf58..0000000 --- a/Client/Assets/Books/UIExtension/RecyclerView.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: a63300f4743578b4cbe32d788b056cd5 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/UXButton.md b/Client/Assets/Books/UIExtension/UXButton.md deleted file mode 100644 index cd15452..0000000 --- a/Client/Assets/Books/UIExtension/UXButton.md +++ /dev/null @@ -1,22 +0,0 @@ -# UIExtension UXButton 按钮模块手册 - -## 模块概述 -`UXButton` 是 `Button` 的增强版本,继承自 `UXSelectable`,统一了鼠标、键盘和手柄提交行为。 - -## 可调用 API -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Button/UXButton.cs` - -### 类型:`UXButton` -- `onClick` -- `OnPointerEnter(PointerEventData eventData)` -- `OnPointerClick(PointerEventData eventData)` -- `OnSubmit(BaseEventData eventData)` - -### 接口:`AlicizaX.UI.Extension.IButton` -- 按钮能力抽象接口。 - -## 快速上手 -```csharp -var btn = gameObject.AddComponent(); -btn.onClick.AddListener(() => UnityEngine.Debug.Log("Clicked")); -``` diff --git a/Client/Assets/Books/UIExtension/UXButton.md.meta b/Client/Assets/Books/UIExtension/UXButton.md.meta deleted file mode 100644 index 069d9f6..0000000 --- a/Client/Assets/Books/UIExtension/UXButton.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: e56d6be154e2f23409e38811debf993c -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/UXController.md b/Client/Assets/Books/UIExtension/UXController.md deleted file mode 100644 index e9779a9..0000000 --- a/Client/Assets/Books/UIExtension/UXController.md +++ /dev/null @@ -1,51 +0,0 @@ -# UIExtension UXController 数据驱动绑定模块手册 - -## 模块概述 -`UXController` 与 `UXBinding` 组成 UI 状态驱动绑定系统,用于把控制器状态映射到 UI 属性值。 - -## 适用场景 -- 页签状态切换。 -- UI 主题或皮肤切换。 -- 可见性、文本、颜色、尺寸等属性联动。 - -## 可调用 API -### 类型:`UXController` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Controller/UXController.cs` -- 属性:`Controllers`、`Bindings`、`ControllerCount` -- `ControllerDefinition` 属性:`Id`、`Name`、`Length`、`Description`、`SelectedIndex` -- 方法:`TryGetControllerById(...)`、`TryGetControllerByName(...)`、`GetControllerByName(...)`、`GetControllerAt(...)`、`GetControllerIndex(...)`、`SetControllerIndex(...)`、`SetControllerIndexByName(...)`、`ResetAllControllers()` - -### 类型:`UXBinding` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Controller/UXBinding.cs` -- 属性:`Controller`、`Entries` -- 方法:`Initialize()`、`SetController(...)`、`CaptureDefaults()`、`ResetToDefaults()`、`PreviewEntry(...)`、`CaptureEntryValue(...)`、`CaptureEntryFallbackValue(...)` -- `BindingEntry` 属性:`ControllerId`、`ControllerIndex`、`Property`、`Value`、`FallbackMode`、`FallbackValue`、`HasCapturedDefault` - -### 类型:`UXBindingValue` -- `BoolValue` -- `FloatValue` -- `StringValue` -- `ColorValue` -- `Vector2Value` -- `Vector3Value` -- `ObjectValue` -- `CopyFrom(UXBindingValue other)` - -### 工具类:`UXBindingPropertyUtility` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Controller/UXBindingPropertyUtility.cs` -- `AllMetadata` -- `GetMetadata(...)` -- `IsSupported(...)` -- `GetSupportedProperties(...)` -- `CaptureValue(...)` -- `ApplyValue(...)` - -### 枚举 -- `UXBindingFallbackMode` -- `UXBindingValueKind` -- `UXBindingProperty` - -## 快速上手 -```csharp -controller.SetControllerIndex("TabState", 1); -``` diff --git a/Client/Assets/Books/UIExtension/UXController.md.meta b/Client/Assets/Books/UIExtension/UXController.md.meta deleted file mode 100644 index c614965..0000000 --- a/Client/Assets/Books/UIExtension/UXController.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 7c684c4c671e41847866139254703281 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/UXDraggable.md b/Client/Assets/Books/UIExtension/UXDraggable.md deleted file mode 100644 index 4c282fe..0000000 --- a/Client/Assets/Books/UIExtension/UXDraggable.md +++ /dev/null @@ -1,16 +0,0 @@ -# UIExtension UXDraggable 拖拽模块手册 - -## 模块概述 -`UXDraggable` 把拖拽生命周期包装为 `UnityEvent`,便于直接在 Inspector 或代码中订阅。 - -## 可调用 API -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Drag/UXDraggable.cs` -- `onDrag` -- `onBeginDrag` -- `onEndDrag` - -## 快速上手 -```csharp -var draggable = gameObject.AddComponent(); -draggable.onBeginDrag.AddListener(_ => UnityEngine.Debug.Log("Begin")); -``` diff --git a/Client/Assets/Books/UIExtension/UXDraggable.md.meta b/Client/Assets/Books/UIExtension/UXDraggable.md.meta deleted file mode 100644 index d13380a..0000000 --- a/Client/Assets/Books/UIExtension/UXDraggable.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 5ab5ad69624ed404da25069210b5d4c2 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/UXGroupToggle.md b/Client/Assets/Books/UIExtension/UXGroupToggle.md deleted file mode 100644 index ec3f51a..0000000 --- a/Client/Assets/Books/UIExtension/UXGroupToggle.md +++ /dev/null @@ -1,22 +0,0 @@ -# UIExtension UXGroup 与 UXToggle 模块手册 - -## 模块概述 -`UXGroup` 与 `UXToggle` 提供增强的单选组与 Toggle 体系,兼容更好的导航和状态控制。 - -## 可调用 API -### 类型:`UXGroup` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Group/UXGroup.cs` -- 字段:`allowSwitchOff`、`defaultToggle` -- 方法:`NotifyToggleOn(...)`、`UnregisterToggle(...)`、`RegisterToggle(...)`、`ContainsToggle(...)`、`EnsureValidState()`、`AnyTogglesOn()`、`ActiveToggles()`、`GetFirstActiveToggle()`、`SetAllTogglesOff(...)`、`Next()`、`Preview()` - -### 类型:`UXToggle` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Group/UXToggle.cs` -- 字段/属性:`toggleTransition`、`graphic`、`group`、`onValueChanged`、`isOn` -- 方法:`Rebuild(...)`、`LayoutComplete()`、`GraphicUpdateComplete()`、`SetIsOnWithoutNotify(...)`、`OnPointerEnter(...)`、`OnPointerClick(...)`、`OnSubmit(...)` -- 类型:`ToggleEvent : UnityEvent` - -## 快速上手 -```csharp -toggle.group = group; -toggle.onValueChanged.AddListener(v => UnityEngine.Debug.Log(v)); -``` diff --git a/Client/Assets/Books/UIExtension/UXGroupToggle.md.meta b/Client/Assets/Books/UIExtension/UXGroupToggle.md.meta deleted file mode 100644 index 93b1d7a..0000000 --- a/Client/Assets/Books/UIExtension/UXGroupToggle.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 74b5de19eb107b64d8e4ef6eb081d2c0 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/UXHelper.md b/Client/Assets/Books/UIExtension/UXHelper.md deleted file mode 100644 index f5b4c5b..0000000 --- a/Client/Assets/Books/UIExtension/UXHelper.md +++ /dev/null @@ -1,31 +0,0 @@ -# UIExtension UXHelper 辅助接口模块手册 - -## 模块概述 -`UXHelper` 用于把 UI 扩展层与项目本地化、音频等外部系统解耦。 - -## 适用场景 -- 接入项目本地化系统。 -- 为 UI 组件统一注入帮助器。 - -## 快速上手 -```csharp -public class DemoLocalizationHelper : IUXLocalizationHelper -{ - public string GetString(string key) => key; -} - -UXComponentExtensionsHelper.SetLocalizationHelper(new DemoLocalizationHelper()); -``` - -## 可调用 API -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/UXHelper.cs` - -### 类型:`UXComponentExtensionsHelper` -- `SetLocalizationHelper(IUXLocalizationHelper helper)` -- `SetAudioHelper(IUXAudioHelper helper)` - -### 接口:`IUXLocalizationHelper` -- `GetString(string key)` - -### 接口:`IUXAudioHelper` -- 说明:当前未定义公开方法,作为项目扩展点保留。 diff --git a/Client/Assets/Books/UIExtension/UXHelper.md.meta b/Client/Assets/Books/UIExtension/UXHelper.md.meta deleted file mode 100644 index 018b981..0000000 --- a/Client/Assets/Books/UIExtension/UXHelper.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 5da96e90155e6a54697735f6428f6812 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/UXHotkey.md b/Client/Assets/Books/UIExtension/UXHotkey.md deleted file mode 100644 index 91723fa..0000000 --- a/Client/Assets/Books/UIExtension/UXHotkey.md +++ /dev/null @@ -1,24 +0,0 @@ -# UIExtension UXHotkey 热键注册模块手册 - -## 模块概述 -`UXHotkey` 模块统一管理 UI 热键触发器,支持作用域、层级阻断、批量绑定与调试输出。 - -## 可调用 API -### 类型:`HotkeyComponent` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Hotkey/HotkeyComponent.cs` -- `HotkeyAction` - -### 接口:`IHotkeyTrigger` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Hotkey/IHotkeyTrigger.cs` -- `HotkeyAction { get; }` - -### 类型:`UXHotkeyRegisterManager` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Hotkey/UXHotkeyRegisterManager.cs` -- `GetDebugInfo()` -- 扩展方法:`BindHotKey(...)`、`UnBindHotKey(...)`、`BindHotKeyBatch(...)`、`UnBindHotKeyBatch(...)` -- 公开辅助类型:`HotkeyRegistration`、`HotkeyScope`、`ActionRegistration`、`TriggerRegistration` - -## 快速上手 -```csharp -trigger.BindHotKey(); -``` diff --git a/Client/Assets/Books/UIExtension/UXHotkey.md.meta b/Client/Assets/Books/UIExtension/UXHotkey.md.meta deleted file mode 100644 index d608c11..0000000 --- a/Client/Assets/Books/UIExtension/UXHotkey.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: d08c92b1da506154683918cc2a4766b4 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/UXImage.md b/Client/Assets/Books/UIExtension/UXImage.md deleted file mode 100644 index 762fa0c..0000000 --- a/Client/Assets/Books/UIExtension/UXImage.md +++ /dev/null @@ -1,41 +0,0 @@ -# UIExtension UXImage 图像增强模块手册 - -## 模块概述 -`UXImage` 扩展了 Unity `Image`,支持渐变、镜像、翻转填充和顶点重映射。 - -## 可调用 API -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Image/UXImage.cs` - -### 类型:`UXImage` -- `gradient` -- `Direction` -- `m_OriginFlipMode` -- `m_FlipMode` -- `flipMode` -- `m_FlipWithCopy` -- `flipWithCopy` -- `flipEdge` -- `m_FlipEdgeHorizontal` -- `flipEdgeHorizontal` -- `m_FlipEdgeVertical` -- `flipEdgeVertical` -- `m_FlipFillCenter` -- `flipFillCenter` -- `m_FlipDirection` -- `flipDirection` -- `RemapVertex(ref UIVertex vertex, FlipMode flipMode, float Min1, float Max1, float Min2, float Max2)` - -### 枚举 -- `ColorType` -- `GradientDirection` -- `FlipPart` -- `FlipDirection` -- `FlipMode` -- `FlipEdge` -- `FlipEdgeHorizontal` -- `FlipEdgeVertical` -- `FlipFillCenter` - -### 辅助类型 -- `Vert2D` -- `Comparer` diff --git a/Client/Assets/Books/UIExtension/UXImage.md.meta b/Client/Assets/Books/UIExtension/UXImage.md.meta deleted file mode 100644 index 45c99a2..0000000 --- a/Client/Assets/Books/UIExtension/UXImage.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 6661d6e0bda019844b46c7b7e087a16a -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/UXNavigation.md b/Client/Assets/Books/UIExtension/UXNavigation.md deleted file mode 100644 index 5590c33..0000000 --- a/Client/Assets/Books/UIExtension/UXNavigation.md +++ /dev/null @@ -1,37 +0,0 @@ -# UIExtension UXNavigation 导航模块手册 - -## 模块概述 -`UXNavigation` 在新输入系统下增强 UI 导航,支持输入模式识别、导航作用域、默认选中项、上次选中记忆和跳过导航对象。 - -## 适用场景 -- 手柄导航友好的菜单与弹窗。 -- 多层 UI 叠加时管理焦点归属。 -- 鼠标与手柄输入模式自动切换。 - -## 可调用 API -### 枚举:`UXInputMode` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Navigation/UXInputMode.cs` -- 表示当前输入模式。 - -### 类型:`UXInputModeService` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Navigation/UXInputModeService.cs` -- `CurrentMode` -- `OnModeChanged` - -### 类型:`UXNavigationScope` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Navigation/UXNavigationScope.cs` -- 属性:`DefaultSelectable`、`RememberLastSelection`、`RequireSelectionWhenGamepad`、`BlockLowerScopes` -- 方法:`InvalidateSelectableCache()` - -### 类型:`UXNavigationSkip` -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Navigation/UXNavigationSkip.cs` -- 标记对象跳过导航体系。 - -### 其他核心类型 -- `UXNavigationRuntime` -- `UXNavigationLayerWatcher` - -## 快速上手 -```csharp -scope.DefaultSelectable = firstSelectable; -``` diff --git a/Client/Assets/Books/UIExtension/UXNavigation.md.meta b/Client/Assets/Books/UIExtension/UXNavigation.md.meta deleted file mode 100644 index 44742bb..0000000 --- a/Client/Assets/Books/UIExtension/UXNavigation.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: f349249d5e2bb9d409be050f28f8562d -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/UXSelectable.md b/Client/Assets/Books/UIExtension/UXSelectable.md deleted file mode 100644 index fd5708c..0000000 --- a/Client/Assets/Books/UIExtension/UXSelectable.md +++ /dev/null @@ -1,22 +0,0 @@ -# UIExtension UXSelectable 选择态增强模块手册 - -## 模块概述 -`UXSelectable` 是 `Selectable` 的增强基类,支持对子图形做额外颜色/精灵过渡,并重写方向查找逻辑。 - -## 可调用 API -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Selectable/UXSelectable.cs` - -### 类型:`TransitionData` -- `targetGraphic` -- `transition` -- `colors` -- `spriteState` - -### 类型:`UXSelectable` -- `FindSelectableOnLeft()` -- `FindSelectableOnRight()` -- `FindSelectableOnUp()` -- `FindSelectableOnDown()` - -## 使用建议 -- 若控件的多个子节点需要跟随选择态变化,可优先通过 `TransitionData` 配置。 diff --git a/Client/Assets/Books/UIExtension/UXSelectable.md.meta b/Client/Assets/Books/UIExtension/UXSelectable.md.meta deleted file mode 100644 index 2cf046b..0000000 --- a/Client/Assets/Books/UIExtension/UXSelectable.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 28b1ee8e6cd0239429b1ff80625c8f6e -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Books/UIExtension/UXText.md b/Client/Assets/Books/UIExtension/UXText.md deleted file mode 100644 index c9cc829..0000000 --- a/Client/Assets/Books/UIExtension/UXText.md +++ /dev/null @@ -1,16 +0,0 @@ -# UIExtension UXTextMeshPro 文本模块手册 - -## 模块概述 -`UXTextMeshPro` 扩展了 `TextMeshProUGUI`,用于接入项目本地化系统并支持编辑器预览刷新。 - -## 可调用 API -源码:`Packages/com.alicizax.unity.ui.extension/Runtime/UXComponent/Text/UXTextMeshPro.cs` -- `SetLocalization(string localizationID)` - -## 快速上手 -```csharp -text.SetLocalization("UI_Common_Confirm"); -``` - -## 注意事项 -- 需先通过 `UXComponentExtensionsHelper.SetLocalizationHelper()` 注入本地化帮助器。 diff --git a/Client/Assets/Books/UIExtension/UXText.md.meta b/Client/Assets/Books/UIExtension/UXText.md.meta deleted file mode 100644 index 2ded17e..0000000 --- a/Client/Assets/Books/UIExtension/UXText.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: cd107b4f367d78f4b903f7be1908cdd6 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Client/Assets/Bundles/Configs/PoolConfig.asset b/Client/Assets/Bundles/Configs/PoolConfig.asset index a0a03b6..61c0973 100644 --- a/Client/Assets/Bundles/Configs/PoolConfig.asset +++ b/Client/Assets/Bundles/Configs/PoolConfig.asset @@ -12,4 +12,70 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 32739bac255eb5f428628746c6e427f4, type: 3} m_Name: PoolConfig m_EditorClassIdentifier: - configs: [] + entries: + - entryName: PoolEntry1 + group: Default + assetPath: + matchMode: 0 + loaderType: 0 + overflowPolicy: 0 + trimPolicy: 1 + activationMode: 0 + resetMode: 1 + minRetained: 0 + softCapacity: 8 + hardCapacity: 8 + idleTrimDelay: 30 + prefabUnloadDelay: 60 + autoRecycleDelay: 0 + trimBatchPerTick: 2 + warmupBatchPerFrame: 2 + warmupFrameBudgetMs: 1 + allowRuntimeExpand: 0 + keepPrefabResident: 0 + aggressiveTrimOnLowMemory: 0 + priority: 0 + - entryName: PoolEntry2 + group: Default + assetPath: + matchMode: 0 + loaderType: 0 + overflowPolicy: 0 + trimPolicy: 1 + activationMode: 0 + resetMode: 1 + minRetained: 0 + softCapacity: 8 + hardCapacity: 8 + idleTrimDelay: 30 + prefabUnloadDelay: 60 + autoRecycleDelay: 0 + trimBatchPerTick: 2 + warmupBatchPerFrame: 2 + warmupFrameBudgetMs: 1 + allowRuntimeExpand: 0 + keepPrefabResident: 0 + aggressiveTrimOnLowMemory: 0 + priority: 1 + - entryName: PoolEntry3 + group: Default + assetPath: + matchMode: 0 + loaderType: 0 + overflowPolicy: 0 + trimPolicy: 1 + activationMode: 0 + resetMode: 1 + minRetained: 0 + softCapacity: 8 + hardCapacity: 8 + idleTrimDelay: 30 + prefabUnloadDelay: 60 + autoRecycleDelay: 0 + trimBatchPerTick: 2 + warmupBatchPerFrame: 2 + warmupFrameBudgetMs: 1 + allowRuntimeExpand: 0 + keepPrefabResident: 0 + aggressiveTrimOnLowMemory: 0 + priority: 2 diff --git a/Client/Assets/Bundles/UIRaw/Atlas/Common/Grunge/Brush Impact 5.png.meta b/Client/Assets/Bundles/UIRaw/Atlas/Common/Grunge/Brush Impact 5.png.meta index 0a81bf5..1d6d064 100644 --- a/Client/Assets/Bundles/UIRaw/Atlas/Common/Grunge/Brush Impact 5.png.meta +++ b/Client/Assets/Bundles/UIRaw/Atlas/Common/Grunge/Brush Impact 5.png.meta @@ -67,7 +67,7 @@ TextureImporter: swizzle: 50462976 cookieLightType: 1 platformSettings: - - serializedVersion: 4 + - serializedVersion: 3 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,7 +80,7 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 + - serializedVersion: 3 buildTarget: Standalone maxTextureSize: 2048 resizeAlgorithm: 0 @@ -93,11 +93,23 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: [] outline: [] - customData: physicsShape: [] bones: [] spriteID: 5e97eb03825dee720800000000000000 @@ -107,8 +119,6 @@ TextureImporter: edges: [] weights: [] secondaryTextures: [] - spriteCustomMetadata: - entries: [] nameFileIdTable: {} mipmapLimitGroupName: pSDRemoveMatte: 0 diff --git a/Client/Assets/Books.meta b/Client/Assets/Bundles/UIRaw/Atlas/Common/bb.meta similarity index 77% rename from Client/Assets/Books.meta rename to Client/Assets/Bundles/UIRaw/Atlas/Common/bb.meta index 65748bf..ad5f85c 100644 --- a/Client/Assets/Books.meta +++ b/Client/Assets/Bundles/UIRaw/Atlas/Common/bb.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 00a54e10cd11cbd419de04cbd506b6d3 +guid: bf510dab34b4e7847a7819b48a1cf122 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Client/Assets/Bundles/UIRaw/Atlas/Common/bb/tst.png b/Client/Assets/Bundles/UIRaw/Atlas/Common/bb/tst.png new file mode 100644 index 0000000..a7b51a2 Binary files /dev/null and b/Client/Assets/Bundles/UIRaw/Atlas/Common/bb/tst.png differ diff --git a/Client/Assets/Bundles/UIRaw/Atlas/Common/bb/tst.png.meta b/Client/Assets/Bundles/UIRaw/Atlas/Common/bb/tst.png.meta new file mode 100644 index 0000000..2ee5bfe --- /dev/null +++ b/Client/Assets/Bundles/UIRaw/Atlas/Common/bb/tst.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 5fe20064a619ceb4aacf49802ee4f70e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 0 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Scripts/Hotfix/GameLogic/HotfixEntry.cs b/Client/Assets/Scripts/Hotfix/GameLogic/HotfixEntry.cs index 5dc7a77..36f3630 100644 --- a/Client/Assets/Scripts/Hotfix/GameLogic/HotfixEntry.cs +++ b/Client/Assets/Scripts/Hotfix/GameLogic/HotfixEntry.cs @@ -41,6 +41,7 @@ namespace GameLogic { GameLocaizationTable table = await GameApp.Resource.LoadAssetAsync("LocalizationTable"); GameApp.Localization.IncreAddLocalizationConfig(table); + GameApp.Resource.UnloadAsset(table); Log.Info("加载多语言配置表完毕"); GameApp.UI.ShowUISync(); Log.Info("sdadasdas"); diff --git a/Client/Assets/Scripts/Startup/LauncherUIHandler.cs b/Client/Assets/Scripts/Startup/LauncherUIHandler.cs index b51a723..39ed760 100644 --- a/Client/Assets/Scripts/Startup/LauncherUIHandler.cs +++ b/Client/Assets/Scripts/Startup/LauncherUIHandler.cs @@ -68,19 +68,18 @@ namespace Unity.Startup.Procedure } - private static void SetProgressUpdate(AssetDownloadProgressUpdateEventArgs gameEventArgs) + private static void SetProgressUpdate(in AssetDownloadProgressUpdateEventArgs gameEventArgs) { - var message = (AssetDownloadProgressUpdateEventArgs)gameEventArgs; - _currentDownloadBytes = message.CurrentDownloadSizeBytes; - float progress = message.CurrentDownloadSizeBytes / (message.TotalDownloadSizeBytes * 1f); - string currentSizeMb = Utility.File.GetBytesSize(message.CurrentDownloadSizeBytes); - string totalSizeMb = Utility.File.GetBytesSize(message.TotalDownloadSizeBytes); + _currentDownloadBytes = gameEventArgs.CurrentDownloadSizeBytes; + float progress = gameEventArgs.CurrentDownloadSizeBytes / (gameEventArgs.TotalDownloadSizeBytes * 1f); + string currentSizeMb = Utility.File.GetBytesSize(gameEventArgs.CurrentDownloadSizeBytes); + string totalSizeMb = Utility.File.GetBytesSize(gameEventArgs.TotalDownloadSizeBytes); string speed = Utility.File.GetLengthString((int)CurrentSpeed); - string line1 = Utility.Text.Format("正在更新,已更新 {0}/{1} ({2:F2}%)", message.CurrentDownloadCount, message.TotalDownloadCount, progress); + string line1 = Utility.Text.Format("正在更新,已更新 {0}/{1} ({2:F2}%)", gameEventArgs.CurrentDownloadCount, gameEventArgs.TotalDownloadCount, progress); string line2 = Utility.Text.Format("已更新大小 {0}MB/{1}MB", currentSizeMb, totalSizeMb); - string line3 = Utility.Text.Format("当前网速 {0}/s,剩余时间 {1}", speed, GetRemainingTime(message.TotalDownloadSizeBytes, message.CurrentDownloadSizeBytes, CurrentSpeed)); + string line3 = Utility.Text.Format("当前网速 {0}/s,剩余时间 {1}", speed, GetRemainingTime(gameEventArgs.TotalDownloadSizeBytes, gameEventArgs.CurrentDownloadSizeBytes, CurrentSpeed)); Log.Info($"{line1} \n {line2}\n {line3}"); diff --git a/Client/Assets/Settings/PC_Renderer.asset b/Client/Assets/Settings/PC_Renderer.asset index e125e49..be134be 100644 --- a/Client/Assets/Settings/PC_Renderer.asset +++ b/Client/Assets/Settings/PC_Renderer.asset @@ -15,9 +15,8 @@ MonoBehaviour: debugShaders: debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3} hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3} - m_RendererFeatures: - - {fileID: 7833122117494664109} - m_RendererFeatureMap: ad6b866f10d7b46c + m_RendererFeatures: [] + m_RendererFeatureMap: m_UseNativeRenderPass: 1 postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2} xrSystemData: {fileID: 11400000, guid: 60e1133243b97e347b653163a8c01b64, type: 2} @@ -56,38 +55,3 @@ MonoBehaviour: m_CopyDepthMode: 0 m_AccurateGbufferNormals: 0 m_IntermediateTextureMode: 0 ---- !u!114 &7833122117494664109 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f62c9c65cf3354c93be831c8bc075510, type: 3} - m_Name: ScreenSpaceAmbientOcclusion - m_EditorClassIdentifier: - m_Active: 1 - m_Settings: - AOMethod: 0 - Downsample: 0 - AfterOpaque: 0 - Source: 1 - NormalSamples: 1 - Intensity: 0.4 - DirectLightingStrength: 0.25 - Radius: 0.3 - Samples: 1 - BlurQuality: 0 - Falloff: 100 - SampleCount: -1 - m_BlueNoise256Textures: - - {fileID: 2800000, guid: 36f118343fc974119bee3d09e2111500, type: 3} - - {fileID: 2800000, guid: 4b7b083e6b6734e8bb2838b0b50a0bc8, type: 3} - - {fileID: 2800000, guid: c06cc21c692f94f5fb5206247191eeee, type: 3} - - {fileID: 2800000, guid: cb76dd40fa7654f9587f6a344f125c9a, type: 3} - - {fileID: 2800000, guid: e32226222ff144b24bf3a5a451de54bc, type: 3} - - {fileID: 2800000, guid: 3302065f671a8450b82c9ddf07426f3a, type: 3} - - {fileID: 2800000, guid: 56a77a3e8d64f47b6afe9e3c95cb57d5, type: 3} - m_Shader: {fileID: 4800000, guid: 0849e84e3d62649e8882e9d6f056a017, type: 3} diff --git a/Client/Assets/TimerBugTest.cs b/Client/Assets/TimerBugTest.cs new file mode 100644 index 0000000..9cb9fa5 --- /dev/null +++ b/Client/Assets/TimerBugTest.cs @@ -0,0 +1,53 @@ +using AlicizaX; +using AlicizaX.Timer.Runtime; +using UnityEngine; + +public sealed class TimerBugTest : MonoBehaviour +{ + private ITimerService _timerService; + + private void Start() + { + _timerService = AppServices.Require(); + + Debug.Log("=== Timer Bug Test Started ==="); + TestNormalOnceTimer(); + TestStopInCallback(); + TestRemoveInCallback(); + } + + private void TestNormalOnceTimer() + { + Debug.Log("[Test 1] Creating normal once timer (1 second)..."); + _timerService.AddTimer(OnNormalOnceTimer, 1f, isLoop: false); + } + + private void TestStopInCallback() + { + Debug.Log("[Test 2] Creating once timer that calls Stop() in callback (2 seconds)..."); + + int timerId = 0; + timerId = _timerService.AddTimer(() => + { + Debug.Log("[Test 2] Timer callback executed, calling Stop()..."); + _timerService.Stop(timerId); + }, 2f, isLoop: false); + } + + private void TestRemoveInCallback() + { + Debug.Log("[Test 3] Creating once timer that calls RemoveTimer() in callback (3 seconds)..."); + + int timerId = 0; + timerId = _timerService.AddTimer(() => + { + Debug.Log("[Test 3] Timer callback executed, calling RemoveTimer()..."); + _timerService.RemoveTimer(timerId); + }, 3f, isLoop: false); + } + + private static void OnNormalOnceTimer() + { + Debug.Log("[Test 1] Timer callback executed!"); + } +} diff --git a/Client/Assets/TimerBugTest.cs.meta b/Client/Assets/TimerBugTest.cs.meta new file mode 100644 index 0000000..ed65411 --- /dev/null +++ b/Client/Assets/TimerBugTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b84897f4b42a76e4fa7c21be2fdd47d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/TimerGenericTest.cs b/Client/Assets/TimerGenericTest.cs new file mode 100644 index 0000000..13dcdbb --- /dev/null +++ b/Client/Assets/TimerGenericTest.cs @@ -0,0 +1,122 @@ +using AlicizaX; +using AlicizaX.Timer.Runtime; +using UnityEngine; + +public sealed class TimerGenericTest : MonoBehaviour +{ + private sealed class IntBox + { + public int Value; + } + + private sealed class PlayerData + { + public int Id; + public string Name; + public int Score; + } + + private sealed class Enemy + { + public string Name; + public int Health; + } + + private ITimerService _timerService; + + private void Start() + { + _timerService = AppServices.Require(); + + Debug.Log("=== Timer Generic Test Started ==="); + TestNoArgs(); + TestIntArg(); + TestStringArg(); + TestPlayerDataArg(); + TestClassArg(); + TestLoopWithArg(); + } + + private void TestNoArgs() + { + Debug.Log("[Test 1] No args timer (1s)"); + _timerService.AddTimer(OnNoArgsCallback, 1f); + } + + private static void OnNoArgsCallback() + { + Debug.Log("[Test 1] Callback executed!"); + } + + private void TestIntArg() + { + Debug.Log("[Test 2] Boxed int reference timer (2s)"); + _timerService.AddTimer(OnIntCallback, new IntBox { Value = 42 }, 2f); + } + + private static void OnIntCallback(IntBox value) + { + Debug.Log(Utility.Text.Format("[Test 2] Int callback executed! Value: {0}", value.Value)); + } + + private void TestStringArg() + { + Debug.Log("[Test 3] String arg timer (3s)"); + _timerService.AddTimer(OnStringCallback, "Hello Timer!", 3f); + } + + private static void OnStringCallback(string message) + { + Debug.Log(Utility.Text.Format("[Test 3] String callback executed! Message: {0}", message)); + } + + private void TestPlayerDataArg() + { + Debug.Log("[Test 4] PlayerData class arg timer (4s)"); + _timerService.AddTimer(OnPlayerDataCallback, new PlayerData + { + Id = 123, + Name = "Alice", + Score = 9999 + }, 4f); + } + + private static void OnPlayerDataCallback(PlayerData data) + { + Debug.Log(Utility.Text.Format("[Test 4] Player callback executed! Player: {0} (ID: {1}, Score: {2})", data.Name, data.Id, data.Score)); + } + + private void TestClassArg() + { + Debug.Log("[Test 5] Class arg timer (5s)"); + _timerService.AddTimer(OnClassCallback, new Enemy + { + Name = "Goblin", + Health = 100 + }, 5f); + } + + private static void OnClassCallback(Enemy enemy) + { + Debug.Log(Utility.Text.Format("[Test 5] Class callback executed! Enemy: {0}, Health: {1}", enemy.Name, enemy.Health)); + } + + private void TestLoopWithArg() + { + Debug.Log("[Test 6] Loop timer with reference arg (0.5s interval)"); + IntBox counter = new IntBox { Value = 1 }; + int timerId = 0; + timerId = _timerService.AddTimer(count => + { + Debug.Log(Utility.Text.Format("[Test 6] Loop callback #{0}", count.Value)); + if (count.Value >= 3) + { + _timerService.RemoveTimer(timerId); + Debug.Log("[Test 6] Loop timer removed"); + return; + } + + count.Value++; + }, counter, 0.5f, isLoop: true); + } +} diff --git a/Client/Assets/TimerGenericTest.cs.meta b/Client/Assets/TimerGenericTest.cs.meta new file mode 100644 index 0000000..2d317a9 --- /dev/null +++ b/Client/Assets/TimerGenericTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d7b412d41637467439d2e1e391f91c67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Books/EditorExtension.meta b/Client/Assets/UI Toolkit.meta similarity index 77% rename from Client/Assets/Books/EditorExtension.meta rename to Client/Assets/UI Toolkit.meta index 1fd5f93..a8bf45e 100644 --- a/Client/Assets/Books/EditorExtension.meta +++ b/Client/Assets/UI Toolkit.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 55079f02326c270468f1eca1808de28c +guid: 23fc170912d33cd499f7f04b662a6e9a folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Client/Assets/Books/Framework.meta b/Client/Assets/UI Toolkit/UnityThemes.meta similarity index 77% rename from Client/Assets/Books/Framework.meta rename to Client/Assets/UI Toolkit/UnityThemes.meta index 62b6000..5fadb47 100644 --- a/Client/Assets/Books/Framework.meta +++ b/Client/Assets/UI Toolkit/UnityThemes.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8e2de7df9f6693246b6ce82a1753c975 +guid: 9f2ea1142ef07324b8f6e23022cc34e5 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Client/Assets/UI Toolkit/UnityThemes/UnityDefaultRuntimeTheme.tss b/Client/Assets/UI Toolkit/UnityThemes/UnityDefaultRuntimeTheme.tss new file mode 100644 index 0000000..1056e07 --- /dev/null +++ b/Client/Assets/UI Toolkit/UnityThemes/UnityDefaultRuntimeTheme.tss @@ -0,0 +1 @@ +@import url("unity-theme://default"); \ No newline at end of file diff --git a/Client/Assets/UI Toolkit/UnityThemes/UnityDefaultRuntimeTheme.tss.meta b/Client/Assets/UI Toolkit/UnityThemes/UnityDefaultRuntimeTheme.tss.meta new file mode 100644 index 0000000..8e4a0bb --- /dev/null +++ b/Client/Assets/UI Toolkit/UnityThemes/UnityDefaultRuntimeTheme.tss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e65490858afddc54a80013f2f6e36e21 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12388, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage.version b/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage.version new file mode 100644 index 0000000..de541bb --- /dev/null +++ b/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage.version @@ -0,0 +1 @@ +Simulate \ No newline at end of file diff --git a/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage_Simulate.bytes b/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage_Simulate.bytes new file mode 100644 index 0000000..96bb79a Binary files /dev/null and b/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage_Simulate.bytes differ diff --git a/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage_Simulate.hash b/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage_Simulate.hash new file mode 100644 index 0000000..55cb1b9 --- /dev/null +++ b/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage_Simulate.hash @@ -0,0 +1 @@ +0f2260db \ No newline at end of file diff --git a/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage_Simulate.json b/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage_Simulate.json new file mode 100644 index 0000000..80fa921 --- /dev/null +++ b/Client/Bundles/StandaloneWindows64/DefaultPackage/Simulate/DefaultPackage_Simulate.json @@ -0,0 +1,185 @@ +{ + "FileVersion": "2025.9.30", + "EnableAddressable": true, + "SupportExtensionless": true, + "LocationToLower": false, + "IncludeAssetGUID": false, + "ReplaceAssetPathWithAddress": false, + "OutputNameStyle": 0, + "BuildBundleType": 1, + "BuildPipeline": "EditorSimulateBuildPipeline", + "PackageName": "DefaultPackage", + "PackageVersion": "Simulate", + "PackageNote": "2026/4/24 20:49:52", + "AssetList": [ + { + "Address": "LocalizationTable", + "AssetPath": "Assets/Bundles/Configs/LocalizationTable.asset", + "AssetGUID": "", + "AssetTags": [ + "WEBGL_PRELOAD", + "Configs" + ], + "BundleID": 0, + "DependBundleIDs": [] + }, + { + "Address": "PoolConfig", + "AssetPath": "Assets/Bundles/Configs/PoolConfig.asset", + "AssetGUID": "", + "AssetTags": [ + "WEBGL_PRELOAD", + "Configs" + ], + "BundleID": 0, + "DependBundleIDs": [] + }, + { + "Address": "Map1000", + "AssetPath": "Assets/Bundles/Scenes/Map1000.unity", + "AssetGUID": "", + "AssetTags": [ + "Scenes" + ], + "BundleID": 1, + "DependBundleIDs": [] + }, + { + "Address": "MyShaderVariants", + "AssetPath": "Assets/Bundles/ShaderVariants/MyShaderVariants.shadervariants", + "AssetGUID": "", + "AssetTags": [ + "ShaderVariants" + ], + "BundleID": 6, + "DependBundleIDs": [] + }, + { + "Address": "UILoadUpdateWindow", + "AssetPath": "Assets/Bundles/UI/UILoadUpdateWindow.prefab", + "AssetGUID": "", + "AssetTags": [ + "UI" + ], + "BundleID": 2, + "DependBundleIDs": [] + }, + { + "Address": "UILogicTestAlert", + "AssetPath": "Assets/Bundles/UI/Window/UILogicTestAlert.prefab", + "AssetGUID": "", + "AssetTags": [ + "UI" + ], + "BundleID": 3, + "DependBundleIDs": [] + }, + { + "Address": "tst", + "AssetPath": "Assets/Bundles/UIRaw/Atlas/Common/bb/tst.png", + "AssetGUID": "", + "AssetTags": [ + "UIRaw" + ], + "BundleID": 4, + "DependBundleIDs": [] + }, + { + "Address": "Brush Impact 5", + "AssetPath": "Assets/Bundles/UIRaw/Atlas/Common/Grunge/Brush Impact 5.png", + "AssetGUID": "", + "AssetTags": [ + "UIRaw" + ], + "BundleID": 5, + "DependBundleIDs": [] + } + ], + "BundleList": [ + { + "BundleName": "assets_bundles_configs.bundle", + "UnityCRC": 0, + "FileHash": "3a378d308429cd99b280d880a27af478", + "FileCRC": 0, + "FileSize": 6942, + "Encrypted": false, + "Tags": [ + "WEBGL_PRELOAD", + "Configs" + ], + "DependBundleIDs": [] + }, + { + "BundleName": "assets_bundles_scenes_map1000.bundle", + "UnityCRC": 0, + "FileHash": "4530edb970dd229fed5dd3259fee4ece", + "FileCRC": 0, + "FileSize": 12277, + "Encrypted": false, + "Tags": [ + "Scenes" + ], + "DependBundleIDs": [] + }, + { + "BundleName": "assets_bundles_ui_uiloadupdatewindow.bundle", + "UnityCRC": 0, + "FileHash": "9ff2ef11fc95cbce9b51368acd1d4383", + "FileCRC": 0, + "FileSize": 59076, + "Encrypted": false, + "Tags": [ + "UI" + ], + "DependBundleIDs": [] + }, + { + "BundleName": "assets_bundles_ui_window_uilogictestalert.bundle", + "UnityCRC": 0, + "FileHash": "cf84038a2a1620e6ff49815bcd8e6a73", + "FileCRC": 0, + "FileSize": 21519, + "Encrypted": false, + "Tags": [ + "UI" + ], + "DependBundleIDs": [] + }, + { + "BundleName": "assets_bundles_uiraw_atlas_common_bb.bundle", + "UnityCRC": 0, + "FileHash": "472a7fd3623bc56802c6e39c904d00a3", + "FileCRC": 0, + "FileSize": 806, + "Encrypted": false, + "Tags": [ + "UIRaw" + ], + "DependBundleIDs": [] + }, + { + "BundleName": "assets_bundles_uiraw_atlas_common_grunge.bundle", + "UnityCRC": 0, + "FileHash": "3dcf5b07fab616778c379807810653ea", + "FileCRC": 0, + "FileSize": 36701, + "Encrypted": false, + "Tags": [ + "UIRaw" + ], + "DependBundleIDs": [] + }, + { + "BundleName": "unityshaders.bundle", + "UnityCRC": 0, + "FileHash": "c9f21eee9f3febcafa65a26802cf31f0", + "FileCRC": 0, + "FileSize": 3518, + "Encrypted": false, + "Tags": [ + "ShaderVariants" + ], + "DependBundleIDs": [] + } + ] +} \ No newline at end of file diff --git a/Client/Packages/com.alicizax.unity.editor.extension b/Client/Packages/com.alicizax.unity.editor.extension index 8d9b4a3..28edc2d 160000 --- a/Client/Packages/com.alicizax.unity.editor.extension +++ b/Client/Packages/com.alicizax.unity.editor.extension @@ -1 +1 @@ -Subproject commit 8d9b4a32ce2274020796f54a9f0465725d0bd84f +Subproject commit 28edc2dfa710217fa3446e7e500b0030d9020263 diff --git a/Client/Packages/com.alicizax.unity.framework b/Client/Packages/com.alicizax.unity.framework index 9d75547..f4f0ea1 160000 --- a/Client/Packages/com.alicizax.unity.framework +++ b/Client/Packages/com.alicizax.unity.framework @@ -1 +1 @@ -Subproject commit 9d755479987795111e7b8721713b70abf98846e5 +Subproject commit f4f0ea1754a33b3e224d04cbcccedacfd41fe278 diff --git a/Client/Packages/com.alicizax.unity.ui.extension b/Client/Packages/com.alicizax.unity.ui.extension index ae16bdc..8c3c136 160000 --- a/Client/Packages/com.alicizax.unity.ui.extension +++ b/Client/Packages/com.alicizax.unity.ui.extension @@ -1 +1 @@ -Subproject commit ae16bdcf37f77929aec1c6178321c333ad9d4d92 +Subproject commit 8c3c13634d25ce3a1808253962e483c9bbbd9f66 diff --git a/Client/UserSettings/EditorUserSettings.asset b/Client/UserSettings/EditorUserSettings.asset index d0c7a7a..d7386d6 100644 --- a/Client/UserSettings/EditorUserSettings.asset +++ b/Client/UserSettings/EditorUserSettings.asset @@ -30,10 +30,10 @@ EditorUserSettings: value: 0606045450065e5d55575a241522064444161c797f7b7234757c4f32e1b0353d flags: 0 RecentlyUsedSceneGuid-7: - value: 5a07065703500c59585e0e7748770d44444f4a737d2d7f35787d4f63e0b26668 + value: 5001560504060c590f5b0f7245725a44404f1d7c297e2233787e4a36b5e4666b flags: 0 RecentlyUsedSceneGuid-8: - value: 5001560504060c590f5b0f7245725a44404f1d7c297e2233787e4a36b5e4666b + value: 5a07065703500c59585e0e7748770d44444f4a737d2d7f35787d4f63e0b26668 flags: 0 RecentlyUsedSceneGuid-9: value: 50500404540c580d0f0b5e7543725b44424f4c7a7b7c7734747e4f36e4b1676d diff --git a/Client/UserSettings/Layouts/CurrentMaximizeLayout.dwlt b/Client/UserSettings/Layouts/CurrentMaximizeLayout.dwlt index 71cb2d7..b6f9353 100644 --- a/Client/UserSettings/Layouts/CurrentMaximizeLayout.dwlt +++ b/Client/UserSettings/Layouts/CurrentMaximizeLayout.dwlt @@ -48,10 +48,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 0 - y: 161 - width: 707 - height: 838 + x: 1920 + y: 295 + width: 1184 + height: 709 m_SerializedDataModeController: m_DataMode: 0 m_PreferredDataMode: 0 @@ -104,23 +104,23 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 21 - width: 707 - height: 817 - m_Scale: {x: 0.36822918, y: 0.36822915} - m_Translation: {x: 353.49997, y: 408.5} + width: 1184 + height: 688 + m_Scale: {x: 0.6166667, y: 0.6166667} + m_Translation: {x: 592, y: 344} m_MarginLeft: 0 m_MarginRight: 0 m_MarginTop: 0 m_MarginBottom: 0 m_LastShownAreaInsideMargins: serializedVersion: 2 - x: -959.9999 - y: -1109.3635 - width: 1919.9999 - height: 2218.727 + x: -960 + y: -557.8378 + width: 1920 + height: 1115.6757 m_MinimalGUI: 1 - m_defaultScale: 0.36822918 - m_LastWindowPixelSize: {x: 707, y: 838} + m_defaultScale: 0.6166667 + m_LastWindowPixelSize: {x: 1184, y: 709} m_ClearInEditMode: 1 m_NoCameraWarning: 1 m_LowResolutionForAspectRatios: 01000000000000000000 @@ -140,12 +140,12 @@ MonoBehaviour: m_EditorClassIdentifier: m_Children: - {fileID: 4} - - {fileID: 7} + - {fileID: 6} m_Position: serializedVersion: 2 x: 0 y: 0 - width: 708 + width: 1185 height: 947 m_MinSize: {x: 100, y: 100} m_MaxSize: {x: 8096, y: 16192} @@ -169,87 +169,16 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 708 - height: 88 - m_MinSize: {x: 201, y: 221} - m_MaxSize: {x: 4001, y: 4021} - m_ActualView: {fileID: 6} + width: 1185 + height: 217 + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_ActualView: {fileID: 5} m_Panes: - {fileID: 5} - - {fileID: 6} - m_Selected: 1 + m_Selected: 0 m_LastSelected: 0 --- !u!114 &5 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 12914, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_MinSize: {x: 100, y: 100} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Animator - m_Image: {fileID: -1673928668082335149, guid: 0000000000000000d000000000000000, type: 0} - m_Tooltip: - m_Pos: - serializedVersion: 2 - x: 0 - y: 79 - width: 365 - height: 915 - m_SerializedDataModeController: - m_DataMode: 0 - m_PreferredDataMode: 0 - m_SupportedDataModes: - isAutomatic: 1 - m_ViewDataDictionary: {fileID: 0} - m_OverlayCanvas: - m_LastAppliedPresetName: Default - m_SaveData: [] - m_OverlaysVisible: 1 - m_ViewTransforms: - m_KeySerializationHelper: - - {fileID: 528180310289601023, guid: 75db14477f59de44b96f358d16e02e73, type: 2} - m_ValueSerializationHelper: - - e00: 1 - e01: 0 - e02: 0 - e03: 0 - e10: 0 - e11: 1 - e12: 0 - e13: 0 - e20: 0 - e21: 0 - e22: 1 - e23: 0 - e30: 0 - e31: 0 - e32: 0 - e33: 1 - m_PreviewAnimator: {fileID: 0} - m_AnimatorController: {fileID: 9100000, guid: 75db14477f59de44b96f358d16e02e73, type: 2} - m_BreadCrumbs: - - m_Target: {fileID: 528180310289601023, guid: 75db14477f59de44b96f358d16e02e73, type: 2} - m_ScrollPosition: {x: 0, y: 0} - stateMachineGraph: {fileID: 0} - stateMachineGraphGUI: {fileID: 0} - blendTreeGraph: {fileID: 0} - blendTreeGraphGUI: {fileID: 0} - m_AutoLiveLink: 1 - m_MiniTool: 0 - m_LockTracker: - m_IsLocked: 0 - m_CurrentEditor: 1 - m_LayerEditor: - m_SelectedLayerIndex: 0 ---- !u!114 &6 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -269,10 +198,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 0 - y: 73 - width: 707 - height: 67 + x: 1920 + y: 78 + width: 1184 + height: 196 m_SerializedDataModeController: m_DataMode: 0 m_PreferredDataMode: 0 @@ -287,7 +216,7 @@ MonoBehaviour: floating: 0 collapsed: 0 displayed: 1 - snapOffset: {x: -174, y: -26} + snapOffset: {x: -179, y: -26} snapOffsetDelta: {x: 0, y: 0} snapCorner: 3 id: Tool Settings @@ -313,9 +242,9 @@ MonoBehaviour: floating: 0 collapsed: 0 displayed: 1 - snapOffset: {x: 0, y: -42} + snapOffset: {x: 0, y: 25} snapOffsetDelta: {x: 0, y: 0} - snapCorner: 2 + snapCorner: 0 id: unity-scene-view-toolbar index: 0 layout: 1 @@ -339,9 +268,9 @@ MonoBehaviour: floating: 0 collapsed: 0 displayed: 1 - snapOffset: {x: 0, y: 41} + snapOffset: {x: 0, y: -51} snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 + snapCorner: 2 id: unity-transform-toolbar index: 0 layout: 2 @@ -365,9 +294,9 @@ MonoBehaviour: floating: 0 collapsed: 0 displayed: 1 - snapOffset: {x: 24, y: 0} + snapOffset: {x: -111, y: 52} snapOffsetDelta: {x: 0, y: 0} - snapCorner: 0 + snapCorner: 1 id: Orientation index: 0 layout: 4 @@ -813,9 +742,9 @@ MonoBehaviour: m_PlayAudio: 0 m_AudioPlay: 0 m_Position: - m_Target: {x: 1061.5972, y: 683.41235, z: 6.893609} + m_Target: {x: 999.7848, y: 1000.034, z: 302.38394} speed: 2 - m_Value: {x: 1061.5972, y: 683.41235, z: 6.893609} + m_Value: {x: 999.7848, y: 1000.034, z: 302.38394} m_RenderMode: 0 m_CameraMode: drawMode: 0 @@ -865,9 +794,9 @@ MonoBehaviour: speed: 2 m_Value: {x: 0, y: 0, z: 0, w: 1} m_Size: - m_Target: 412.0922 + m_Target: 1.5241313 speed: 2 - m_Value: 412.0922 + m_Value: 1.5241313 m_Ortho: m_Target: 1 speed: 2 @@ -892,7 +821,7 @@ MonoBehaviour: m_SceneVisActive: 1 m_LastLockedObject: {fileID: 0} m_ViewIsLockedToObject: 0 ---- !u!114 &7 +--- !u!114 &6 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -908,16 +837,87 @@ MonoBehaviour: m_Position: serializedVersion: 2 x: 0 - y: 88 - width: 708 - height: 859 - m_MinSize: {x: 51, y: 71} - m_MaxSize: {x: 4001, y: 4021} + y: 217 + width: 1185 + height: 730 + m_MinSize: {x: 50, y: 50} + m_MaxSize: {x: 4000, y: 4000} m_ActualView: {fileID: 2} m_Panes: - {fileID: 2} + - {fileID: 7} m_Selected: 0 - m_LastSelected: 0 + m_LastSelected: 1 +--- !u!114 &7 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12914, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 100, y: 100} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Animator + m_Image: {fileID: -1673928668082335149, guid: 0000000000000000d000000000000000, type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 0 + y: 639 + width: 491 + height: 360 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_ViewTransforms: + m_KeySerializationHelper: + - {fileID: 528180310289601023, guid: 75db14477f59de44b96f358d16e02e73, type: 2} + m_ValueSerializationHelper: + - e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_PreviewAnimator: {fileID: 0} + m_AnimatorController: {fileID: 9100000, guid: 75db14477f59de44b96f358d16e02e73, type: 2} + m_BreadCrumbs: + - m_Target: {fileID: 528180310289601023, guid: 75db14477f59de44b96f358d16e02e73, type: 2} + m_ScrollPosition: {x: 0, y: 0} + stateMachineGraph: {fileID: 0} + stateMachineGraphGUI: {fileID: 0} + blendTreeGraph: {fileID: 0} + blendTreeGraphGUI: {fileID: 0} + m_AutoLiveLink: 1 + m_MiniTool: 0 + m_LockTracker: + m_IsLocked: 0 + m_CurrentEditor: 1 + m_LayerEditor: + m_SelectedLayerIndex: 0 --- !u!114 &8 MonoBehaviour: m_ObjectHideFlags: 52 @@ -935,14 +935,14 @@ MonoBehaviour: - {fileID: 11} m_Position: serializedVersion: 2 - x: 708 + x: 1185 y: 0 - width: 493 + width: 320 height: 947 m_MinSize: {x: 100, y: 100} m_MaxSize: {x: 8096, y: 16192} vertical: 1 - controlID: 1814 + controlID: 3247 draggingID: 0 --- !u!114 &9 MonoBehaviour: @@ -961,10 +961,10 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 493 - height: 461 - m_MinSize: {x: 202, y: 221} - m_MaxSize: {x: 4002, y: 4021} + width: 320 + height: 415 + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} m_ActualView: {fileID: 10} m_Panes: - {fileID: 10} @@ -990,10 +990,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 708 - y: 73 - width: 491 - height: 440 + x: 3105 + y: 78 + width: 318 + height: 394 m_SerializedDataModeController: m_DataMode: 0 m_PreferredDataMode: 0 @@ -1007,9 +1007,9 @@ MonoBehaviour: m_SceneHierarchy: m_TreeViewState: scrollPos: {x: 0, y: 0} - m_SelectedIDs: ac6e0000 - m_LastClickedID: 28332 - m_ExpandedIDs: 18fbffff28fbffffb46d0000ac6e0000 + m_SelectedIDs: + m_LastClickedID: 0 + m_ExpandedIDs: 02fbffff04fbfffff4ffffff m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -1049,11 +1049,11 @@ MonoBehaviour: m_Position: serializedVersion: 2 x: 0 - y: 461 - width: 493 - height: 486 - m_MinSize: {x: 102, y: 121} - m_MaxSize: {x: 4002, y: 4021} + y: 415 + width: 320 + height: 532 + m_MinSize: {x: 100, y: 100} + m_MaxSize: {x: 4000, y: 4000} m_ActualView: {fileID: 12} m_Panes: - {fileID: 12} @@ -1079,10 +1079,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 708 - y: 534 - width: 491 - height: 465 + x: 3105 + y: 493 + width: 318 + height: 511 m_SerializedDataModeController: m_DataMode: 0 m_PreferredDataMode: 0 @@ -1108,9 +1108,9 @@ MonoBehaviour: m_Children: [] m_Position: serializedVersion: 2 - x: 1201 + x: 1505 y: 0 - width: 100 + width: 164 height: 947 m_MinSize: {x: 232, y: 271} m_MaxSize: {x: 10002, y: 10021} @@ -1139,9 +1139,9 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 1202 - y: 19 - width: 98 + x: 3425 + y: 78 + width: 162 height: 926 m_SerializedDataModeController: m_DataMode: 0 @@ -1163,8 +1163,7 @@ MonoBehaviour: m_ShowAllHits: 0 m_SkipHidden: 0 m_SearchArea: 1 - m_Folders: - - Assets/Resources + m_Folders: [] m_Globs: [] m_OriginalText: m_ImportLogFlags: 0 @@ -1180,7 +1179,7 @@ MonoBehaviour: scrollPos: {x: 0, y: 0} m_SelectedIDs: e48c0000 m_LastClickedID: 36068 - m_ExpandedIDs: ffffffff00000000da720000dc720000de720000e0720000e2720000e4720000e6720000e8720000ea720000ec720000ee720000f0720000f2720000f4720000f6720000f8720000fa720000fc720000fe72000000730000027300000473000006730000087300000a7300000c7300000e73000010730000127300001473000016730000187300001a7300001c7300001e73000020730000227300002473000026730000287300002a7300002c7300002e73000030730000327300003473000036730000387300003a7300003c7300003e73000040730000427300004473000046730000487300004a7300004c7300004e73000050730000527300005473000056730000587300005a7300005c7300005e73000060730000627300006473000066730000687300006a7300006c7300006e73000070730000727300007473000076730000787300007a7300007c7300007e73000080730000827300008473000086730000887300008a7300008c7300008e73000090730000927300009473000096730000987300009a7300009c7300009e730000a0730000a2730000a4730000a6730000a8730000aa730000ac730000ae730000b0730000b2730000b4730000b6730000b8730000ba730000bc730000be730000c0730000c2730000c4730000c6730000c8730000ca730000cc730000ce730000d0730000d2730000d4730000d6730000d8730000da730000dc730000de730000e0730000e2730000e4730000e6730000e8730000ea730000ec730000ee730000f0730000f2730000f4730000f6730000f8730000fa730000fc730000fe73000000740000027400000474000006740000087400000a7400000c7400000e74000010740000127400001474000016740000187400001a7400001c7400001e74000020740000227400002474000026740000287400002a7400002c7400002e74000030740000327400003474000036740000387400003a7400003c7400003e74000040740000427400004474000046740000487400004a7400004c7400004e74000050740000527400005474000056740000587400005a7400005c7400005e74000060740000627400006474000066740000687400006a7400006c7400006e74000070740000727400007474000076740000787400007a7400007c7400007e74000080740000827400008474000086740000887400008a7400008c7400008e74000090740000927400009474000096740000987400009a7400009c7400009e740000a0740000 + m_ExpandedIDs: ffffffff00000000020d0000a26a0000a46a0000a66a0000a86a0000aa6a0000ac6a0000ae6a0000b06a0000b26a0000b46a0000b66a0000b86a0000ba6a0000bc6a0000be6a0000c06a0000c26a0000c46a0000c66a0000c86a0000ca6a0000cc6a0000ce6a0000d06a0000d26a0000d46a0000d66a0000d86a0000da6a0000dc6a0000de6a0000e06a0000e26a0000e46a0000e66a0000e86a0000ea6a0000ec6a0000ee6a0000f06a0000f26a0000f46a0000f66a0000f86a0000fa6a0000fc6a0000fe6a0000006b0000026b0000046b0000066b0000c46c0000106d0000ffffff7f m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -1205,10 +1204,10 @@ MonoBehaviour: m_Icon: {fileID: 0} m_ResourceFile: m_AssetTreeState: - scrollPos: {x: 0, y: 0} - m_SelectedIDs: ac6e0000 + scrollPos: {x: 0, y: 1712} + m_SelectedIDs: m_LastClickedID: 0 - m_ExpandedIDs: ffffffff00000000da720000dc720000de720000e0720000e2720000e4720000e6720000e8720000ea720000ec720000ee720000f0720000f2720000f4720000f6720000f8720000fa720000fc720000fe72000000730000027300000473000006730000087300000a7300000c7300000e73000010730000127300001473000016730000187300001a7300001c7300001e73000020730000227300002473000026730000287300002a7300002c7300002e73000030730000327300003473000036730000387300003a7300003c7300003e73000040730000427300004473000046730000487300004a7300004c7300004e73000050730000527300005473000056730000587300005a7300005c7300005e73000060730000627300006473000066730000687300006a7300006c7300006e73000070730000727300007473000076730000787300007a7300007c7300007e73000080730000827300008473000086730000887300008a7300008c7300008e73000090730000927300009473000096730000987300009a7300009c7300009e730000a0730000a2730000a4730000a6730000a8730000aa730000ac730000ae730000b0730000b2730000b4730000b6730000b8730000ba730000bc730000be730000c0730000c2730000c4730000c6730000c8730000ca730000cc730000ce730000d0730000d2730000d4730000d6730000d8730000da730000dc730000de730000e0730000e2730000e4730000e6730000e8730000ea730000ec730000ee730000f0730000f2730000f4730000f6730000f8730000fa730000fc730000fe73000000740000027400000474000006740000087400000a7400000c7400000e74000010740000127400001474000016740000187400001a7400001c7400001e74000020740000227400002474000026740000287400002a7400002c7400002e74000030740000327400003474000036740000387400003a7400003c7400003e74000040740000427400004474000046740000487400004a7400004c7400004e74000050740000527400005474000056740000587400005a7400005c7400005e74000060740000627400006474000066740000687400006a7400006c7400006e74000070740000727400007474000076740000787400007a7400007c7400007e74000080740000827400008474000086740000887400008a7400008c7400008e74000090740000927400009474000096740000987400009a7400009c7400009e740000a0740000 + m_ExpandedIDs: ffffffff00000000020d0000a26a0000a46a0000a66a0000a86a0000aa6a0000ac6a0000ae6a0000b06a0000b26a0000b46a0000b66a0000b86a0000ba6a0000bc6a0000be6a0000c06a0000c26a0000c46a0000c66a0000c86a0000ca6a0000cc6a0000ce6a0000d06a0000d26a0000d46a0000d66a0000d86a0000da6a0000dc6a0000de6a0000e06a0000e26a0000e46a0000e66a0000e86a0000ea6a0000ec6a0000ee6a0000f06a0000f26a0000f46a0000f66a0000f86a0000fa6a0000fc6a0000fe6a0000006b0000026b0000046b0000066b0000c46c0000106d0000ffffff7f m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -1233,8 +1232,8 @@ MonoBehaviour: m_Icon: {fileID: 0} m_ResourceFile: m_ListAreaState: - m_SelectedInstanceIDs: ac6e0000 - m_LastClickedInstanceID: 28332 + m_SelectedInstanceIDs: + m_LastClickedInstanceID: 0 m_HadKeyboardFocusLastEvent: 0 m_ExpandedInstanceIDs: 0c750000f2d60000 m_RenameOverlay: @@ -1279,12 +1278,12 @@ MonoBehaviour: m_Children: [] m_Position: serializedVersion: 2 - x: 1301 + x: 1669 y: 0 - width: 619 + width: 251 height: 947 - m_MinSize: {x: 276, y: 71} - m_MaxSize: {x: 4001, y: 4021} + m_MinSize: {x: 275, y: 50} + m_MaxSize: {x: 4000, y: 4000} m_ActualView: {fileID: 16} m_Panes: - {fileID: 16} @@ -1310,9 +1309,9 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 1302 - y: 19 - width: 618 + x: 3589 + y: 78 + width: 250 height: 926 m_SerializedDataModeController: m_DataMode: 0 @@ -1327,7 +1326,7 @@ MonoBehaviour: m_ObjectsLockedBeforeSerialization: [] m_InstanceIDsLockedBeforeSerialization: m_PreviewResizer: - m_CachedPref: 219 + m_CachedPref: 151 m_ControlHash: 1412526313 m_PrefName: Preview_InspectorPreview m_LastInspectedObjectInstanceID: -1 diff --git a/Client/UserSettings/Layouts/default-2022.dwlt b/Client/UserSettings/Layouts/default-2022.dwlt index 8444e16..d314ece 100644 --- a/Client/UserSettings/Layouts/default-2022.dwlt +++ b/Client/UserSettings/Layouts/default-2022.dwlt @@ -14,70 +14,17 @@ MonoBehaviour: m_EditorClassIdentifier: m_PixelRect: serializedVersion: 2 - x: 0 - y: 43 + x: 1920 + y: 38 width: 1920 height: 997 m_ShowMode: 4 m_Title: Hierarchy - m_RootView: {fileID: 4} + m_RootView: {fileID: 2} m_MinSize: {x: 875, y: 300} m_MaxSize: {x: 10000, y: 10000} m_Maximized: 1 --- !u!114 &2 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: GameView - m_EditorClassIdentifier: - m_Children: [] - m_Position: - serializedVersion: 2 - x: 0 - y: 549 - width: 519 - height: 398 - m_MinSize: {x: 50, y: 50} - m_MaxSize: {x: 4000, y: 4000} - m_ActualView: {fileID: 14} - m_Panes: - - {fileID: 14} - - {fileID: 15} - m_Selected: 0 - m_LastSelected: 1 ---- !u!114 &3 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_Children: - - {fileID: 8} - - {fileID: 2} - m_Position: - serializedVersion: 2 - x: 0 - y: 0 - width: 519 - height: 947 - m_MinSize: {x: 100, y: 100} - m_MaxSize: {x: 8096, y: 16192} - vertical: 1 - controlID: 17 - draggingID: 0 ---- !u!114 &4 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -90,9 +37,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Children: + - {fileID: 3} - {fileID: 5} - - {fileID: 7} - - {fileID: 6} + - {fileID: 4} m_Position: serializedVersion: 2 x: 0 @@ -105,7 +52,7 @@ MonoBehaviour: m_TopViewHeight: 30 m_UseBottomView: 1 m_BottomViewHeight: 20 ---- !u!114 &5 +--- !u!114 &3 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -127,7 +74,7 @@ MonoBehaviour: m_MinSize: {x: 0, y: 0} m_MaxSize: {x: 0, y: 0} m_LastLoadedLayoutName: ---- !u!114 &6 +--- !u!114 &4 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -148,7 +95,7 @@ MonoBehaviour: height: 20 m_MinSize: {x: 0, y: 0} m_MaxSize: {x: 0, y: 0} ---- !u!114 &7 +--- !u!114 &5 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -161,7 +108,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Children: - - {fileID: 3} + - {fileID: 6} - {fileID: 9} - {fileID: 12} - {fileID: 13} @@ -174,9 +121,35 @@ MonoBehaviour: m_MinSize: {x: 400, y: 100} m_MaxSize: {x: 32384, y: 16192} vertical: 0 - controlID: 48 + controlID: 157 draggingID: 0 ---- !u!114 &8 +--- !u!114 &6 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 7} + - {fileID: 8} + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 1016 + height: 947 + m_MinSize: {x: 100, y: 100} + m_MaxSize: {x: 8096, y: 16192} + vertical: 1 + controlID: 18 + draggingID: 0 +--- !u!114 &7 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -193,15 +166,42 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 519 - height: 549 + width: 1016 + height: 71 m_MinSize: {x: 201, y: 221} m_MaxSize: {x: 4001, y: 4021} - m_ActualView: {fileID: 16} + m_ActualView: {fileID: 15} m_Panes: - - {fileID: 16} + - {fileID: 15} m_Selected: 0 m_LastSelected: 0 +--- !u!114 &8 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: GameView + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 71 + width: 1016 + height: 876 + m_MinSize: {x: 51, y: 71} + m_MaxSize: {x: 4001, y: 4021} + m_ActualView: {fileID: 14} + m_Panes: + - {fileID: 14} + - {fileID: 16} + m_Selected: 0 + m_LastSelected: 1 --- !u!114 &9 MonoBehaviour: m_ObjectHideFlags: 52 @@ -219,14 +219,14 @@ MonoBehaviour: - {fileID: 11} m_Position: serializedVersion: 2 - x: 519 + x: 1016 y: 0 - width: 373 + width: 364 height: 947 m_MinSize: {x: 100, y: 100} m_MaxSize: {x: 8096, y: 16192} vertical: 1 - controlID: 49 + controlID: 56 draggingID: 0 --- !u!114 &10 MonoBehaviour: @@ -245,8 +245,8 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 373 - height: 415 + width: 364 + height: 398 m_MinSize: {x: 202, y: 221} m_MaxSize: {x: 4002, y: 4021} m_ActualView: {fileID: 17} @@ -270,9 +270,9 @@ MonoBehaviour: m_Position: serializedVersion: 2 x: 0 - y: 415 - width: 373 - height: 532 + y: 398 + width: 364 + height: 549 m_MinSize: {x: 102, y: 121} m_MaxSize: {x: 4002, y: 4021} m_ActualView: {fileID: 18} @@ -295,9 +295,9 @@ MonoBehaviour: m_Children: [] m_Position: serializedVersion: 2 - x: 892 + x: 1380 y: 0 - width: 407 + width: 100 height: 947 m_MinSize: {x: 232, y: 271} m_MaxSize: {x: 10002, y: 10021} @@ -321,9 +321,9 @@ MonoBehaviour: m_Children: [] m_Position: serializedVersion: 2 - x: 1299 + x: 1480 y: 0 - width: 621 + width: 440 height: 947 m_MinSize: {x: 276, y: 71} m_MaxSize: {x: 4001, y: 4021} @@ -352,10 +352,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 0 - y: 622 - width: 518 - height: 377 + x: 1920 + y: 139 + width: 1015 + height: 855 m_SerializedDataModeController: m_DataMode: 0 m_PreferredDataMode: 0 @@ -408,99 +408,29 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 21 - width: 518 - height: 356 - m_Scale: {x: 0.26979166, y: 0.26979166} - m_Translation: {x: 259, y: 178} + width: 1015 + height: 834 + m_Scale: {x: 0.5286458, y: 0.5286458} + m_Translation: {x: 507.5, y: 417} m_MarginLeft: 0 m_MarginRight: 0 m_MarginTop: 0 m_MarginBottom: 0 m_LastShownAreaInsideMargins: serializedVersion: 2 - x: -960 - y: -659.7684 - width: 1920 - height: 1319.5367 + x: -960.00006 + y: -788.8079 + width: 1920.0001 + height: 1577.6158 m_MinimalGUI: 1 - m_defaultScale: 0.26979166 - m_LastWindowPixelSize: {x: 518, y: 377} + m_defaultScale: 0.5286458 + m_LastWindowPixelSize: {x: 1015, y: 855} m_ClearInEditMode: 1 m_NoCameraWarning: 1 m_LowResolutionForAspectRatios: 01000000000000000000 m_XRRenderMode: 0 m_RenderTexture: {fileID: 0} --- !u!114 &15 -MonoBehaviour: - m_ObjectHideFlags: 52 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 12914, guid: 0000000000000000e000000000000000, type: 0} - m_Name: - m_EditorClassIdentifier: - m_MinSize: {x: 100, y: 100} - m_MaxSize: {x: 4000, y: 4000} - m_TitleContent: - m_Text: Animator - m_Image: {fileID: -1673928668082335149, guid: 0000000000000000d000000000000000, type: 0} - m_Tooltip: - m_Pos: - serializedVersion: 2 - x: 0 - y: 639 - width: 491 - height: 360 - m_SerializedDataModeController: - m_DataMode: 0 - m_PreferredDataMode: 0 - m_SupportedDataModes: - isAutomatic: 1 - m_ViewDataDictionary: {fileID: 0} - m_OverlayCanvas: - m_LastAppliedPresetName: Default - m_SaveData: [] - m_OverlaysVisible: 1 - m_ViewTransforms: - m_KeySerializationHelper: - - {fileID: 528180310289601023, guid: 75db14477f59de44b96f358d16e02e73, type: 2} - m_ValueSerializationHelper: - - e00: 1 - e01: 0 - e02: 0 - e03: 0 - e10: 0 - e11: 1 - e12: 0 - e13: 0 - e20: 0 - e21: 0 - e22: 1 - e23: 0 - e30: 0 - e31: 0 - e32: 0 - e33: 1 - m_PreviewAnimator: {fileID: 0} - m_AnimatorController: {fileID: 9100000, guid: 75db14477f59de44b96f358d16e02e73, type: 2} - m_BreadCrumbs: - - m_Target: {fileID: 528180310289601023, guid: 75db14477f59de44b96f358d16e02e73, type: 2} - m_ScrollPosition: {x: 0, y: 0} - stateMachineGraph: {fileID: 0} - stateMachineGraphGUI: {fileID: 0} - blendTreeGraph: {fileID: 0} - blendTreeGraphGUI: {fileID: 0} - m_AutoLiveLink: 1 - m_MiniTool: 0 - m_LockTracker: - m_IsLocked: 0 - m_CurrentEditor: 1 - m_LayerEditor: - m_SelectedLayerIndex: 0 ---- !u!114 &16 MonoBehaviour: m_ObjectHideFlags: 52 m_CorrespondingSourceObject: {fileID: 0} @@ -520,10 +450,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 0 - y: 73 - width: 518 - height: 528 + x: 1920 + y: 68 + width: 1015 + height: 50 m_SerializedDataModeController: m_DataMode: 0 m_PreferredDataMode: 0 @@ -552,7 +482,7 @@ MonoBehaviour: collapsed: 0 displayed: 1 snapOffset: {x: -179, y: 25} - snapOffsetDelta: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: -1} snapCorner: 1 id: unity-grid-and-snap-toolbar index: 1 @@ -564,7 +494,7 @@ MonoBehaviour: floating: 0 collapsed: 0 displayed: 1 - snapOffset: {x: 0, y: 25} + snapOffset: {x: 0, y: 24} snapOffsetDelta: {x: 0, y: 0} snapCorner: 0 id: unity-scene-view-toolbar @@ -591,7 +521,7 @@ MonoBehaviour: collapsed: 0 displayed: 1 snapOffset: {x: 0, y: -51} - snapOffsetDelta: {x: 0, y: 0} + snapOffsetDelta: {x: 0, y: 1} snapCorner: 2 id: unity-transform-toolbar index: 0 @@ -616,8 +546,8 @@ MonoBehaviour: floating: 0 collapsed: 0 displayed: 1 - snapOffset: {x: -111, y: -106} - snapOffsetDelta: {x: 0, y: 0} + snapOffset: {x: -111, y: -144} + snapOffsetDelta: {x: 0, y: 94} snapCorner: 3 id: Orientation index: 0 @@ -1059,14 +989,14 @@ MonoBehaviour: m_OverrideSceneCullingMask: 6917529027641081856 m_SceneIsLit: 0 m_SceneLighting: 1 - m_2DMode: 1 + m_2DMode: 0 m_isRotationLocked: 0 m_PlayAudio: 0 m_AudioPlay: 0 m_Position: - m_Target: {x: 1003.16583, y: 999.6796, z: 155.55556} + m_Target: {x: -16.97024, y: 9.175228, z: 6.588712} speed: 2 - m_Value: {x: 1003.16583, y: 999.6796, z: 155.55556} + m_Value: {x: -16.97024, y: 9.175228, z: 6.588712} m_RenderMode: 0 m_CameraMode: drawMode: 0 @@ -1094,17 +1024,17 @@ MonoBehaviour: m_Size: {x: 0, y: 0} yGrid: m_Fade: - m_Target: 0 + m_Target: 1 speed: 2 - m_Value: 0 + m_Value: 1 m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} m_Pivot: {x: 0, y: 0, z: 0} m_Size: {x: 1, y: 1} zGrid: m_Fade: - m_Target: 1 + m_Target: 0 speed: 2 - m_Value: 1 + m_Value: 0 m_Color: {r: 0.5, g: 0.5, b: 0.5, a: 0.4} m_Pivot: {x: 0, y: 0, z: 0} m_Size: {x: 1, y: 1} @@ -1112,17 +1042,17 @@ MonoBehaviour: m_GridAxis: 1 m_gridOpacity: 0.5 m_Rotation: - m_Target: {x: 0, y: 0, z: 0, w: 1} + m_Target: {x: -0.11765223, y: -0.82547, z: 0.14422774, w: -0.53288794} speed: 2 - m_Value: {x: 0, y: 0, z: 0, w: 1} + m_Value: {x: -0.11765142, y: -0.82546425, z: 0.14422674, w: -0.53288424} m_Size: - m_Target: 4.8635325 + m_Target: 1.0792253 speed: 2 - m_Value: 4.8635325 + m_Value: 1.0792253 m_Ortho: - m_Target: 1 + m_Target: 0 speed: 2 - m_Value: 1 + m_Value: 0 m_CameraSettings: m_Speed: 2 m_SpeedNormalized: 1 @@ -1143,6 +1073,76 @@ MonoBehaviour: m_SceneVisActive: 1 m_LastLockedObject: {fileID: 0} m_ViewIsLockedToObject: 0 +--- !u!114 &16 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 12914, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_MinSize: {x: 100, y: 100} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Animator + m_Image: {fileID: -1673928668082335149, guid: 0000000000000000d000000000000000, type: 0} + m_Tooltip: + m_Pos: + serializedVersion: 2 + x: 1920 + y: 285 + width: 793 + height: 709 + m_SerializedDataModeController: + m_DataMode: 0 + m_PreferredDataMode: 0 + m_SupportedDataModes: + isAutomatic: 1 + m_ViewDataDictionary: {fileID: 0} + m_OverlayCanvas: + m_LastAppliedPresetName: Default + m_SaveData: [] + m_OverlaysVisible: 1 + m_ViewTransforms: + m_KeySerializationHelper: + - {fileID: 528180310289601023, guid: 75db14477f59de44b96f358d16e02e73, type: 2} + m_ValueSerializationHelper: + - e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_PreviewAnimator: {fileID: 0} + m_AnimatorController: {fileID: 9100000, guid: 75db14477f59de44b96f358d16e02e73, type: 2} + m_BreadCrumbs: + - m_Target: {fileID: 528180310289601023, guid: 75db14477f59de44b96f358d16e02e73, type: 2} + m_ScrollPosition: {x: 0, y: 0} + stateMachineGraph: {fileID: 0} + stateMachineGraphGUI: {fileID: 0} + blendTreeGraph: {fileID: 0} + blendTreeGraphGUI: {fileID: 0} + m_AutoLiveLink: 1 + m_MiniTool: 0 + m_LockTracker: + m_IsLocked: 0 + m_CurrentEditor: 1 + m_LayerEditor: + m_SelectedLayerIndex: 0 --- !u!114 &17 MonoBehaviour: m_ObjectHideFlags: 52 @@ -1163,10 +1163,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: -31481 - y: -31970 - width: 371 - height: 394 + x: 2936 + y: 68 + width: 362 + height: 377 m_SerializedDataModeController: m_DataMode: 0 m_PreferredDataMode: 0 @@ -1182,7 +1182,7 @@ MonoBehaviour: scrollPos: {x: 0, y: 0} m_SelectedIDs: m_LastClickedID: 0 - m_ExpandedIDs: 9e34ffff + m_ExpandedIDs: 10a8ffff12a8ffffb6a8ffffeafaffffecfaffff m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -1226,10 +1226,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 519 - y: 488 - width: 371 - height: 511 + x: 2936 + y: 466 + width: 362 + height: 528 m_SerializedDataModeController: m_DataMode: 0 m_PreferredDataMode: 0 @@ -1260,9 +1260,9 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 892 - y: 73 - width: 405 + x: 3300 + y: 68 + width: 98 height: 926 m_SerializedDataModeController: m_DataMode: 0 @@ -1283,9 +1283,8 @@ MonoBehaviour: m_SceneHandles: m_ShowAllHits: 0 m_SkipHidden: 0 - m_SearchArea: 2 - m_Folders: - - Assets/Bundles/UI + m_SearchArea: 1 + m_Folders: [] m_Globs: [] m_OriginalText: m_ImportLogFlags: 0 @@ -1301,7 +1300,7 @@ MonoBehaviour: scrollPos: {x: 0, y: 0} m_SelectedIDs: e48c0000 m_LastClickedID: 36068 - m_ExpandedIDs: + m_ExpandedIDs: 00000000f80c0000aa6a0000ac6a0000ae6a0000b06a0000b26a0000b46a0000b66a0000b86a0000ba6a0000bc6a0000be6a0000c06a0000c26a0000c46a0000c66a0000c86a0000ca6a0000cc6a0000ce6a0000d06a0000d26a0000d46a0000d66a0000d86a0000da6a0000dc6a0000de6a0000e06a0000e26a0000e46a0000e66a0000e86a0000ea6a0000ec6a0000ee6a0000f06a0000f26a0000f46a0000f66a0000f86a0000fa6a0000fc6a0000fe6a0000006b0000026b0000046b0000066b0000086b00000a6b00000c6b00000e6b0000 m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -1326,24 +1325,24 @@ MonoBehaviour: m_Icon: {fileID: 0} m_ResourceFile: m_AssetTreeState: - scrollPos: {x: 0, y: 60} + scrollPos: {x: 0, y: 0} m_SelectedIDs: m_LastClickedID: 0 - m_ExpandedIDs: ffffffff0a0600006e060000920600009a070000b2070000560900009a0e0000ffffff7f + m_ExpandedIDs: ffffffff00000000f80c0000aa6a0000ac6a0000ae6a0000b06a0000b26a0000b46a0000b66a0000b86a0000ba6a0000bc6a0000be6a0000c06a0000c26a0000c46a0000c66a0000c86a0000ca6a0000cc6a0000ce6a0000d06a0000d26a0000d46a0000d66a0000d86a0000da6a0000dc6a0000de6a0000e06a0000e26a0000e46a0000e66a0000e86a0000ea6a0000ec6a0000ee6a0000f06a0000f26a0000f46a0000f66a0000f86a0000fa6a0000fc6a0000fe6a0000006b0000026b0000046b0000066b0000086b00000a6b00000c6b00000e6b0000cc6c0000 m_RenameOverlay: m_UserAcceptedRename: 0 - m_Name: UILoadUpdateWindow - m_OriginalName: UILoadUpdateWindow + m_Name: + m_OriginalName: m_EditFieldRect: serializedVersion: 2 x: 0 y: 0 width: 0 height: 0 - m_UserData: 34468 + 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 +1404,9 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 1299 - y: 73 - width: 620 + x: 3400 + y: 68 + width: 439 height: 926 m_SerializedDataModeController: m_DataMode: 0