[docs][refactor]: 将旧 01~06 中文教程合并整理为 modules/frameworks 文件夹结构

- 删除 01~05 旧中文教程目录
- 将 03 核心功能教程拆分为各 framework 模块的子文档
- 06 工具脚本移入 modules/frameworks/编辑器工具/UI工具/
- 非框架内容合并进 rules/20-开发规则.md 与 rules/40-工作流清单.md
- 更新所有交叉链接与文档关联表

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lms
2026-07-07 18:36:15 +08:00
parent 5e5b9f8c80
commit ee1ff7f444
77 changed files with 6263 additions and 1133 deletions

View File

@@ -0,0 +1,95 @@
# Hotfix 启动
## 分类
- 核心框架
## 目的
记录 hotfix 侧根入口和功能目录聚合,梳理游戏启动链路。
---
## 核心路径
- `Assets/Game/GameHFScripte/GameFunction/GameRoot/`
- 关键文件:`HFGameStart.cs``HFFunCatalogue_GameManager.cs`
- 程序集:`GameHF`
---
## 启动调用链
```text
HFGameStart.Init(GameSetting)
-> HFFunCatalogue.SetGameSetting
-> HFFunCatalogue.ChangeLanguage(enHFLanguageClassify.EN)
-> HFGameStartAssetsCheckManager.CheckFirstDownLoadWindowAsset
-> HFGameStart.LoadDB
-> Hotfix_ZYXlsx_GoogleProtobuf_Helper.LoadAllXlsx
-> HFFunCatalogue.Scene.LoadScene(enHFSceneName.Game)
-> HFGameStart.InitManager
-> HFFunCatalogue.UIManager.OpenWindow<登录窗口>
```
---
## InitManager 初始化单例
`HFGameStart.InitManager()` 负责初始化各业务/框架管理器,例如:
- `HFSeverManager`
- `HFMapManager`
- `HFDamageManager`
- `HFPopupTextManager`
- 各业务管理器(如背包、登录等,按项目实际)
---
## HFFunCatalogue 聚合入口
`HFFunCatalogue` 提供统一访问入口:
- `UIManager` — UI 窗口
- `Asset` — 资源加载
- `Scene` — 场景加载
- `Sprite` — 图集/精灵
- `EntityPool` — 实体池
- `LinePool` / `SoundPool` — 其他对象池
- `Event` — 事件系统
- `GameConstant` — 游戏常量
- `NDB` — 配置表读取
- `Bag` / `Tips` — 业务相关入口(按项目实际)
---
## 使用规则
- 业务代码通过 `HFFunCatalogue` 访问核心库,禁止直接实例化核心库内部类。
- `HFGameStart` 是 hotfix 侧启动入口,不要绕过它直接初始化管理器。
- 启动顺序修改需谨慎,确保配置表、资源、场景按正确顺序加载。
---
## 相关模块
- [UI 窗口系统](UI窗口系统/README.md)
- [资源管理](资源管理/README.md)
- [场景系统](场景系统.md)
- [数据/配置框架](数据配置框架.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/UI窗口系统/README.md](UI窗口系统/README.md) | 启动后打开登录窗口依赖 UI 框架 |
| [modules/frameworks/资源管理/README.md](资源管理/README.md) | 启动流程涉及资源检查 |
| [modules/frameworks/场景系统.md](场景系统.md) | 启动流程涉及场景加载 |
| [modules/frameworks/数据配置框架.md](数据配置框架.md) | 启动流程涉及配置表加载 |
| [rules/10-架构说明.md](../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,129 @@
# UI 框架
## 分类
- 项目框架
## 目的
记录 UIWindow 管理、窗口基类、UIMono 桥接、窗口资源加载和 UI 生命周期。
## 已知线索
- 管理路径:`Assets/Game/GameHFScripte/CopyToHF/UIWindowMgr/`
- 窗口实现:`Assets/Game/GameHFScripte/GameFunction/UIWindows/`
- Mono 桥接:`Assets/Game/GameMono/Scripts/UIMono/`
- 资源路径:`Assets/Game/GameHFResource/CN/UIWindows/`
## 已确认链路
UI manager 初始化:
```text
HFUIWindowMgr.OnAfterCreateInstance()
-> OnInitializeCanvasAndCamera()
-> 为 enHFUICanvasLayer 创建 Canvas
-> 为 ScreenSpaceCamera Canvas 创建 UI Camera
-> 创建 CacheSiblingIndex Canvas
-> 创建或接管 EventSystem / StandaloneInputModule
```
打开窗口:
```text
HFFunCatalogue.UIManager.OpenWindow<T>()
-> HFUIWindowMgr.OnOpenWindow<HFUIWindowSetting>()
-> 创建 HFDefaultTask / HFUIWindow_RespondTask
-> 读取窗口类型上的 HFAssetBundlePathAttribute 和 HFUIWindowLayerAttribute
-> OnSettingWindowSiblingIndex()
-> 从缓存队列复用窗口实例,或 Activator.CreateInstance 创建窗口逻辑实例
-> OnPreloadWindowInMemory()
-> HFABMgr.Instance.LoadInMemory(windowPrefabPath)
-> win.BindGameObject(prefab)
-> OnAttachWindow(win)
-> win.SyncWindowState()
-> AbsHFUIWindow.OnOpen()
```
关闭窗口:
```text
CloseWindow<T>() / AbsHFUIWindow.CloseWindow()
-> HFUIWindowMgr.OnCloseWindow()
-> siblingIndexLayer.ToggleWindowActive(false)
-> 放入 mCacheWindowInstanceMaping
-> 处理 backtrack 自动恢复窗口
-> win.SyncWindowState()
-> AbsHFUIWindow.OnClose()
```
窗口资源绑定:
- 窗口类通过 `[HFAssetBundlePath(...)]` 标记 prefab 路径。
- 窗口类通过 `[HFUIWindowLayer(...)]` 标记 Canvas/Layer 和是否允许多开。
- `AbsHFUIWindowSetting.SetWindowType` 会读取上述 attribute生成 `winId``winTypeId` 和资源路径。
## 使用规则
- 新窗口必须继承 `AbsHFUIWindow`
- 新窗口必须配置 `HFAssetBundlePath``HFUIWindowLayer`
- 打开窗口统一走 `HFFunCatalogue.UIManager.OpenWindow<T>()`
- 关闭窗口统一走 `CloseWindow()``HFFunCatalogue.UIManager.CloseWindow<T>()`
-`OnOpen` 中注册的事件必须在 `OnClose` 中移除。
- 不直接 `Instantiate` / `Destroy` UI 窗口 prefab。
- 不绕过 `HFUIWindowMgr` 手动修改窗口 active、父节点或 siblingIndex。
- 普通业务窗口默认使用 `Dynamic`,提示类优先使用 `Tooltip`,强交互弹窗优先使用 `Popup``Highest` 仅用于明确的顶层兜底。
- UI prefab 放在 `Assets/Game/GameHFResource/<语言>/UIWindows/<模块>/`,代码中的 `HFAssetBundlePath` 使用相对 `UIWindows/...` 的路径。
- 优先使用项目已有 UIMono / ZYUGUI 绑定流程,不手写大量 `transform.Find`
- 列表、循环列表、网格优先使用已有 ZYUGUI / SuperScrollView 组件和编辑器转换工具。
- 异步加载或动画结束后再调用完成回调时,必须保证失败/取消路径不会卡住窗口状态。
## 新增 UI 窗口清单
1. 确认窗口所属功能模块。
2. 创建窗口类,继承 `AbsHFUIWindow`
3. 添加 `HFAssetBundlePath``HFUIWindowLayer`
4. 创建或更新 prefab。
5. 使用 UIMono / ZYUGUI 生成或绑定 UI 引用。
6.`HFFunCatalogue.UIManager.OpenWindow<T>()` 接入入口。
7.`OnOpen` / `OnClose` 对称处理事件、计时器、临时数据。
8. 验证打开、关闭、重复打开、切场景或语言变化时的表现。
9. 如果新增模块或改变 UI 架构,更新相关模块文档。
## 梳理任务
- [x] 梳理窗口基类和打开/关闭 API。
- [x] 梳理窗口 prefab 路径绑定方式。
- [ ] 梳理 UIMono 自动绑定和 partial 生成关系。
- [x] 梳理窗口生命周期Open/Close/状态同步。
- [x] 梳理 UI 事件订阅和释放规范。
- [x] 梳理功能 UI 和框架 UI 的边界。
## 待确认
- 常规入口已确认是 `HFFunCatalogue.UIManager` / `HFUIWindowMgr`,是否存在绕过路径待继续扫描。
- UI 资源会经过 `HFAssetBundlePathAttribute` 和语言路径规则,哪些 UI 真正有 CN/EN 差异待确认。
- UIMono 是否由编辑器工具生成。
- `OnOpen`/`OnClose` 外是否还有业务窗口统一订阅/释放规范待确认。
## 相关模块
- [资源框架](../资源管理/README.md)
- [事件框架](../事件系统/README.md)
- [ZYUGUI Tools](../../模块索引.md#编辑器工具)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | ZYUGUI Tools |
| [modules/frameworks/事件系统/README.md](../事件系统/README.md) | 事件框架 |
| [modules/frameworks/资源管理/README.md](../资源管理/README.md) | 资源框架 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | 开发规则与框架/生成规则互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| TODO补充依赖或使用本框架的模块文档 | 如依赖本框架的功能模块 |

View File

@@ -0,0 +1,231 @@
# StrayFog 框架 UI 开发指南
## 一、窗口开发
### 1.1 创建模板
```csharp
[HFAssetBundlePath(enHFLanguageRootFolder.ASSETS_GAME_GAMEHFRESOURCE, "UIWindows/MyWindow/MyWindow.prefab")]
[HFUIWindowLayer(enHFUICanvasLayer.UIWindow, enHFUIWindowLayer.Dynamic)]
public class MyWindow : AbsHFUIWindow
{
private SFUI_Button BtnClose;
protected override void OnRunAwake()
{
base.OnRunAwake();
BtnClose = FindUICtrlByName<SFUI_Button>("BtnClose");
BtnClose.OnAddClickListener += OnBtnClose;
}
protected override void OnOpen(Action _onOpenComplete)
{
base.OnOpen(_onOpenComplete);
RefreshUI();
}
private void OnBtnClose(GameObject go) => CloseWindow();
private void RefreshUI() { }
}
```
### 1.2 生命周期
| 方法 | 调用时机 | 推荐操作 |
|-----|---------|---------|
| `OnRunAwake` | 创建时一次 | 绑定 UI 事件、初始化滚动列表 `BindEvent` |
| `OnOpen` | 每次打开 | 注册 TC 事件、调用 `ShowView()` 刷新数据 |
| `OnClose` | 每次关闭 | 取消 TC 事件 |
**关键原则**
- **绑定类操作**(如 `BindEvent`)放在 `OnRunAwake`,只需执行一次
- **刷新类操作**(如 `ShowView`)放在 `OnOpen`,每次打开都需执行
### 1.3 窗口操作
```csharp
// 打开
HFFunCatalogue.UIManager.OpenWindow<MyWindow>();
// 关闭
HFFunCatalogue.UIManager.CloseWindow<MyWindow>();
```
---
## 二、事件绑定
| 事件类型 | 绑定位置 | 是否需要 `-=` |
|---------|---------|--------------|
| UI 按钮/拖拽 | `OnRunAwake` | 不需要 |
| 全局 TC 事件 | `OnOpen`/`OnClose` | 需要 |
---
## 三、常用组件
### 3.1 按钮
```csharp
SFUI_Button btn = GetComponent<SFUI_Button>();
btn.OnAddClickListener += OnBtnClick;
btn.SetSelfGray(true);
```
### 3.2 文本
```csharp
SFUI_TextMeshProUGUI txt = GetComponent<SFUI_TextMeshProUGUI>();
txt.text = "Hello";
```
### 3.3 图片
```csharp
SFUI_Image img = GetComponent<SFUI_Image>();
img.HFSetSprite(/* atlasName */, "Icon_Player", /* languageRoot */);
```
### 3.4 滚动列表(核心)
**生命周期正确划分示例**
```csharp
private SFUI_ScrollRect m_ScrollList;
private List<ItemData> m_ItemList;
protected override void OnRunAwake()
{
base.OnRunAwake();
m_ScrollList = FindUICtrlByName<SFUI_ScrollRect>("ScrollList");
// ✅ 绑定事件放在 Awake一次绑定永久生效
m_ScrollList.BindEvent(
() => m_ItemList.Count, // 数据源闭包
(idx) => itemPrefab.gameObject, // 生成预制体
OnUpdateItem, // 更新回调
(item) => item.gameObject.HFGetOrAddSimulateBehaviour<ItemSlot>() // 获取组件
);
}
protected override void OnOpen(Action _onOpenComplete)
{
base.OnOpen(_onOpenComplete);
m_ItemList = GetDataFromServer(); // 获取最新数据
m_ScrollList.ShowView(); // ✅ 刷新显示放在 Open
}
private void OnUpdateItem(int index, AbsHFUIWindow window)
{
ItemSlot slot = window as ItemSlot;
slot.SetData(m_ItemList[index]);
}
```
**设计要点**
- `BindEvent``OnRunAwake` 中执行,建立绑定机制
- `ShowView``OnOpen` 中执行,触发数据刷新
- 数据源通过闭包引用,`ShowView` 时自动获取最新数据
---
## 四、对象池
| 场景 | 推荐方式 |
|-----|---------|
| 滚动列表 | `SFUI_ScrollRect.BindEvent`(内置池) |
| 固定栏(装备/技能) | `HFSimulateBehaviourPool<T>` |
---
## 五、子组件
### 5.1 多态基类
```csharp
public abstract class AbsListItem<T> : AbsHFUIWindow
{
protected SFUI_Button m_BtnItem;
protected Action<T> m_OnSelect;
protected override void OnRunAwake()
{
base.OnRunAwake();
m_BtnItem = gameObject.GetComponent<SFUI_Button>();
m_BtnItem.OnAddClickListener += OnBtnClick;
}
public virtual void SetData(T data, Action<T> onSelect = null)
{
m_OnSelect = onSelect;
OnDataChanged(data);
}
protected abstract void OnDataChanged(T data);
private void OnBtnClick(GameObject go) => m_OnSelect?.Invoke(m_CurrentData);
}
// 子类
public class CardItem : AbsListItem<CardData>
{
protected override void OnDataChanged(CardData data) { }
}
```
---
## 六、开发规范
### 6.1 文件路径
#### 6.1.1 核心资源路径
| 类型 | 路径 | 说明 |
|-----|------|------|
| **窗口脚本** | `Assets/Game/GameHFScripte/GameFunction/UIWindows/{WindowName}/` | 窗口逻辑脚本存放目录 |
| **窗口预制体** | `Assets/Game/GameHFResource/CN/UIWindows/{WindowName}/` | UI 窗口预制体存放目录 |
| **图标图集** | `Assets/Game/GameHFResource/CN/SpriteAtlas/{AtlasName}/` | SpriteAtlas 图集资源 |
| **字体资源** | `Assets/Game/GameHFResource/CN/Fonts/` | 字体文件存放目录 |
#### 6.1.2 工具脚本路径
| 工具名称 | 路径 | 职责 |
|---------|------|------|
| **UIPrefabGenerator** | `Assets/Editor/Command/UIPrefabGenerator.cs` | JSON 转 UI 预制体 |
| **UIPrefabToJson** | `Assets/Editor/Command/UIPrefabToJson.cs` | 预制体转 JSON |
| **RemoteCommand** | `Assets/Editor/Command/RemoteCommand.cs` | 外部命令接口 |
| **ExternalCommandListener** | `Assets/Editor/Command/ExternalCommandListener.cs` | 外部命令监听器 |
#### 6.1.3 文档路径
| 文档名称 | 路径 |
|---------|------|
| UI 开发指南 | [UI窗口系统/开发指南.md](开发指南.md) |
| UI 界面拼接设计指南 | [UI窗口系统/界面拼接.md](界面拼接.md) |
#### 6.1.4 命令触发路径
- **触发文件**: `{ProjectRoot}/Trigger_Command.txt`
- **JSON 配置**: `Assets/Editor/UI/{WindowName}.json`
### 6.2 性能优化
- 使用对象池复用 UI 元素
- 将图标打包到 SpriteAtlas
- 滚动列表用 `SFUI_ScrollRect.BindEvent`
---
**版本**: v1.1
**适用**: StrayFog 框架 UI 开发人员
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [UI 窗口系统入口](README.md) | 本文件是 UI 窗口系统模块的子文档 |
| [UI 界面拼接设计指南](界面拼接.md) | 界面拼接是 UI 开发的补充设计指南 |
| [UI 拼接工具](../编辑器工具/UI拼接工具.md) | JSON 驱动 UI 生成工具说明 |
| [事件系统](../事件系统/README.md) | UI 事件订阅和释放规范 |
| [对象池与运行时对象管理](../内存对象管理/README.md) | UI 元素复用相关 |
| [资源管理](../资源管理/README.md) | UI 资源加载依赖资源管理 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | UI 命名与编码规范互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,192 @@
# UI 界面拼接设计指南
## 一、界面结构设计原则
### 1.1 模块化拆分
将界面按功能划分为独立模块:
| 模块类型 | 功能定位 | 示例 |
|---------|---------|------|
| **信息展示区** | 显示玩家状态、资源等静态信息 | 顶部信息栏、左侧状态栏 |
| **功能入口区** | 提供功能按钮入口 | 中央功能区、底部快捷栏 |
| **交互面板区** | 可展开/收起的功能面板 | 右侧功能面板、顶部按钮区 |
| **输入输出区** | 用户输入和系统反馈 | 聊天输入框、提示信息 |
### 1.2 经典布局模式
```
┌─────────────────────────────────────────────────────┐
│ TopArea (信息展示 + 可收起功能按钮) │
├──────────┬───────────────────────┬──────────────────┤
│ Left │ │ Right │
│ Area │ CenterArea │ Area │
│ (状态) │ (核心功能入口) │ (功能面板) │
│ │ │ │
├──────────┴───────────────────────┴──────────────────┤
│ BottomArea (快捷功能 + 状态信息 + 输入) │
└─────────────────────────────────────────────────────┘
```
---
## 二、组件拆分方法
### 2.1 基础组件识别
| 组件类型 | 识别特征 | 处理方式 |
|---------|---------|---------|
| **按钮** | 可点击、有交互反馈 | 使用 `SFUI_Button` |
| **文本** | 静态/动态文字显示 | 使用 `SFUI_TextMeshProUGUI` |
| **图片** | 图标、背景图 | 使用 `SFUI_Image` |
| **进度条** | HP、MP、经验等 | 使用 `SFUI_Slider` |
| **列表** | 可滚动的项目列表 | 使用 `SFUI_ScrollRect` |
### 2.2 复合组件封装
将功能相关的基础组件组合为复合组件,便于复用和维护:
**设计原则**
- 将同一功能模块的组件封装在一起(如玩家头像+血条+蓝条)
- 提供统一的数据设置接口
- 保持组件之间的逻辑独立性
---
## 三、预制体层级规范
### 3.1 标准层级结构
```
WindowName (RectTransform)
├── ModuleName (RectTransform) # 模块容器
│ ├── ComponentName (SFUI_*) # 功能组件
│ ├── SubModule (RectTransform) # 子模块
│ └── CollapsibleArea (RectTransform) # 可收起区域
└── BtnCollapse (SFUI_Button) # 收起控制按钮
```
### 3.2 命名规范
| 类型 | 命名规则 | 示例 |
|-----|---------|------|
| 模块容器 | PascalCase + Area | `TopArea` |
| 功能组件 | PascalCase | `BtnCollapseTop` |
| 可收起区域 | PascalCase + FuncArea | `TopFuncArea` |
| 状态变量 | m_ + PascalCase | `m_IsTopCollapsed` |
---
## 四、收起/展开功能设计
### 4.1 功能定义
| 按钮 | 控制区域 | 用途 |
|-----|---------|------|
| `BtnCollapseTop` | 顶部功能按钮区 | 收起/展开顶部按钮群 |
| `BtnCollapseRight` | 右侧功能面板区 | 收起/展开右侧功能面板 |
### 4.2 设计要点
**状态管理**:每个可收起区域需要一个布尔状态变量来记录当前收起/展开状态。
**交互逻辑**
1. 点击收起按钮时,切换对应区域的显示/隐藏状态
2. 同时切换按钮图标(收起时显示展开图标,展开时显示收起图标)
**布局要求**
- 收起按钮应放置在对应区域的边缘位置,便于用户找到
- 收起区域内的所有元素应作为一个整体进行显示/隐藏控制
- 收起状态应在窗口生命周期内保持记忆
---
## 五、实际案例:主界面拼接
### 5.1 模块化设计思想
**核心原则**:将界面按功能边界划分为独立模块,模块内可嵌套子模块。
**模块归类方法**
1. **一级模块**:按空间位置划分(顶部、左侧、中央、右侧、底部)
2. **二级模块**:按功能类型划分(信息展示区、功能按钮区、输入输出区)
3. **三级模块**:按具体功能划分(玩家头像、资源显示、功能按钮群)
**层级关系示例**
```
Window
├── TopArea (一级:位置)
│ ├── InfoPanel (二级:信息展示)
│ │ └── TxtWelcome (三级:具体组件)
│ └── FuncButtons (二级:功能按钮)
│ └── BtnUpload (三级:具体按钮)
└── LeftArea (一级:位置)
└── PlayerPortrait (二级:复合组件)
├── ImgAvatar (三级:头像)
└── BarHP (三级:血条)
```
**设计要点**
- 同一功能域内的元素归为同一模块(如头像、血条、蓝条归为玩家状态模块)
- 独立交互功能作为独立模块(如收起按钮单独作为控制模块)
- 可收起区域作为独立子模块,便于统一控制显示/隐藏
---
## 六、界面拼接流程
### 6.1 设计阶段
1. **需求分析**:明确界面功能和交互需求
2. **模块划分**:将界面拆分为独立功能模块
3. **层级设计**:设计预制体层级结构
### 6.2 开发阶段
1. **组件创建**:创建基础组件和复合组件
2. **预制体搭建**:按层级结构搭建预制体
3. **脚本编写**:实现交互逻辑和收起/展开功能
### 6.3 测试阶段
1. **功能测试**:验证所有按钮和交互功能
2. **收起测试**:测试收起/展开功能正常工作
3. **适配测试**:验证不同分辨率下的显示效果
---
## 七、注意事项
1. **组件复用**:相同功能的组件尽量复用,减少冗余
2. **状态管理**:收起状态需要正确保存和恢复
3. **图标反馈**:收起按钮图标应随状态变化
4. **性能优化**:可收起区域使用 `SetActive` 而非销毁重建
5. **命名规范**:统一的命名规则便于维护和查找
---
## 八、JSON 配置驱动的 UI 生成方式
本项目支持通过 JSON 配置快速生成 UI 预制体,详细工具说明、命令格式和部署步骤见 [UI 拼接工具](../编辑器工具/UI拼接工具.md)。
UI 相关人员核心工作流程:读取设计效果图和碎片图片,生成 JSON 配置文件,再通过工具自动生成 Unity 预制体。
---
**版本**: v1.2
**适用**: StrayFog 框架 UI 拼接开发人员
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [UI 窗口系统入口](README.md) | 本文件是 UI 窗口系统模块的子文档 |
| [UI 开发指南](开发指南.md) | 窗口开发与界面拼接互相补充 |
| [UI 拼接工具](../编辑器工具/UI拼接工具.md) | JSON 驱动 UI 生成工具说明 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | UI 命名与资源规则互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,122 @@
# 事件系统
## 分类
- 核心框架
## 目的
提供客户端内部事件CTC和服务器消息事件STC的统一派发、监听和释放机制。
---
## 核心路径
- 框架层:`Assets/Game/GameHFScripte/CopyToHF/Event/`
- 功能层:`Assets/Game/GameHFScripte/GameFunction/Event/`
- STC 事件参数:`Assets/Game/GameHFScripte/GameFunction/Event/STCEventArg/`
---
## 核心类
| 类 | 职责 |
|---|---|
| `HFEventManager` | 事件管理器单例,继承 `AbsHFSingleRunTime<HFEventManager>` |
| `TCEvent` | CTC 事件句柄 |
| `STCEvent` | STC 事件句柄 |
| `CTCHandle` | CTC 事件处理器定义位置 |
| `AbsHFEventArg<K,T>` | 事件参数基类 |
---
## 事件派发链路
### CTC 事件
```text
AbsHFEventArg<enHFCTCEvent>.Dispatch()
-> HFEventManager.Instance.TCEvent.Dispatch(arg)
```
CTC 事件来源:
- 功能代码手动创建事件参数对象。
- 设置字段后调用 `.Dispatch()`
- 示例:业务管理器派发初始化事件、背包更新事件。
### STC 事件
```text
AbsHFEventArg<enHFSTCEvent>.Dispatch()
-> HFEventManager.Instance.STCEvent.Dispatch(arg)
```
STC 事件来源:
```text
WSSession 收到 protobuf S2C
-> 反射创建 HFEvent_STC_{MessageTypeName}
-> 写入 Request / ErrorCode
-> Dispatch 到 STCEvent
```
---
## 监听与派发机制
```text
AddListener<TEventArg>(eventId, callback)
-> 按 eventId 分组
-> 按事件参数类型 hash 分组
Dispatch(arg)
-> 根据 arg.eventId 找事件组
-> 根据 arg.GetType() 找具体类型监听
-> Invoke callbacks
-> arg.Recycle()
```
---
## 使用规则
- UI 窗口在 `OnOpen` 中注册的事件,必须在 `OnClose` 中移除。
- 管理器监听 STC 事件通常在初始化时添加,在 `Recycle` 或销毁时移除。
- 不要直接监听与当前模块无关的事件。
- 事件参数对象由框架自动回收,业务代码不要重复释放。
---
## 错误响应
当服务器返回错误响应时:
```text
messagePage.IsError = true
-> 解析 ErrorResponse
-> 派发 HFEvent_ErrorCode
-> 同时仍创建对应 HFEvent_STC_* 并写入 ErrorCode
```
---
## 相关模块
- [网络系统](../网络系统.md)
- [UI 窗口系统](../UI窗口系统/README.md)
- [实体系统](../实体系统/README.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/网络系统.md](../网络系统.md) | S2C 消息是 STC 事件来源 |
| [modules/frameworks/UI窗口系统/README.md](../UI窗口系统/README.md) | UI 窗口订阅和释放事件 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,168 @@
# 事件系统使用指南
## 一、事件系统概述
事件系统分为两类:
- **STCEvent**Server To Client服务器到客户端消息
- **CTCEvent**Client To Client客户端内部消息
---
## 二、定义事件
### 2.1 定义事件枚举和参数
```csharp
// 事件 ID 枚举
public enum GameEventId
{
PlayerLevelUp,
EnemyKilled,
GoldChanged
}
// 事件参数基类
public class GameEventArgs : IHFEventArg<GameEventId>, IHFRecycle
{
public GameEventId eventId { get; set; }
public void Recycle()
{
// 重置状态
eventId = default;
}
}
// 具体事件参数
public class PlayerLevelUpEventArgs : GameEventArgs
{
public int playerId;
public int oldLevel;
public int newLevel;
}
```
---
## 三、UI 窗口事件监听
### 3.1 在窗口中注册 CTCEvent
```csharp
[HFAssetBundlePath(enHFLanguageRootFolder.ASSETS_GAME_GAMEHFRESOURCE, "UIWindows/CardDeck/CardDeckWindow.prefab")]
[HFUIWindowLayer(enHFUICanvasLayer.UIWindow, enHFUIWindowLayer.Dynamic, isIgnoreCloseByESC = false)]
public class CardDeckWindow : AbsHFUIWindow
{
protected override void OnOpen(Action _onOpenComplete)
{
base.OnOpen(_onOpenComplete);
// 注册客户端内部消息监听CTCEvent
HFFunCatalogue.Event.CTCEvent.AddListener<HFEvent_CTC_CardUpdate>(enHFCTCEvent.CardUpdate, OnCardUpdate);
}
protected override void OnClose(Action _onCloseComplete)
{
base.OnClose(_onCloseComplete);
// 取消客户端内部消息监听
HFFunCatalogue.Event.CTCEvent.RemoveListener<HFEvent_CTC_CardUpdate>(enHFCTCEvent.CardUpdate, OnCardUpdate);
}
void OnCardUpdate(HFEventArgBase arg)
{
HFEvent_CTC_CardUpdate cardArg = arg as HFEvent_CTC_CardUpdate;
// 更新卡组 UI 显示
}
}
```
> **注意**UI 窗口中**不监听服务器事件STCEvent**,服务器事件应在 Logic 层的 Manager 中监听。
---
## 四、Manager 事件监听
### 4.1 使用 AbsHFSingleMonoBehaviour需要 Update
```csharp
public class PlayerManager : AbsHFSingleMonoBehaviour<PlayerManager>
{
protected override int[] simulateBehaviourEvents => new int[]
{
enHFSimulateBehaviourEvent.Mono_Update,
enHFSimulateBehaviourEvent.Mono_FixedUpdate
};
protected override void OnAfterCreateInstance()
{
base.OnAfterCreateInstance();
RegisterServerEvents();
}
void RegisterServerEvents()
{
HFFunCatalogue.Event.STCEvent.AddListener<HFEvent_STC_PlayerInfo>(enHFSTCEvent.PlayerInfo, OnPlayerInfo);
}
void OnPlayerInfo(HFEventArgBase arg)
{
HFEvent_STC_PlayerInfo info = arg as HFEvent_STC_PlayerInfo;
// 处理服务器返回的玩家信息
// 触发客户端事件通知 UI 更新
HFEvent_CTC_PlayerLevelUp evt = HFFunCatalogue.New<HFEvent_CTC_PlayerLevelUp>();
evt.playerId = info.playerId;
evt.newLevel = info.level;
HFFunCatalogue.Event.CTCEvent.Dispatch(evt);
}
}
```
### 4.2 使用 AbsHFSingleRunTime纯逻辑
```csharp
public class ConfigManager : AbsHFSingleRunTime<ConfigManager>
{
protected override void OnAfterCreateInstance()
{
base.OnAfterCreateInstance();
// 加载配置表
LoadConfigTables();
}
void LoadConfigTables()
{
// 加载配置数据
}
}
```
---
## 五、Manager 类型对比
| 类型 | 说明 | 适用场景 |
|-----|------|---------|
| `AbsHFSingleMonoBehaviour` | 生成 GameObject可注册 MonoBehaviour 事件 | 需要 Update/FixedUpdate 等生命周期 |
| `AbsHFSingleRunTime` | 纯内存单例,无 GameObject | 不需要生命周期事件 |
---
**版本**: v1.0
**生效日期**: 2026年
**适用范围**: GiftGameX 项目业务开发人员
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [事件系统入口](README.md) | 本文件是事件系统模块的子文档 |
| [网络系统](../网络系统.md) | STC 事件来源与网络系统相关 |
| [UI 窗口系统](../UI窗口系统/README.md) | UI 窗口订阅和释放事件 |
| [实体系统](../实体系统/README.md) | Manager 常与实体系统协作 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | 事件订阅/释放与开发规则互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,110 @@
# 对象池与运行时对象管理
## 分类
- 核心框架
## 目的
提供类实例池、行为对象池、脚本对象池、实体池和 RT 展示对象的统一复用机制,减少 GC 和实例化开销。
---
## 核心路径
- 内存管理:`Assets/Game/GameHFScripte/CopyToHF/MemoryManager/`
- 运行时对象:`Assets/Game/GameHFScripte/GameFunction/RTObjectManager/`
- 调用池:`Assets/Game/GameHFScripte/GameFunction/Logic/CallPoolManager/`
- 实体池:`Assets/Game/GameHFScripte/GameFunction/GameEntity/EntityPool/`
---
## 池类型
### HFClassEntityManager
类实例池,按类型 hash 维护 `Stack<object>`
```text
GetClassEntity<T>()
-> 优先从 Stack 弹出复用
-> 否则 Activator.CreateInstance<T>()
```
### HFSimulateBehaviourPool<T>
行为对象池,用于继承 `AbsHFSimulateBehaviour` 的对象。
- 维护未使用队列和使用中列表。
- `GiveBack()` 隐藏 GameObject 并调用 `Recycle()`
### HFScriptMemPool<T>
脚本对象池,用于 `IHFRecycle` 的普通对象或 MonoBehaviour。
- 若类型继承 MonoBehaviour自动创建 GameObject 挂载组件。
- 若类型为普通类,直接 `Activator.CreateInstance`
### 实体池
`HFEntityPool` + 各类 `AbsHFEntityPool` 实现,见 [实体系统](../实体系统/README.md)。
---
## 运行时展示对象RT
RT 对象用于在独立相机上展示模型、特效等:
- `RTObject`:运行时展示对象基类
- `RTGameObjectRes`:从资源 id 创建展示对象
- `RTEntity`:实体展示对象
- `RTCameraItem`RT 相机项
- `RTUIEffectCameraItem`UI 特效相机项
创建流程:
```text
RTGameObjectRes
-> 从资源 id 创建 GameObject
-> 资源加载走 HFABMgr.Instance.LoadAsset
```
---
## 回收接口
| 接口 | 职责 |
|---|---|
| `IHFRecycle` | 基础回收接口,声明 `Recycle()` |
| `IHFRecycleEventHandler` | 回收事件处理 |
| `OnAfterRecycleHandler` | 回收到类实例池 |
---
## 使用规则
- 频繁创建/销毁的对象优先使用对象池。
- 归还对象时确保来源正确,避免泄漏。
- 对象池内对象在归还后应重置状态。
- RT 对象注意及时释放相机 seat 和资源引用。
---
## 相关模块
- [实体系统](../实体系统/README.md)
- [资源管理](../资源管理/README.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/实体系统/README.md](../实体系统/README.md) | 实体池是对象池的一种特化 |
| [modules/frameworks/资源管理/README.md](../资源管理/README.md) | RT 对象资源加载依赖资源管理 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,116 @@
# 对象池使用指南
## 一、GameObject 对象池
### 1.1 基本用法
```csharp
public class BulletManager : MonoBehaviour
{
[SerializeField] private GameObject bulletPrefab;
private HFObjectMemPool m_BulletPool;
void Awake()
{
// 创建对象池
m_BulletPool = new HFObjectMemPool(bulletPrefab, transform);
}
public void Fire(Vector3 position, Quaternion rotation)
{
bool isNew;
GameObject bullet = m_BulletPool.GetItem(out isNew);
bullet.transform.position = position;
bullet.transform.rotation = rotation;
// 如果是新创建的对象,初始化组件
if (isNew)
{
bullet.GetComponent<BulletComponent>().Initialize();
}
}
public void RecycleBullet(GameObject bullet)
{
m_BulletPool.GiveBack(bullet);
}
void OnDestroy()
{
// 清理对象池
m_BulletPool.Recycle();
}
}
```
---
## 二、脚本对象池
### 2.1 基本用法
```csharp
public class EventArgPool : MonoBehaviour
{
private HFScriptMemPool<GameEventArgs> m_ArgPool;
void Awake()
{
m_ArgPool = new HFScriptMemPool<GameEventArgs>();
}
public GameEventArgs GetEventArgs(GameEventId eventId)
{
bool isNew;
GameEventArgs arg = m_ArgPool.GetItem(out isNew);
arg.eventId = eventId;
return arg;
}
public void RecycleEventArgs(GameEventArgs arg)
{
m_ArgPool.GiveBack(arg);
}
}
```
---
## 三、使用注意事项
### 3.1 对象池设计特点
| 特性 | 说明 |
|-----|------|
| **双池结构** | 空闲池 + 使用池,高效管理对象状态 |
| **内存泄漏检测** | 归还时校验来源,及时发现问题 |
| **泛型支持** | `HFScriptMemPool<T>` 提供类型安全 |
| **Mono 支持** | 自动创建 GameObject无缝集成 Unity |
### 3.2 性能优化建议
- 频繁创建销毁的对象必须使用对象池
- 合理设置对象池初始容量
- 定期清理空闲对象避免内存泄漏
---
**版本**: v1.0
**生效日期**: 2026年
**适用范围**: GiftGameX 项目业务开发人员
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [对象池与运行时对象管理入口](README.md) | 本文件是内存对象管理模块的子文档 |
| [实体系统](../实体系统/README.md) | 实体池是对象池的一种特化 |
| [资源管理](../资源管理/README.md) | RT 对象资源加载依赖资源管理 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,98 @@
# 场景系统
## 分类
- 核心框架
## 目的
管理 Unity 场景的加载、资源配置读取和场景生命周期回调。
---
## 核心路径
- 场景管理器:`Assets/Game/GameHFScripte/GameFunction/SceneManager/`
- Unity 场景:`Assets/Game/GameMono/Scenes/`
---
## 核心类
| 类 | 职责 |
|---|---|
| `HFSceneMgr` | 场景管理器单例,继承 `AbsHFSingleMonoBehaviour<HFSceneMgr>` |
| `enHFSceneName` | 场景枚举,例如 `Game = 80001` |
---
## 加载流程
```text
LoadScene(enHFSceneName scene, Action complete)
-> 转为资源/关卡 id
-> LoadScene(int levelId, Action complete)
-> HFFunCatalogue.NDB.ds_Resource840C5F09TableData.ReadTableByPK(levelId)
-> 读取资源配置
```
### Editor 模式
```text
从配置 Path 取文件名
-> SceneManager.LoadScene(scName)
```
### 非 Editor 模式
```text
HFFunCatalogue.Asset.LoadAsset(levelId, ...)
-> 用结果中的 res.sceneName 调用 SceneManager.LoadScene
```
`OnLoadScene()` 同步调用 `SceneManager.LoadScene()` 后立即执行完成回调。
---
## 生命周期
`ZYCore` 中存在 `IZYH_ZYCore_Scene``AbsZYH_ZYCore_Scene` 接口:
- 进入加载场景
- 加载完成
- 进入/退出场景前后
- 切场景释放
`ZYUGUI` 窗口管理器提供:
- `isAutoCloseWhenToggleScene`:切场景时自动关闭
- `isAutoDestoryWhenToggleScene`:切场景时自动销毁
---
## 使用规则
- 场景切换统一走 `HFFunCatalogue.Scene.LoadScene()`
- 不要在场景里堆放运行时动态加载的资源。
- 切场景时注意 UI 窗口的自动关闭/销毁配置。
---
## 相关模块
- [资源管理](资源管理/README.md)
- [UI 窗口系统](UI窗口系统/README.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/资源管理/README.md](资源管理/README.md) | 场景资源加载依赖资源管理 |
| [modules/frameworks/UI窗口系统/README.md](UI窗口系统/README.md) | 切场景影响 UI 窗口生命周期 |
| [rules/10-架构说明.md](../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,138 @@
# 实体系统
## 分类
- 核心框架
## 目的
管理游戏实体的创建、生命周期、System-Model 架构、实体池和子系统协作。
---
## 核心路径
- 实体框架:`Assets/Game/GameHFScripte/GameFunction/GameEntity/`
- 实体数据:`Assets/Game/GameHFScripte/GameFunction/Logic/EntityData/`
- 子目录:`Core/``Entity/``EntityPool/``RVO/``StateMachineBehaviour/`
---
## 核心类
| 类 | 职责 |
|---|---|
| `HFAbsEntity` | 实体基类,继承 `AbsHFSimulateBehaviour` |
| `HFEntity_Basic` | 基础实体标签 |
| `HFEntity_Enemy` | 敌人类型标签 |
| `HFEntity_Human` | 人类/玩家类型标签 |
| `HFEntityPool` | 实体池入口 |
| `HFBasicPool` / `HFEnemyPool` / `HFHumanityPool` | 按 `enEntityLayer` 分类的实体池实现 |
| `AbsHFEntityPool` | 实体池抽象基类 |
| `stEntityInfo` | 实体数据结构 |
---
## System-Model 架构
`HFAbsEntity` 内部维护两个集合:
```text
m_Models : IModel[] // 数据层
m_Systems : ISystem[] // 表现层/逻辑层
```
`enSystemType` 关联 `IModel``ISystem`
添加系统:
```csharp
AddSystem<T>()
-> HFClassEntityManager.Instance.GetClassEntity<T>() // 创建或复用系统对象
-> IModel
-> model.Init(this, system.Init) // 建立实体、模型、系统关系
```
更新顺序:
```text
OnSystemUpdate() // 表现层更新
OnModelUpdate() // 数据层更新
```
---
## 实体池机制
`HFEntityPool``enEntityLayer` 注册不同的 `AbsHFEntityPool` 实现。
创建实体:
```text
AbsHFEntityPool.OnCreateEnity<T>()
-> 创建 GameObject 根节点
-> 维护 onlyid -> entity 索引
-> 维护 EntityId -> onlyid 索引
```
查询实体:
```text
HFEntityPool.SelectByOnlyid(onlyid)
-> 通过 onlyid -> enEntityLayer 找到对应池
-> 从具体池取实体
```
回收实体:
```text
DelEntityByOnlyid(onlyid)
-> 从池索引移除
-> 调用 entity.Recycle()
```
---
## 子系统
实体相关的子系统通常位于:
- `Models/AI``Systems/AI` — AI
- `Models/Animation``Systems/Animation` — 动画
- `Models/Feature``Systems/Feature` — 特性/属性
- `Models/Move``Systems/Move` — 移动
- `Models/Skill``Systems/Skill` — 技能
- `HFSkillTrigger_*` — 技能触发器
- `HFSkill_*_Releaser` — 技能释放器
- `GameEntity/RVO/RVOManager.cs` — RVO 避障
---
## 使用规则
- 新实体类型应继承 `HFAbsEntity`
- 实体对象统一通过 `HFEntityPool` 创建和回收。
- 系统通过 `AddSystem<T>()` 添加,避免直接 new。
- 子系统数据更新放在 `OnModelUpdate()`,表现更新放在 `OnSystemUpdate()`
---
## 相关模块
- [技能系统](../技能系统/README.md)
- [对象池](../内存对象管理/README.md)
- [数据/配置框架](../数据配置框架.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/技能系统/README.md](../技能系统/README.md) | 技能系统是实体系统的子系统 |
| [modules/frameworks/内存对象管理/README.md](../内存对象管理/README.md) | 实体池与对象池机制相关 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,339 @@
# Entity 开发指南
## 一、Entity 概述
Entity 是游戏世界中所有游戏对象的抽象基类包括玩家、敌人、塔楼、城堡等。Entity 系统提供统一的对象管理、生命周期控制和 **System-Model 架构**
### 1.1 Entity 分类
Entity 分类根据具体业务需求定义,以下为示例分类(项目实际分类以业务为准):
| 类别 | 说明 | 示例 |
|-----|------|------|
| **Human** | 具有生命、移动能力的实体 | Player、Enemy |
| **Tower** | 防御塔实体 | 箭塔、炮塔 |
| **Castle** | 城堡/基地实体 | 玩家基地 |
| **Obstacle** | 障碍物实体 | 墙壁、陷阱 |
> **说明**:实际项目中需根据业务需求定义 Entity 类型,继承 `HFAbsEntity` 基类并实现对应逻辑。
### 1.2 核心数据结构
```csharp
// Entity 唯一标识
public long onlyid { get; } // 运行时唯一ID
public int EntityId { get; } // 资源配置ID
public enEntityLayer entityType { get; } // Entity 类型
public enEntityCamp entityCamp { get; } // 阵营
public bool isDeath { get; } // 是否死亡
```
---
## 二、Entity 创建规范
### 2.1 继承结构
```csharp
// 核心基类
public abstract partial class HFAbsEntity : AbsHFSimulateBehaviour
{
public long onlyid { get; private set; }
public enEntityLayer entityType { get; private set; }
public enEntityCamp entityCamp { get; private set; }
public int EntityId { get; private set; }
public bool isDeath { get; private set; } // 逻辑死亡标记
// 初始化方法
public void Init() { ... }
public virtual void OnInit() { ... }
// 死亡方法(表现层)
public void Death() { ... } // 触发死亡事件
public virtual void OnDeath() { ... } // 子类重写处理表现死亡逻辑
// 逻辑死亡(数据层)
protected void Logic_Death() { ... } // 设置 isDeath=true数据层死亡
protected virtual void OnLogicDeath() { ... } // 子类重写处理逻辑死亡逻辑
}
// 具体 Entity 示例 - Human 类型
public class HFEntity_Human : HFAbsEntity
{
protected override void OnRunAwake()
{
base.OnRunAwake();
AddSystem<HFFeatureSystem>();
AddSystem<HFAnimationSystem_Human>();
AddSystem<HFApathMoveSystem>();
AddSystem<HFSkillSystem>();
}
public override void OnInit()
{
// 初始化配置数据
}
public override void OnDeath()
{
// 死亡处理逻辑
}
}
```
### 2.2 命名规范
| 类型 | 命名规则 | 示例 |
|-----|---------|------|
| Entity 类 | `HFEntity_{TypeName}` | `HFEntity_Human``HFEntity_Tower` |
| 系统类 | `HF{SystemName}System[_TypeName]` | `HFAnimationSystem_Human` |
| 模型类 | `HF{ModelName}Model[_TypeName]` | `HFAnimationModel_Human` |
| 数据结构 | `st{DataName}` | `stAbilityData``stDamageInfo` |
---
## 三、Entity 生命周期
### 3.1 生命周期方法
| 方法 | 调用时机 | 职责 | 层级 |
|-----|---------|------|-----|
| `OnRunAwake()` | 对象创建时 | 添加 System 组件 | 表现层 |
| `Init()` | 初始化时调用 | 重置死亡状态、调用 OnInit | - |
| `OnInit()` | Init 内部调用 | 子类重写,初始化配置数据 | 数据层 |
| `Logic_Death()` | HP 归零时调用 | 设置 `isDeath=true`,调用 OnLogicDeath | 数据层 |
| `OnLogicDeath()` | Logic_Death 内部调用 | 子类重写处理逻辑死亡逻辑 | 数据层 |
| `Death()` | 逻辑死亡后调用 | 触发死亡事件、调用 OnDeath | 表现层 |
| `OnDeath()` | Death 内部调用 | 子类重写,处理表现死亡逻辑(动画、特效等) | 表现层 |
| `OnRecycle()` | 对象回收时 | 清理资源、释放引用 | - |
> **重要区分**
> - **逻辑死亡**`Logic_Death()` 由伤害计算触发,设置 `isDeath=true`,仅影响数据层
> - **表现死亡**`Death()` 在逻辑死亡后调用,触发死亡事件和表现效果(动画、音效等)
### 3.2 创建流程
```csharp
// 1. 对象池创建 Entity
HFEntityPool.Instance.CreateEntity<HFEntity_Human>(onlyid, entityId,
() => HFClassEntityManager.Instance.GetClassEntity<HFEntity_Human>(),
(entity) => {
// 设置阵营
entity.SetEntityCamp(enEntityCamp.Player);
// 初始化
entity.Init();
});
```
---
## 四、System-Model 架构
### 4.1 架构设计
Entity 采用 **System-Model 分离架构**
- **System 层**处理表现逻辑Update、状态转换
- **Model 层**:处理数据逻辑(数据存储、计算)
### 4.2 添加系统
```csharp
public class HFEntity_Human : HFAbsEntity
{
protected override void OnRunAwake()
{
base.OnRunAwake();
// 添加系统(按执行顺序)
AddSystem<HFFeatureSystem>(); // 属性系统
AddSystem<HFAnimationSystem_Human>(); // 动画系统
AddSystem<HFApathMoveSystem>(); // 寻路移动系统
AddSystem<HFSkillSystem>(); // 技能系统
}
}
```
### 4.3 常用系统
| 系统 | 职责 | 适用 Entity |
|-----|------|-----------|
| `HFFeatureSystem` | 属性管理 | 所有 Entity |
| `HFAnimationSystem` | 动画控制 | Human、Tower |
| `HFMoveSystem` | 移动控制 | Human |
| `HFAISystem` | AI 行为 | Enemy、Tower |
| `HFSkillSystem` | 技能系统 | Human、Tower |
### 4.4 获取 Model
```csharp
// 通过 System 类型获取 Model
HFAnimationModel_Human animModel = GetModel<HFAnimationModel_Human>();
// 通过 System 对象获取 Model
HFSkillSystem skillSystem = ...;
HFSkillModel skillModel = GetModel<HFSkillModel>(skillSystem);
```
---
## 五、属性系统
### 5.1 属性分类
| 属性类型 | 说明 | 示例 |
|---------|------|------|
| **基础属性** | 配置表读取,基础值 | HP、SPD、MoveSpeed |
| **战斗属性** | 实际战斗中使用,受 Buff 影响 | Fight_SPD、Fight_ATK |
### 5.2 属性数据结构
```csharp
public partial class HFAbsEntity
{
// 基础属性(配置表)
private float m_Hp;
private float m_MaxHp;
private float m_SPD;
private float m_MoveSpeed;
private float m_NormalATK;
private float m_Critical;
private float m_CriticalDamage;
// 战斗属性(实时计算)
private float m_Fight_SPD;
private float m_Fight_NormalATK;
// ...
}
```
### 5.3 属性变更
```csharp
// 临时属性变更Buff
public void ChangeData(stAbilityData data)
{
if (data.IsPermanent)
{
// 永久属性变更
switch (data.ChangeTarget)
{
case enEntitityAbility.SPD:
m_SPD += data.Value;
break;
case enEntitityAbility.NormalATK:
m_NormalATK += data.Value;
break;
}
}
else
{
// 临时属性变更(存入 Buff 字典)
OnAddEntityAbilityData(data);
}
// 刷新战斗属性
OnRefushEntityAbility(data.ChangeTarget);
// 通知所有 Model 属性变更
ChangeAbility(data.ChangeTarget);
}
```
---
## 六、Entity 对象池
### 6.1 对象池职责
```csharp
public abstract partial class AbsHFEntityPool : IHFObject
{
public abstract enEntityLayer entityType { get; }
// 根据 onlyid 获取 Entity
public virtual HFAbsEntity SelectByOnlyid(long onlyid) { ... }
// 根据资源 ID 获取所有 Entity
public List<HFAbsEntity> SelectByResId(int resid) { ... }
// 删除 Entity
public virtual bool DelEntityByOnlyid(long onlyid) { ... }
// 获取所有 Entity
public List<HFAbsEntity> GetAllEntities() { ... }
}
```
### 6.2 使用示例
```csharp
// 创建 Entity
HFEntityPool.Instance.CreateEntity<HFEntity_Human>(
onlyid: 1001,
entityId: 10001,
func: () => HFClassEntityManager.Instance.GetClassEntity<HFEntity_Human>(),
action: (entity) => {
entity.SetEntityCamp(enEntityCamp.Player);
entity.Init();
});
// 获取 Entity
HFAbsEntity entity = HFEntityPool.Instance.SelectByOnlyid(1001);
HFEntity_Human human = entity as HFEntity_Human;
// 删除 Entity
HFEntityPool.Instance.DelEntityByOnlyid(1001);
```
---
## 七、开发规范
### 7.1 文件路径
| 类型 | 路径 |
|-----|------|
| Entity 基类 | `Assets/Game/GameHFScripte/GameFunction/GameEntity/Core/BaseEntity/AbsEntity/` |
| 具体 Entity | `Assets/Game/GameHFScripte/GameFunction/GameEntity/Entity/EntityTag/` |
| 系统类 | `Assets/Game/GameHFScripte/GameFunction/GameEntity/Entity/System/` |
| 模型类 | `Assets/Game/GameHFScripte/GameFunction/GameEntity/Entity/Model/` |
| 对象池 | `Assets/Game/GameHFScripte/GameFunction/GameEntity/Core/BaseEntity/EntityPool/` |
### 7.2 编码规范
1. **必须继承** `HFAbsEntity` 基类
2. **必须在** `OnRunAwake()` 中添加所需 System
3. **必须重写** `OnInit()` 初始化配置数据
4. **必须重写** `OnDeath()` 处理死亡逻辑
5. **禁止** 在 Entity 中直接访问 Manager通过 `HFFunCatalogue` 访问
6. **禁止** 在 Entity 中持有场景引用
### 7.3 性能优化
- 使用对象池复用 Entity 实例
- 使用 `AddSystem<T>()` 延迟初始化系统
- 属性变更通过 `ChangeAbility()` 通知所有 Model
- 死亡 Entity 通过 `OnDeathFinish()` 回调回收
---
**版本**: v1.1
**生效日期**: 2026年
**适用范围**: StrayFog 框架开发人员
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [实体系统入口](README.md) | 本文件是实体系统模块的子文档 |
| [通用技能系统使用指南](../技能系统/使用指南.md) | 技能系统是 Entity 常用子系统 |
| [对象池与运行时对象管理](../内存对象管理/README.md) | Entity 池机制相关 |
| [事件系统](../事件系统/README.md) | Entity 间通信常依赖事件系统 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | 编码规范与开发规则互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,89 @@
# Unity Entities 注意事项和最佳实践
## 1. 枚举定义相关
### enHFCTCEvent 枚举
- **新增项**`Custom = 281` - 用于自定义事件
- **用途**:在 `HFEvent_OnDamage` 等自定义事件中使用
- **注意**:添加新事件时,确保数值不与其他事件冲突
### enEntityLayer 枚举
- **现有项**`None=0`, `Role`, `Monster`, `Max`
- **注意**:没有 `Enemy` 项,敌人应归类为 `Monster`
## 2. 实体-系统-模型 (ESM) 架构相关
### HFAbsEntity 访问方式
- **Transform 访问**:使用 `entity.gameObject.transform.position` 而不是 `entity.transform.position`
- **属性访问**:使用公共属性如 `entity.NormalATK` 而不是私有字段 `entity.m_NormalATK`
- **系统获取**`HFAbsEntity` 没有 `GetSystem<T>()` 方法,只有 `GetModel<T>()` 方法
- **模型获取**:通过 `entity.GetModel<T>()` 获取模型实例
### 系统间通信
- **推荐方式**:通过事件系统进行通信,而不是直接获取其他系统
- **事件分发**:使用 `event.Dispatch()` 而不是 `HFFunCatalogue.Event.TCEvent.SendEvent()`
## 3. 数据表字段映射
### Role 表字段
- `Lifefold` -> 生命值
- `Attackfold` -> 攻击力
- `Defensefold` -> 防御力
- `AttackRange` -> 攻击范围
- `CriticalRate` -> 暴击率
- `CriticalDamage` -> 暴击伤害
- **注意**Role 表中没有速度字段 (SPD)
## 4. 动画系统相关
### FSM (有限状态机) 系统
- **HFFMSModel** 现在包含 `CurrentSkeletonAnimation` 属性
- **访问方式**:通过 `FSM.CurrentSkeletonAnimation` 访问 SkeletonAnimation
- **动画速度设置**:使用 `skeletonAnimation.timeScale = speed`
## 5. AI 系统相关
### HFAIModel_Enemy
- **伤害处理**:通过事件系统 (`HFEvent_OnDamage`) 而不是直接获取系统
- **攻击逻辑**:创建并分发伤害事件,让其他系统处理实际伤害计算
## 6. 错误处理和调试
### 常见错误类型
- **CS0117**: 枚举中不包含指定定义 - 检查枚举项是否存在
- **CS1061**: 类型不包含指定定义 - 检查属性/方法名是否正确
- **CS0122**: 成员因保护级别不可访问 - 使用公共 API 而不是私有字段
### 修复策略
- 优先使用公共属性而不是私有字段
- 通过事件系统进行组件间通信
- 正确访问 GameObject 的 Transform 组件
- 使用正确的枚举值
## 7. 架构模式
### 系统-模型 (System-Model) 模式
- **AbsHFMoveSystem<T>** 需要指定泛型参数
- **AbsHFAIModel** 不能直接获取系统,应通过事件或模型进行通信
- **AbsHFAnimationModel** 需要配合 `AbsHFFMSModel` 使用
### 事件驱动架构
- 自定义事件需继承 `AbsHFEventArg<enHFCTCEvent>`
- 事件 ID 使用 `enHFCTCEvent.Custom` 或其他预定义事件
- 事件分发使用 `Dispatch()` 方法
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [实体系统入口](README.md) | 本文件是实体系统模块的子文档 |
| [Entity 开发指南](开发指南.md) | 与开发指南中的 System-Model、属性系统互补 |
| [事件系统](../事件系统/README.md) | 自定义事件派发相关 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | 公共 API 访问方式与开发规则互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,134 @@
# 技能系统
## 分类
- 核心框架
## 目的
为角色、怪物等实体提供统一的技能管理、冷却控制、释放接口和事件通知。
---
## 核心组成
| 类/结构 | 职责 |
|---------|------|
| `HFGenericSkillSystem` | 通用技能系统,继承 `AbsHFSkillSystem<HFGenericSkillModel>` |
| `HFGenericSkillModel` | 通用技能模型,继承 `HFAbsModel` |
| `HFEvent_OnSkillUsed` | 技能使用事件 |
| `stSkillConfig` | 技能配置结构 |
---
## 技能管理
```csharp
// 添加技能
skillSystem.AddSkill(skillId);
// 移除技能
skillSystem.RemoveSkill(skillId);
// 检查技能是否就绪
bool isReady = skillSystem.IsSkillReady(skillId);
// 获取技能冷却时间
float cooldown = skillSystem.GetSkillCooldown(skillId);
// 是否有技能在冷却
bool hasCooldown = skillSystem.HasSkillInCooldown();
// 清除所有冷却
skillSystem.ClearAllCooldowns();
```
---
## 技能使用
```csharp
// 对目标使用技能
skillSystem.UseSkill(skillId, targetEntity);
// 对位置使用技能
skillSystem.UseSkillAtPosition(skillId, worldPosition);
```
触发事件:
```csharp
public class HFEvent_OnSkillUsed : AbsHFEventArg<enHFCTCEvent>
{
public HFAbsEntity Caster { get; set; }
public int SkillId { get; set; }
public HFAbsEntity Target { get; set; }
public Vector3 Position { get; set; }
}
```
---
## 自动配置加载
系统会根据实体配置自动加载技能:
```csharp
var config = HFFunCatalogue.NDB.ds_EntityConfig_Sheet_TableData_1FC4334A.ReadTable(ownerEntity.EntityId);
if (config != null && config.SkillID > 0)
{
AddSkill(config.SkillID);
}
```
---
## 实体中的添加方式
在角色或敌人实体中统一添加:
```csharp
AddSystem<HFGenericSkillSystem>();
```
---
## 子系统边界
从实体系统角度看,技能相关子目录通常包括:
- `Models/Skill` — 技能数据模型
- `Systems/Skill` — 技能系统逻辑
- `HFSkillTrigger_*` — 技能触发器
- `HFSkill_*_Releaser` — 技能释放器
---
## 使用规则
- 角色和怪物使用同一套技能系统接口。
- 技能 ID 在项目中应保持唯一。
- 技能效果逻辑应在事件处理器中实现,避免直接写在释放器里。
- 合理设置冷却时间以平衡游戏体验。
---
## 相关模块
- [实体系统](../实体系统/README.md)
- [事件系统](../事件系统/README.md)
- [数据/配置框架](../数据配置框架.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/实体系统/README.md](../实体系统/README.md) | 技能系统是实体系统的子系统 |
| [modules/frameworks/事件系统/README.md](../事件系统/README.md) | 技能使用事件依赖事件系统 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,143 @@
# 通用技能系统使用指南
## 概述
通用技能系统(`HFGenericSkillSystem`)是一个适用于角色和怪物的统一技能管理系统,提供了一套标准化的技能操作接口。
## 架构设计
### 系统组成
- **HFGenericSkillSystem** - 通用技能系统(继承自 `AbsHFSkillSystem<HFGenericSkillModel>`
- **HFGenericSkillModel** - 通用技能模型(继承自 `HFAbsModel`
- **HFEvent_OnSkillUsed** - 技能使用事件
### 核心功能
#### 1. 技能管理
```csharp
// 添加技能
skillSystem.AddSkill(skillId);
// 移除技能
skillSystem.RemoveSkill(skillId);
// 检查技能是否就绪
bool isReady = skillSystem.IsSkillReady(skillId);
// 获取技能冷却时间
float cooldown = skillSystem.GetSkillCooldown(skillId);
```
#### 2. 技能使用
```csharp
// 使用技能(指定目标)
skillSystem.UseSkill(skillId, targetEntity);
// 使用技能(指定位置)
skillSystem.UseSkillAtPosition(skillId, worldPosition);
```
#### 3. 冷却管理
```csharp
// 检查是否有技能在冷却
bool hasCooldown = skillSystem.HasSkillInCooldown();
// 清除所有技能冷却
skillSystem.ClearAllCooldowns();
```
## 使用场景
### 角色实体
`HFEntity_Role` 中,技能系统被自动添加:
```csharp
AddSystem<HFGenericSkillSystem>(); // 替代原来的 HFSkillSystem
```
### 敌人实体
`HFEntity_Enemy``HFEntity_Boss` 中,技能系统被自动添加:
```csharp
AddSystem<HFGenericSkillSystem>(); // 替代原来的 HFSkillSystem
```
## 技能配置
### 技能配置结构
```csharp
public struct stSkillConfig
{
public int Id; // 技能ID
public string Name; // 技能名称
public float Cooldown; // 冷却时间
public float Range; // 作用范围
public float Damage; // 伤害值
public string Description; // 描述
}
```
### 技能加载
系统会根据实体配置自动加载对应的技能:
```csharp
// 从 EntityConfig 表加载技能
var config = HFFunCatalogue.NDB.ds_EntityConfig_Sheet_TableData_1FC4334A.ReadTable(ownerEntity.EntityId);
if (config != null && config.SkillID > 0)
{
AddSkill(config.SkillID);
}
```
## 事件系统
### 技能使用事件
当技能被使用时,系统会触发 `HFEvent_OnSkillUsed` 事件:
```csharp
public class HFEvent_OnSkillUsed : AbsHFEventArg<enHFCTCEvent>
{
public HFAbsEntity Caster { get; set; } // 施法者
public int SkillId { get; set; } // 技能ID
public HFAbsEntity Target { get; set; } // 目标
public Vector3 Position { get; set; } // 位置(可选)
}
```
## 扩展接口
### Buff 管理
```csharp
// 添加 Buff
skillSystem.AddBuff(buffTemplate);
// 移除 Buff
skillSystem.RemoveBuff(buffId);
```
## 最佳实践
1. **统一接口**:角色和怪物使用相同的技能系统接口
2. **自动配置**:技能配置根据实体类型自动加载
3. **冷却管理**:内置技能冷却机制
4. **事件驱动**:通过事件系统处理技能效果
5. **类型安全**使用强类型的技能ID和配置
## 注意事项
1. 技能释放器的具体实现需要根据游戏需求进行定制
2. 技能效果逻辑应在事件处理器中实现
3. 确保技能 ID 在项目中唯一
4. 合理设置技能冷却时间以平衡游戏体验
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [技能系统入口](README.md) | 本文件是技能系统模块的子文档 |
| [实体系统](../实体系统/README.md) | 技能系统是实体系统的子系统 |
| [事件系统](../事件系统/README.md) | 技能使用事件依赖事件系统 |
| [数据/配置框架](../数据配置框架.md) | 技能配置加载依赖配置框架 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,111 @@
# 数据 / 配置框架
## 分类
- 核心框架
## 目的
管理 XLSX 配置表源、解析、生成运行时数据和读取 API。
---
## 核心路径
- 配置源:`XLSX_SRC/CN/``XLSX_SRC/EN/`
- 生成设置:`ProjectSettings/ZYF/ZYXlsx/ZYXlsxProjectSettings.asset`
- Resolve Asset`Assets/Editor/XLSX_SRC/Resolve/ZYXlsx/`
- 输出数据:`Assets/AutoBuildAsset/{Editor,Hotfix}/AutoFrame/Google.Protobuf/XLSX_SRC/<语言>/`
- 输出代码:`Assets/AutoBuildScript/{Editor,General,Hotfix}/AutoFrame/Google.Protobuf/XLSX_Script/`
---
## 常见配置表
| 表名 | 用途 |
|---|---|
| `Constants.xlsx` | 常量配置 |
| `EntityConfig.xlsx` | 实体配置 |
| `Gift.xlsx` | 礼包/奖励配置 |
| `Resource.xlsx` | 资源/道具配置 |
| `SkillGroup.xlsx` | 技能组配置 |
| `SkillReleaser.xlsx` | 技能释放器配置 |
| `SkillTrigger.xlsx` | 技能触发器配置 |
| `SkillUp.xlsx` | 技能升级配置 |
| `Sounds.xlsx` | 音效配置 |
| `Wave.xlsx` | 波次配置(业务示例) |
---
## 运行时加载调用链
```text
HFGameStart.LoadDB()
-> Hotfix_ZYXlsx_GoogleProtobuf_Helper.instance.LoadAllXlsx(OnLoadDbFinish)
-> 遍历 enZYXlsxSheet
-> GetExistsDataLang / GetSheetMemoryPackBufferFilePath
-> HFFunCatalogue.Asset.LoadDBHelper(path.GetHashCode(), path, callback)
-> 缓存 sheet bytes 到 mDicLangSheetBytes
```
---
## 运行时读取 API
```csharp
// 按主键读取单条
HFFunCatalogue.NDB.ds_xxx.ReadTableByPK(pk);
// 读取全表
HFFunCatalogue.NDB.ds_xxx.ReadTableList();
// 读取行列式数据
HFFunCatalogue.NDB.ds_xxx.ReadTableDeterminant();
```
---
## 使用规则
- 改表后必须重新生成。
- 配置读取必须在 `LoadAllXlsx` 完成后。
- 主键/全表/常量分别用对应 API。
- 禁止高频全表读取。
- id 优先来自表/协议/常量,禁止魔法数字。
- 高频配置在 manager 初始化或窗口打开时缓存。
- 多语言字段需补齐。
---
## 变更清单
1. 修改源表。
2. 确认 settings / resolve 配置。
3. 重新生成。
4. 检查输出。
5. 编译验证。
6. 运行时验证。
7. 更新文档。
---
## 相关模块
- [实体系统](实体系统/README.md)
- [技能系统](技能系统/README.md)
- [资源管理](资源管理/README.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/实体系统/README.md](实体系统/README.md) | 实体配置来自配置表 |
| [modules/frameworks/技能系统/README.md](技能系统/README.md) | 技能配置来自配置表 |
| [modules/frameworks/资源管理/README.md](资源管理/README.md) | 配置资源加载依赖资源管理 |
| [rules/10-架构说明.md](../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,122 @@
# 相机系统
## 分类
- 核心框架
## 目的
管理多相机注册、相机堆栈排序、渲染顺序控制,为 UI、场景、特效等提供分层渲染能力。
---
## 核心类
| 类 | 职责 |
|---|---|
| `HFCameraManager` | 相机管理器单例,维护相机列表和主相机引用 |
| `HFMainCamera` | 主相机,管理 CameraStack |
| `AbsHFCameraItem` | 相机项抽象基类,子类实现具体相机逻辑 |
---
## 创建自定义相机
继承 `AbsHFCameraItem`,实现 `CameraItem``Weight` 属性:
```csharp
public class UIOverlayCamera : AbsHFCameraItem
{
[SerializeField] private Camera uiCamera;
[SerializeField] private int weight = 10;
public override Camera CameraItem => uiCamera;
public override int Weight => weight;
protected override void OnInitialize()
{
base.OnInitialize();
uiCamera.clearFlags = CameraClearFlags.Depth;
uiCamera.depth = 100;
}
}
```
---
## 相机堆栈架构
```text
HFMainCamera (主相机)
├── CameraStack (相机堆栈)
│ ├── CameraItem1 (Weight=10)
│ ├── CameraItem2 (Weight=20)
│ └── CameraItem3 (Weight=5)
└── 按 Weight 排序 → 渲染顺序控制
```
---
## 管理相机
```csharp
public class CameraController : MonoBehaviour
{
void Start()
{
HFCameraManager cameraMgr = HFCameraManager.Instance;
UIOverlayCamera uiCamera = GetComponent<UIOverlayCamera>();
cameraMgr.AddCamera(uiCamera);
Camera mainCam = cameraMgr.mainCamera.MainCamera;
}
void OnDestroy()
{
UIOverlayCamera uiCamera = GetComponent<UIOverlayCamera>();
HFCameraManager.Instance.RemoveCamera(uiCamera);
}
}
```
---
## 常用 API
| 方法 | 说明 |
|-----|------|
| `AddCamera(AbsHFCameraItem)` | 添加相机到堆栈 |
| `RemoveCamera(AbsHFCameraItem)` | 从堆栈移除相机 |
| `GetCamera(string cameraName)` | 获取指定名称相机 |
---
## 使用规则
- 相机项必须继承 `AbsHFCameraItem`
- `Weight` 决定堆栈内渲染顺序,值越大越靠后渲染。
- 添加和移除相机需成对出现,避免残留。
- UI 相机通常使用 `CameraClearFlags.Depth` 和较高 depth。
---
## 相关模块
- [UI 窗口系统](../UI窗口系统/README.md)
- [资源管理](../资源管理/README.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/UI窗口系统/README.md](../UI窗口系统/README.md) | UI 窗口系统常与相机系统协作 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,101 @@
# 相机系统使用指南
## 一、创建自定义相机
### 1.1 继承 AbsHFCameraItem
```csharp
public class UIOverlayCamera : AbsHFCameraItem
{
[SerializeField] private Camera uiCamera;
[SerializeField] private int weight = 10;
public override Camera CameraItem => uiCamera;
public override int Weight => weight;
protected override void OnInitialize()
{
base.OnInitialize();
// 自定义初始化逻辑
uiCamera.clearFlags = CameraClearFlags.Depth;
uiCamera.depth = 100;
}
}
```
---
## 二、管理相机堆栈
### 2.1 使用相机管理器
```csharp
public class CameraController : MonoBehaviour
{
void Start()
{
// 获取相机管理器
HFCameraManager cameraMgr = HFCameraManager.Instance;
// 添加自定义相机
UIOverlayCamera uiCamera = GetComponent<UIOverlayCamera>();
cameraMgr.AddCamera(uiCamera);
// 获取主相机
Camera mainCam = cameraMgr.mainCamera.MainCamera;
}
void OnDestroy()
{
UIOverlayCamera uiCamera = GetComponent<UIOverlayCamera>();
HFCameraManager.Instance.RemoveCamera(uiCamera);
}
}
```
### 2.2 相机堆栈架构
```
相机层级架构:
HFMainCamera (主相机)
├── CameraStack (相机堆栈)
│ ├── CameraItem1 (Weight=10)
│ ├── CameraItem2 (Weight=20)
│ └── CameraItem3 (Weight=5)
└── 按 Weight 排序 → 渲染顺序控制
```
---
## 三、相机管理 API
### 3.1 常用方法
| 方法 | 说明 |
|-----|------|
| `AddCamera()` | 添加相机到堆栈 |
| `RemoveCamera()` | 从堆栈移除相机 |
| `GetCamera()` | 获取指定相机 |
---
**版本**: v1.0
**生效日期**: 2026年
**适用范围**: GiftGameX 项目业务开发人员
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [相机系统入口](README.md) | 本文件是相机系统模块的子文档 |
| [UI 窗口系统](../UI窗口系统/README.md) | UI 窗口系统常与相机系统协作 |
| [资源管理](../资源管理/README.md) | 相机资源加载依赖资源管理 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,49 @@
# 第三方插件
## 分类
- 项目框架
## 目的
记录第三方插件和 embedded packages 的用途、边界和修改规则。
## 已知线索
- 插件目录:`Assets/ThirdPlugins/`
- Package 目录:`Packages/`
- 已识别插件包括 HybridCLR、Spine、DOTween、SuperScrollView、Astar、UnityWebSocket、SoundManagerPro、SRDebugger 等。
## 梳理任务
- [ ] 梳理主要插件列表和用途。
- [ ] 标记哪些插件是运行时依赖,哪些是编辑器依赖。
- [ ] 梳理插件是否有项目本地修改。
- [ ] 梳理升级插件的风险和验证步骤。
- [ ] 记录禁止直接修改 vendor 的规则例外。
## 待确认
- 哪些插件来自 Asset Store哪些来自 package。
- 是否有插件被项目深度改造。
- 哪些插件参与 hotfix 或 WebGL 构建。
## 相关模块
- [通用框架](通用框架/README.md)
- [编辑器工具索引](../模块索引.md#编辑器工具)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../模块索引.md) | 编辑器工具索引 |
| [modules/frameworks/通用框架/README.md](通用框架/README.md) | 通用框架 |
| [rules/10-架构说明.md](../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../rules/20-开发规则.md) | 开发规则与框架/生成规则互相引用 |
| [rules/30-模块登记规则.md](../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| TODO补充依赖或使用本框架的模块文档 | 如依赖本框架的功能模块 |

View File

@@ -0,0 +1,54 @@
# 编辑器工具
## 分类
- 编辑器工具
## 目的
汇总 Unity Editor 内使用的 StrayFog 相关编辑器扩展和项目编辑器资源。
---
## SF Editor
- **路径**`Assets/StrayFog/Editor/`
- **程序集**`SFEditor`
- **职责**
- StrayFog 编辑器扩展
- 打包、UGUI、SpriteAtlas、表格路径选择等编辑器工具
---
## Project Editor Assets
- **路径**`Assets/Editor/`
- **职责**
- 项目编辑器资源与脚本
- 引导菜单、编辑器插件
---
## 使用规则
- 编辑器工具可以读取/生成核心运行时代码,但运行时代码不要反向依赖 Editor 程序集。
- 新增 Editor 工具时,优先在 `Assets/StrayFog/Editor/``Assets/Editor/` 下按功能分类存放。
---
## 相关模块
- [通用框架](../通用框架/README.md)
- [资源管理](../资源管理/README.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 编辑器工具层的架构说明 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,176 @@
using UnityEditor;
using UnityEngine;
using System.IO;
[InitializeOnLoad]
public static class ExternalCommandListener
{
private static FileSystemWatcher watcher;
private static string triggerFilePath;
private static bool isInitialized = false;
static ExternalCommandListener()
{
EditorApplication.update += InitializeOnFirstUpdate;
}
private static void InitializeOnFirstUpdate()
{
if (!isInitialized)
{
isInitialized = true;
EditorApplication.update -= InitializeOnFirstUpdate;
SetupFileWatcher();
Debug.Log("ExternalCommandListener started");
}
}
private static void SetupFileWatcher()
{
triggerFilePath = Path.Combine(Application.dataPath, "..", "Trigger_Command.txt");
string watchFolder = Path.GetDirectoryName(triggerFilePath);
if (watcher != null) watcher.Dispose();
watcher = new FileSystemWatcher(watchFolder, "Trigger_Command.txt");
watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite;
watcher.Created += OnFileChanged;
watcher.Changed += OnFileChanged;
watcher.EnableRaisingEvents = true;
}
private static void OnFileChanged(object sender, FileSystemEventArgs e)
{
if (e.ChangeType != WatcherChangeTypes.Created && e.ChangeType != WatcherChangeTypes.Changed)
return;
EditorApplication.delayCall += ExecuteCommandFromFile;
}
private static void ExecuteCommandFromFile()
{
try
{
if (!File.Exists(triggerFilePath)) return;
string content = File.ReadAllText(triggerFilePath).Trim();
File.Delete(triggerFilePath);
ParseAndExecute(content);
}
catch (System.Exception ex)
{
Debug.LogError("Execute failed: " + ex.Message);
}
}
private static void ParseAndExecute(string content)
{
content = content.Trim().Trim('"', '\'');
string[] parts = content.Split(':');
if (parts.Length == 0) return;
string command = parts[0].Trim();
string[] args = parts.Length > 1 ? parts[1..] : new string[0];
ExecuteCommand(command, args);
}
private static void ExecuteCommand(string command, string[] args)
{
switch (command)
{
case "ReadJson":
ExecuteReadJson(args);
break;
case "GenerateUIPrefab":
ExecuteGenerateUIPrefab(args);
break;
case "ExportPrefabToJson":
ExecuteExportPrefabToJson(args);
break;
default:
Debug.LogWarning("Unknown command: " + command);
break;
}
}
private static void ExecuteReadJson(string[] args)
{
try
{
var method = typeof(RemoteCommand).GetMethod("ReadJson",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
null,
new[] { typeof(string[]) },
null);
if (method != null)
{
method.Invoke(null, new object[] { args });
Debug.Log("Executed: ReadJson(" + string.Join(", ", args) + ")");
}
else
{
Debug.LogError("RemoteCommand.ReadJson not found");
}
}
catch (System.Exception ex)
{
Debug.LogError("ReadJson failed: " + ex.Message);
}
}
private static void ExecuteGenerateUIPrefab(string[] args)
{
try
{
var method = typeof(RemoteCommand).GetMethod("GenerateUIPrefab",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
null,
new[] { typeof(string[]) },
null);
if (method != null)
{
method.Invoke(null, new object[] { args });
Debug.Log("Executed: GenerateUIPrefab(" + string.Join(", ", args) + ")");
}
else
{
Debug.LogError("RemoteCommand.GenerateUIPrefab not found");
}
}
catch (System.Exception ex)
{
Debug.LogError("GenerateUIPrefab failed: " + ex.Message);
}
}
private static void ExecuteExportPrefabToJson(string[] args)
{
try
{
var method = typeof(RemoteCommand).GetMethod("ExportPrefabToJson",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
null,
new[] { typeof(string[]) },
null);
if (method != null)
{
method.Invoke(null, new object[] { args });
Debug.Log("Executed: ExportPrefabToJson(" + string.Join(", ", args) + ")");
}
else
{
Debug.LogError("RemoteCommand.ExportPrefabToJson not found");
}
}
catch (System.Exception ex)
{
Debug.LogError("ExportPrefabToJson failed: " + ex.Message);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 93f1f8f942aa53347a7e58bb4c561893
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
public static class RemoteCommand
{
public static void ReadJson(string[] args)
{
SFP.Error("收到参数: " + string.Join(", ", args));
}
public static void GenerateUIPrefab(string[] args)
{
if (args.Length == 0)
{
SFP.Error("GenerateUIPrefab: 需要提供JSON文件路径参数");
return;
}
string jsonPath = args[0];
string prefabPath = args.Length > 1 ? args[1] : null;
SFP.Error("GenerateUIPrefab: 开始生成UI, JSON路径: " + jsonPath);
UIPrefabGenerator.GeneratePrefabFromJson(jsonPath, prefabPath);
}
public static void ExportPrefabToJson(string[] args)
{
if (args.Length == 0)
{
SFP.Error("ExportPrefabToJson: 需要提供预制体文件路径参数");
return;
}
string prefabPath = args[0];
string jsonPath = args.Length > 1 ? args[1] : null;
SFP.Error("ExportPrefabToJson: 开始导出JSON, 预制体路径: " + prefabPath);
UIPrefabToJson.ExportPrefabToJson(prefabPath, jsonPath);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 359ded64689f3f64c87c561d88cead9d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,584 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using TMPro;
public static class UIPrefabGenerator
{
public static void GeneratePrefabFromJson(string jsonPath, string prefabPath = null)
{
if (!File.Exists(jsonPath))
{
Debug.LogError("JSON file not found: " + jsonPath);
return;
}
string jsonContent = File.ReadAllText(jsonPath);
UILayoutData layoutData = JsonUtility.FromJson<UILayoutData>(jsonContent);
if (layoutData == null)
{
Debug.LogError("Failed to parse JSON file: " + jsonPath);
return;
}
if (string.IsNullOrEmpty(prefabPath))
{
prefabPath = "Assets/Game/GameHFResource/CN/UIWindows/" + layoutData.name + "/" + layoutData.name + ".prefab";
}
GameObject root = new GameObject(layoutData.name);
root.layer = layoutData.layer;
Canvas canvas = root.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
if (layoutData.canvas != null)
{
canvas.pixelPerfect = layoutData.canvas.pixelPerfect;
}
root.AddComponent<CanvasScaler>();
root.AddComponent<GraphicRaycaster>();
RectTransform rootRect = root.GetComponent<RectTransform>();
SetRectTransform(rootRect, layoutData.rectTransform);
foreach (var childData in layoutData.children)
{
CreateUIElement(childData, root.transform);
}
CreatePrefab(root, prefabPath);
GameObject.DestroyImmediate(root);
AssetDatabase.Refresh();
Debug.Log("UI Prefab generated successfully: " + prefabPath);
}
private static void CreateUIElement(UIElementData data, Transform parent)
{
GameObject obj = new GameObject(data.name);
obj.transform.SetParent(parent, false);
RectTransform rect = obj.AddComponent<RectTransform>();
SetRectTransform(rect, data.rectTransform);
switch (data.type)
{
case "Image":
case "SFUI_Image":
CreateImage(obj, data.image);
break;
case "Button":
case "SFUI_Button":
CreateButton(obj, data.button, data.image);
break;
case "TextMeshProUGUI":
case "SFUI_TextMeshProUGUI":
CreateTextMeshPro(obj, data.text);
break;
case "RectTransform":
case "SFUI_Viewport":
case "SFUI_Content":
CreateRectTransform(obj, data.layoutGroup);
break;
case "ScrollRect":
case "SFUI_ScrollRect":
CreateScrollRect(obj, data.scrollRect);
break;
case "GridLayoutGroup":
CreateGridLayoutGroup(obj, data.layoutGroup);
break;
case "VerticalLayoutGroup":
CreateVerticalLayoutGroup(obj, data.layoutGroup);
break;
case "HorizontalLayoutGroup":
CreateHorizontalLayoutGroup(obj, data.layoutGroup);
break;
case "Toggle":
case "SFUI_Toggle":
CreateToggle(obj, data.toggle);
break;
case "Slider":
case "SFUI_Slider":
CreateSlider(obj, data.slider);
break;
case "InputField":
case "SFUI_InputField":
CreateInputField(obj, data.inputField);
break;
case "CanvasScaler":
case "GraphicRaycaster":
break;
case "SFUI_ListViewItem":
CreateListViewItemPlaceholder(obj);
break;
case "Canvas":
case "SFUI_Window":
break;
default:
break;
}
if (data.children != null)
{
foreach (var childData in data.children)
{
CreateUIElement(childData, obj.transform);
}
}
}
private static void SetRectTransform(RectTransform rect, RectTransformData data)
{
if (data == null) return;
rect.anchorMin = new Vector2(data.anchorMin[0], data.anchorMin[1]);
rect.anchorMax = new Vector2(data.anchorMax[0], data.anchorMax[1]);
rect.anchoredPosition3D = new Vector3(data.position[0], data.position[1], data.position[2]);
rect.sizeDelta = new Vector2(data.sizeDelta[0], data.sizeDelta[1]);
rect.pivot = new Vector2(data.pivot[0], data.pivot[1]);
if (data.localScale != null && data.localScale.Length >= 3)
{
rect.localScale = new Vector3(data.localScale[0], data.localScale[1], data.localScale[2]);
}
if (data.localRotation != null && data.localRotation.Length >= 3)
{
rect.localEulerAngles = new Vector3(data.localRotation[0], data.localRotation[1], data.localRotation[2]);
}
}
private static void CreateImage(GameObject obj, ImageData data)
{
if (data == null) return;
Image image = obj.AddComponent<Image>();
if (!string.IsNullOrEmpty(data.sprite))
{
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(data.sprite);
if (sprite != null)
{
image.sprite = sprite;
}
}
try
{
image.type = (Image.Type)Enum.Parse(typeof(Image.Type), data.type);
}
catch
{
image.type = Image.Type.Simple;
}
image.color = new Color(data.color[0], data.color[1], data.color[2], data.color[3]);
image.raycastTarget = data.raycastTarget;
image.preserveAspect = data.preserveAspect;
}
private static void CreateButton(GameObject obj, ButtonData buttonData, ImageData imageData)
{
Button button = obj.AddComponent<Button>();
if (buttonData != null)
{
button.interactable = buttonData.interactable;
try
{
button.transition = (Selectable.Transition)Enum.Parse(typeof(Selectable.Transition), buttonData.transition);
}
catch
{
button.transition = Selectable.Transition.ColorTint;
}
}
if (imageData != null)
{
CreateImage(obj, imageData);
}
}
private static void CreateTextMeshPro(GameObject obj, TextData data)
{
if (data == null) return;
TextMeshProUGUI text = obj.AddComponent<TextMeshProUGUI>();
text.text = data.text;
text.fontSize = data.fontSize;
text.color = new Color(data.color[0], data.color[1], data.color[2], data.color[3]);
if (!string.IsNullOrEmpty(data.font))
{
TMP_FontAsset fontAsset = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(data.font);
if (fontAsset != null)
{
text.font = fontAsset;
}
}
if (!string.IsNullOrEmpty(data.materialPreset))
{
Material material = AssetDatabase.LoadAssetAtPath<Material>(data.materialPreset);
if (material != null)
{
text.material = material;
}
}
try
{
text.alignment = (TextAlignmentOptions)Enum.Parse(typeof(TextAlignmentOptions), data.alignment);
}
catch
{
text.alignment = TextAlignmentOptions.Center;
}
try
{
text.verticalAlignment = (VerticalAlignmentOptions)Enum.Parse(typeof(VerticalAlignmentOptions), data.verticalAlignment);
}
catch
{
text.verticalAlignment = VerticalAlignmentOptions.Middle;
}
text.richText = data.richText;
try
{
text.overflowMode = (TextOverflowModes)Enum.Parse(typeof(TextOverflowModes), data.overflowMode);
}
catch
{
text.overflowMode = TextOverflowModes.Truncate;
}
text.enableWordWrapping = data.enableWordWrapping;
}
private static void CreateRectTransform(GameObject obj, LayoutGroupData layoutData)
{
if (layoutData != null)
{
switch (layoutData.type)
{
case "GridLayoutGroup":
CreateGridLayoutGroup(obj, layoutData);
break;
case "VerticalLayoutGroup":
CreateVerticalLayoutGroup(obj, layoutData);
break;
case "HorizontalLayoutGroup":
CreateHorizontalLayoutGroup(obj, layoutData);
break;
}
}
}
private static void CreateGridLayoutGroup(GameObject obj, LayoutGroupData layoutData)
{
if (layoutData == null) return;
GridLayoutGroup grid = obj.AddComponent<GridLayoutGroup>();
grid.cellSize = new Vector2(layoutData.cellSize[0], layoutData.cellSize[1]);
grid.spacing = new Vector2(layoutData.spacing[0], layoutData.spacing[1]);
grid.constraint = (GridLayoutGroup.Constraint)layoutData.constraint;
try
{
grid.childAlignment = (TextAnchor)Enum.Parse(typeof(TextAnchor), layoutData.childAlignment);
}
catch
{
grid.childAlignment = TextAnchor.UpperLeft;
}
grid.padding = new RectOffset(layoutData.padding[0], layoutData.padding[1], layoutData.padding[2], layoutData.padding[3]);
}
private static void CreateVerticalLayoutGroup(GameObject obj, LayoutGroupData layoutData)
{
if (layoutData == null) return;
VerticalLayoutGroup vertical = obj.AddComponent<VerticalLayoutGroup>();
vertical.spacing = layoutData.spacing[1];
try
{
vertical.childAlignment = (TextAnchor)Enum.Parse(typeof(TextAnchor), layoutData.childAlignment);
}
catch
{
vertical.childAlignment = TextAnchor.UpperLeft;
}
vertical.padding = new RectOffset(layoutData.padding[0], layoutData.padding[1], layoutData.padding[2], layoutData.padding[3]);
}
private static void CreateHorizontalLayoutGroup(GameObject obj, LayoutGroupData layoutData)
{
if (layoutData == null) return;
HorizontalLayoutGroup horizontal = obj.AddComponent<HorizontalLayoutGroup>();
horizontal.spacing = layoutData.spacing[0];
try
{
horizontal.childAlignment = (TextAnchor)Enum.Parse(typeof(TextAnchor), layoutData.childAlignment);
}
catch
{
horizontal.childAlignment = TextAnchor.UpperLeft;
}
horizontal.padding = new RectOffset(layoutData.padding[0], layoutData.padding[1], layoutData.padding[2], layoutData.padding[3]);
}
private static void CreateScrollRect(GameObject obj, ScrollRectData data)
{
if (data == null) return;
ScrollRect scrollRect = obj.AddComponent<ScrollRect>();
scrollRect.horizontal = data.horizontal;
scrollRect.vertical = data.vertical;
try
{
scrollRect.movementType = (ScrollRect.MovementType)Enum.Parse(typeof(ScrollRect.MovementType), data.movementType);
}
catch
{
scrollRect.movementType = ScrollRect.MovementType.Clamped;
}
}
private static void CreateToggle(GameObject obj, ToggleData data)
{
if (data == null) return;
Toggle toggle = obj.AddComponent<Toggle>();
toggle.isOn = data.isOn;
try
{
toggle.toggleTransition = (Toggle.ToggleTransition)Enum.Parse(typeof(Toggle.ToggleTransition), data.toggleTransition);
}
catch
{
toggle.toggleTransition = Toggle.ToggleTransition.Fade;
}
try
{
toggle.transition = (Selectable.Transition)Enum.Parse(typeof(Selectable.Transition), data.transition);
}
catch
{
toggle.transition = Selectable.Transition.ColorTint;
}
}
private static void CreateSlider(GameObject obj, SliderData data)
{
if (data == null) return;
Slider slider = obj.AddComponent<Slider>();
slider.minValue = data.minValue;
slider.maxValue = data.maxValue;
slider.value = data.value;
slider.wholeNumbers = data.wholeNumbers;
try
{
slider.direction = (Slider.Direction)Enum.Parse(typeof(Slider.Direction), data.direction);
}
catch
{
slider.direction = Slider.Direction.LeftToRight;
}
}
private static void CreateInputField(GameObject obj, InputFieldData data)
{
if (data == null) return;
InputField inputField = obj.AddComponent<InputField>();
inputField.text = data.text;
inputField.characterLimit = data.characterLimit;
try
{
inputField.contentType = (InputField.ContentType)Enum.Parse(typeof(InputField.ContentType), data.contentType);
}
catch
{
inputField.contentType = InputField.ContentType.Standard;
}
try
{
inputField.inputType = (InputField.InputType)Enum.Parse(typeof(InputField.InputType), data.inputType);
}
catch
{
inputField.inputType = InputField.InputType.Standard;
}
}
private static void CreateListViewItemPlaceholder(GameObject obj)
{
}
private static void CreatePrefab(GameObject root, string path)
{
string directory = Path.GetDirectoryName(path);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
PrefabUtility.SaveAsPrefabAsset(root, path);
}
}
[Serializable]
public class UILayoutData
{
public string name;
public string type;
public int layer;
public RectTransformData rectTransform;
public CanvasData canvas;
public List<UIElementData> children;
}
[Serializable]
public class UIElementData
{
public string name;
public string type;
public RectTransformData rectTransform;
public ImageData image;
public ButtonData button;
public TextData text;
public LayoutGroupData layoutGroup;
public ScrollRectData scrollRect;
public ToggleData toggle;
public SliderData slider;
public InputFieldData inputField;
public List<CustomComponentData> customComponents;
public List<UIElementData> children;
}
[Serializable]
public class RectTransformData
{
public float[] anchorMin;
public float[] anchorMax;
public float[] position;
public float[] sizeDelta;
public float[] pivot;
public float[] localScale;
public float[] localRotation;
}
[Serializable]
public class CanvasData
{
public string renderMode;
public bool pixelPerfect;
}
[Serializable]
public class ImageData
{
public string sprite;
public string type;
public float[] color;
public bool raycastTarget;
public bool preserveAspect;
}
[Serializable]
public class ButtonData
{
public bool interactable;
public string transition;
public string navigationMode;
}
[Serializable]
public class TextData
{
public string text;
public float fontSize;
public string font;
public string materialPreset;
public string alignment;
public string verticalAlignment;
public float[] color;
public bool richText;
public string overflowMode;
public bool enableWordWrapping;
}
[Serializable]
public class LayoutGroupData
{
public string type;
public float[] cellSize;
public float[] spacing;
public int constraint;
public string childAlignment;
public int[] padding;
}
[Serializable]
public class ScrollRectData
{
public bool horizontal;
public bool vertical;
public string movementType;
public string horizontalScrollbarVisibility;
public string verticalScrollbarVisibility;
}
[Serializable]
public class ToggleData
{
public bool isOn;
public string toggleTransition;
public string transition;
}
[Serializable]
public class SliderData
{
public float minValue;
public float maxValue;
public float value;
public bool wholeNumbers;
public string direction;
}
[Serializable]
public class InputFieldData
{
public string text;
public string placeholder;
public int characterLimit;
public string contentType;
public string inputType;
}
[Serializable]
public class CustomComponentData
{
public string typeName;
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 31deea0e57bec2f4893548b0a41853cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,366 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using TMPro;
public static class UIPrefabToJson
{
public static void ExportPrefabToJson(string prefabPath, string jsonPath = null)
{
if (!AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath))
{
Debug.LogError("Prefab not found: " + prefabPath);
return;
}
if (string.IsNullOrEmpty(jsonPath))
{
jsonPath = prefabPath.Replace(".prefab", ".json");
}
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
UILayoutData layoutData = ConvertPrefabToLayoutData(prefab);
string jsonContent = JsonUtility.ToJson(layoutData, true);
string fullPath = Path.Combine(Application.dataPath, "..", jsonPath);
string directory = Path.GetDirectoryName(fullPath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
File.WriteAllText(fullPath, jsonContent);
AssetDatabase.Refresh();
Debug.Log("JSON exported successfully: " + jsonPath);
}
private static UILayoutData ConvertPrefabToLayoutData(GameObject prefab)
{
UILayoutData data = new UILayoutData();
data.name = prefab.name;
data.type = "Canvas";
data.layer = prefab.layer;
Canvas canvas = prefab.GetComponent<Canvas>();
if (canvas != null)
{
data.canvas = new CanvasData();
data.canvas.renderMode = canvas.renderMode.ToString();
data.canvas.pixelPerfect = canvas.pixelPerfect;
}
RectTransform rectTransform = prefab.GetComponent<RectTransform>();
if (rectTransform != null)
{
data.rectTransform = ConvertRectTransform(rectTransform);
}
data.children = new List<UIElementData>();
foreach (Transform child in prefab.transform)
{
UIElementData childData = ConvertTransformToElement(child);
if (childData != null)
{
data.children.Add(childData);
}
}
return data;
}
private static UIElementData ConvertTransformToElement(Transform transform)
{
UIElementData data = new UIElementData();
data.name = transform.name;
RectTransform rectTransform = transform.GetComponent<RectTransform>();
if (rectTransform != null)
{
data.rectTransform = ConvertRectTransform(rectTransform);
}
Component[] components = transform.GetComponents<Component>();
List<string> componentTypes = new List<string>();
foreach (Component comp in components)
{
if (comp == null) continue;
string compTypeName = comp.GetType().Name;
componentTypes.Add(compTypeName);
switch (compTypeName)
{
case "Image":
case "SFUI_Image":
data.type = compTypeName;
data.image = ConvertImage((Image)comp);
break;
case "Button":
case "SFUI_Button":
data.type = compTypeName;
data.button = ConvertButton((Button)comp);
break;
case "TextMeshProUGUI":
case "SFUI_TextMeshProUGUI":
data.type = compTypeName;
data.text = ConvertText((TextMeshProUGUI)comp);
break;
case "ScrollRect":
case "SFUI_ScrollRect":
data.type = compTypeName;
data.scrollRect = ConvertScrollRect((ScrollRect)comp);
break;
case "GridLayoutGroup":
data.type = compTypeName;
data.layoutGroup = ConvertGridLayoutGroup((GridLayoutGroup)comp);
break;
case "VerticalLayoutGroup":
data.type = compTypeName;
data.layoutGroup = ConvertVerticalLayoutGroup((VerticalLayoutGroup)comp);
break;
case "HorizontalLayoutGroup":
data.type = compTypeName;
data.layoutGroup = ConvertHorizontalLayoutGroup((HorizontalLayoutGroup)comp);
break;
case "Toggle":
case "SFUI_Toggle":
data.type = compTypeName;
data.toggle = ConvertToggle((Toggle)comp);
break;
case "Slider":
case "SFUI_Slider":
data.type = compTypeName;
data.slider = ConvertSlider((Slider)comp);
break;
case "InputField":
case "SFUI_InputField":
data.type = compTypeName;
data.inputField = ConvertInputField((InputField)comp);
break;
case "RectTransform":
if (string.IsNullOrEmpty(data.type))
data.type = compTypeName;
break;
case "Canvas":
if (string.IsNullOrEmpty(data.type))
data.type = compTypeName;
break;
case "CanvasScaler":
data.type = compTypeName;
break;
case "GraphicRaycaster":
data.type = compTypeName;
break;
default:
if (compTypeName != "Transform" && compTypeName != "RectTransform")
{
if (data.customComponents == null)
data.customComponents = new List<CustomComponentData>();
data.customComponents.Add(ConvertCustomComponent(comp));
}
break;
}
}
if (string.IsNullOrEmpty(data.type))
{
data.type = "Transform";
}
if (transform.childCount > 0)
{
data.children = new List<UIElementData>();
foreach (Transform child in transform)
{
UIElementData childData = ConvertTransformToElement(child);
if (childData != null)
{
data.children.Add(childData);
}
}
}
return data;
}
private static RectTransformData ConvertRectTransform(RectTransform rect)
{
RectTransformData data = new RectTransformData();
data.anchorMin = new float[] { rect.anchorMin.x, rect.anchorMin.y };
data.anchorMax = new float[] { rect.anchorMax.x, rect.anchorMax.y };
data.position = new float[] { rect.anchoredPosition3D.x, rect.anchoredPosition3D.y, rect.anchoredPosition3D.z };
data.sizeDelta = new float[] { rect.sizeDelta.x, rect.sizeDelta.y };
data.pivot = new float[] { rect.pivot.x, rect.pivot.y };
data.localScale = new float[] { rect.localScale.x, rect.localScale.y, rect.localScale.z };
data.localRotation = new float[] { rect.localEulerAngles.x, rect.localEulerAngles.y, rect.localEulerAngles.z };
return data;
}
private static ImageData ConvertImage(Image image)
{
ImageData data = new ImageData();
data.type = image.type.ToString();
data.color = new float[] { image.color.r, image.color.g, image.color.b, image.color.a };
data.raycastTarget = image.raycastTarget;
data.preserveAspect = image.preserveAspect;
if (image.sprite != null)
{
string spritePath = AssetDatabase.GetAssetPath(image.sprite);
if (!string.IsNullOrEmpty(spritePath))
{
data.sprite = spritePath;
}
}
return data;
}
private static ButtonData ConvertButton(Button button)
{
ButtonData data = new ButtonData();
data.interactable = button.interactable;
data.transition = button.transition.ToString();
data.navigationMode = button.navigation.mode.ToString();
return data;
}
private static TextData ConvertText(TextMeshProUGUI text)
{
TextData data = new TextData();
data.text = text.text;
data.fontSize = text.fontSize;
data.color = new float[] { text.color.r, text.color.g, text.color.b, text.color.a };
data.alignment = text.alignment.ToString();
data.verticalAlignment = text.verticalAlignment.ToString();
data.richText = text.richText;
data.overflowMode = text.overflowMode.ToString();
data.enableWordWrapping = text.enableWordWrapping;
if (text.font != null)
{
string fontPath = AssetDatabase.GetAssetPath(text.font);
if (!string.IsNullOrEmpty(fontPath))
{
data.font = fontPath;
}
}
if (text.material != null)
{
string materialPath = AssetDatabase.GetAssetPath(text.material);
if (!string.IsNullOrEmpty(materialPath))
{
data.materialPreset = materialPath;
}
}
return data;
}
private static ScrollRectData ConvertScrollRect(ScrollRect scrollRect)
{
ScrollRectData data = new ScrollRectData();
data.horizontal = scrollRect.horizontal;
data.vertical = scrollRect.vertical;
data.movementType = scrollRect.movementType.ToString();
data.horizontalScrollbarVisibility = scrollRect.horizontalScrollbarVisibility.ToString();
data.verticalScrollbarVisibility = scrollRect.verticalScrollbarVisibility.ToString();
return data;
}
private static LayoutGroupData ConvertGridLayoutGroup(GridLayoutGroup layout)
{
LayoutGroupData data = new LayoutGroupData();
data.type = "GridLayoutGroup";
data.cellSize = new float[] { layout.cellSize.x, layout.cellSize.y };
data.spacing = new float[] { layout.spacing.x, layout.spacing.y };
data.constraint = (int)layout.constraint;
data.childAlignment = layout.childAlignment.ToString();
data.padding = new int[] { layout.padding.left, layout.padding.right, layout.padding.top, layout.padding.bottom };
return data;
}
private static LayoutGroupData ConvertVerticalLayoutGroup(VerticalLayoutGroup layout)
{
LayoutGroupData data = new LayoutGroupData();
data.type = "VerticalLayoutGroup";
data.spacing = new float[] { layout.spacing, layout.spacing };
data.childAlignment = layout.childAlignment.ToString();
data.padding = new int[] { layout.padding.left, layout.padding.right, layout.padding.top, layout.padding.bottom };
return data;
}
private static LayoutGroupData ConvertHorizontalLayoutGroup(HorizontalLayoutGroup layout)
{
LayoutGroupData data = new LayoutGroupData();
data.type = "HorizontalLayoutGroup";
data.spacing = new float[] { layout.spacing, layout.spacing };
data.childAlignment = layout.childAlignment.ToString();
data.padding = new int[] { layout.padding.left, layout.padding.right, layout.padding.top, layout.padding.bottom };
return data;
}
private static ToggleData ConvertToggle(Toggle toggle)
{
ToggleData data = new ToggleData();
data.isOn = toggle.isOn;
data.toggleTransition = toggle.toggleTransition.ToString();
data.transition = toggle.transition.ToString();
return data;
}
private static SliderData ConvertSlider(Slider slider)
{
SliderData data = new SliderData();
data.minValue = slider.minValue;
data.maxValue = slider.maxValue;
data.value = slider.value;
data.wholeNumbers = slider.wholeNumbers;
data.direction = slider.direction.ToString();
return data;
}
private static InputFieldData ConvertInputField(InputField inputField)
{
InputFieldData data = new InputFieldData();
data.text = inputField.text;
if (inputField.placeholder != null)
{
Text textPlaceholder = inputField.placeholder as Text;
if (textPlaceholder != null)
{
data.placeholder = textPlaceholder.text;
}
else
{
TextMeshProUGUI tmpPlaceholder = inputField.placeholder as TextMeshProUGUI;
if (tmpPlaceholder != null)
{
data.placeholder = tmpPlaceholder.text;
}
}
}
data.characterLimit = inputField.characterLimit;
data.contentType = inputField.contentType.ToString();
data.inputType = inputField.inputType.ToString();
return data;
}
private static CustomComponentData ConvertCustomComponent(Component comp)
{
CustomComponentData data = new CustomComponentData();
data.typeName = comp.GetType().Name;
return data;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 33906f611de8963438be24b30bc8592e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,248 @@
# UI 拼接工具
## 概述
UI 拼接工具提供 JSON 配置与 Unity 预制体的双向转换,帮助 UI 相关人员通过 JSON 配置文件快速生成 UI 预制体,同时支持从现有预制体反向导出 JSON 用于版本控制或重新生成。
## 核心组件
| 组件 | 职责 | 文件路径 |
|-----|------|---------|
| **UIPrefabGenerator** | 解析 JSON 并生成 UI 预制体 | `Assets/Editor/Command/UIPrefabGenerator.cs` |
| **UIPrefabToJson** | 从预制体反向导出 JSON | `Assets/Editor/Command/UIPrefabToJson.cs` |
| **RemoteCommand** | 提供外部调用接口 | `Assets/Editor/Command/RemoteCommand.cs` |
| **ExternalCommandListener** | 监听外部命令触发文件 | `Assets/Editor/Command/ExternalCommandListener.cs` |
工具脚本在文档库中的位置(已并入编辑器工具模块):
```
modules/frameworks/编辑器工具/UI工具/
├── ExternalCommandListener.cs # 外部命令监听器
├── RemoteCommand.cs # 远程命令接口
├── UIPrefabGenerator.cs # JSON 转预制体生成器
└── UIPrefabToJson.cs # 预制体转 JSON 导出器
```
## JSON 配置文件结构
```json
{
"name": "BagWindow",
"width": 1024,
"height": 768,
"elements": [
{
"type": "Image",
"name": "BgPanel",
"anchorMin": [0, 0],
"anchorMax": [1, 1],
"position": [0, 0, 0],
"sizeDelta": [0, 0],
"sprite": "Assets/Game/GameHFResource/CN/UIWindows/BagWindow/Sprites/Bg.png"
},
{
"type": "Text",
"name": "TitleText",
"anchorMin": [0.5, 0.9],
"anchorMax": [0.5, 0.9],
"position": [0, 0, 0],
"sizeDelta": [200, 40],
"text": "背包",
"font": "Assets/Game/GameHFResource/CN/Fonts/FZY4K-M05S SDF.asset",
"fontSize": 28,
"color": [1, 1, 1, 1]
}
]
}
```
## 支持的 UI 元素类型
| 类型 | 对应 Unity 组件 | 关键属性 |
|-----|-------------|---------|
| **Image** | Image | sprite, color, type, preserveAspect |
| **TextMeshProUGUI** | TextMeshProUGUI | text, font, fontSize, color, alignment |
| **Button** | Button | interactable, transition, navigationMode |
| **Slider** | Slider | minValue, maxValue, value, wholeNumbers, direction |
| **Toggle** | Toggle | isOn, toggleTransition, transition |
| **InputField** | InputField | text, placeholder, characterLimit, contentType |
| **ScrollRect** | ScrollRect | horizontal, vertical, movementType |
| **GridLayoutGroup** | GridLayoutGroup | cellSize, spacing, constraint, padding |
| **VerticalLayoutGroup** | VerticalLayoutGroup | spacing, childAlignment, padding |
| **HorizontalLayoutGroup** | HorizontalLayoutGroup | spacing, childAlignment, padding |
## 外部命令触发机制
### 触发方式
通过创建/修改 `Trigger_Command.txt` 文件触发 UI 生成:
```
GenerateUIPrefab:Assets/Editor/UI/BagWindow.json
```
### 命令格式
```
<命令名>:<参数1>:<参数2>:...
```
**支持的命令**
| 命令 | 参数 | 说明 |
|-----|------|------|
| **GenerateUIPrefab** | `jsonPath` [, `prefabPath`] | 根据 JSON 生成 UI 预制体 |
| **ExportPrefabToJson** | `prefabPath` [, `jsonPath`] | 从预制体反向导出 JSON |
| **ReadJson** | [参数列表] | 读取 JSON 配置(测试用) |
### 完整执行流程
```
┌─────────────────────────────────────────────────────────────────────┐
│ 外部系统写入命令到 Trigger_Command.txt │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ ExternalCommandListener (FileSystemWatcher) 检测到文件变化 │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 解析命令格式GenerateUIPrefab:Assets/Editor/UI/BagWindow.json │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 通过反射调用 RemoteCommand.GenerateUIPrefab(string[] args) │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ UIPrefabGenerator.GeneratePrefabFromJson(jsonPath, prefabPath) │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 生成预制体到指定路径Assets/Game/GameHFResource/CN/UIWindows/... │
└─────────────────────────────────────────────────────────────────────┘
```
## 使用示例
### 方式一:外部命令触发
```bash
# 正向:从 JSON 生成预制体
echo "GenerateUIPrefab:Assets/Editor/UI/BagWindow.json" > "F:\JMNet\CrystalBattle_Client\Trigger_Command.txt"
# 反向:从预制体导出 JSON
echo "ExportPrefabToJson:Assets/Game/GameHFResource/CN/UIWindows/BagWindow/BagWindow.prefab" > "F:\JMNet\CrystalBattle_Client\Trigger_Command.txt"
```
### 方式二Unity 编辑器菜单
**正向生成**
1. 打开 Unity 编辑器
2. 点击菜单 `Tools > External > Test GenerateUIPrefab`
3. 查看 Console 窗口日志确认执行结果
**反向导出**
1. 打开 Unity 编辑器
2. 点击菜单 `Tools > UI > Export Prefab to JSON`
3. 选择要导出的预制体文件
### 方式三:代码调用
```csharp
// 正向:从 JSON 生成预制体
UIPrefabGenerator.GeneratePrefabFromJson(
"Assets/Editor/UI/BagWindow.json",
"Assets/Game/GameHFResource/CN/UIWindows/BagWindow/BagWindow.prefab"
);
// 反向:从预制体导出 JSON
UIPrefabToJson.ExportPrefabToJson(
"Assets/Game/GameHFResource/CN/UIWindows/BagWindow/BagWindow.prefab",
"Assets/Editor/UI/BagWindow.json"
);
```
## 注意事项
1. **路径格式**JSON 中的资源路径使用 `/` 分隔符,且为 Unity 项目相对路径
2. **资源引用**:确保 JSON 中引用的 Sprite、Font 等资源已存在于项目中
3. **编码格式**JSON 文件使用 UTF-8 编码
4. **坐标系统**:使用 Unity RectTransform 的 anchorMin/anchorMax/position/sizeDelta 四元组
5. **文件锁定**:生成前确保目标预制体文件未被其他进程占用
## 部署步骤
### 方法一:手动复制
1. 复制 `modules/frameworks/编辑器工具/UI工具/` 目录下的所有文件
2. 粘贴到目标项目的 `Assets/Editor/Command/` 目录中
3. 确保目录结构如下:
```
Assets/Editor/Command/
├── ExternalCommandListener.cs
├── RemoteCommand.cs
├── UIPrefabGenerator.cs
└── UIPrefabToJson.cs
```
### 方法二:自动部署脚本
创建部署脚本 `DeployUITools.bat`
```batch
@echo off
set "SOURCE_DIR=E:\AI\UnityAI\modules\frameworks\编辑器工具\UI工具"
set "TARGET_DIR=%cd%\Assets\Editor\Command"
mkdir "%TARGET_DIR%" 2>nul
xcopy "%SOURCE_DIR%\*.cs" "%TARGET_DIR%\" /y
xcopy "%SOURCE_DIR%\*.cs.meta" "%TARGET_DIR%\" /y
echo UI 工具部署完成
pause
```
### 部署检查项
| 检查项 | 要求 |
|-------|------|
| **目标路径** | 必须放置在 `Assets/Editor/Command/` 目录下 |
| **文件完整性** | 必须包含所有 4 个 cs 文件及其 meta 文件 |
| **Unity 版本** | 支持 Unity 2020 及以上版本 |
| **依赖组件** | 需要导入 TextMeshPro 包 |
### 验证部署
部署完成后,在 Unity 编辑器中验证:
1. 打开 Unity 编辑器
2. 检查菜单 `Tools > UI` 是否存在
3. 检查菜单 `Tools > External` 是否存在
4. 在 Console 窗口确认无编译错误
---
**版本**: v1.2
**适用**: StrayFog 框架 UI 拼接开发人员
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [编辑器工具入口](README.md) | 本文件是编辑器工具模块的子文档 |
| [UI 窗口系统](../UI窗口系统/README.md) | UI 拼接工具服务于 UI 窗口系统 |
| [UI 界面拼接设计指南](../UI窗口系统/界面拼接.md) | 设计指南说明 JSON 驱动的界面拼接原则 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义编辑器工具层与运行时层边界 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | 编辑器工具开发规则互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,121 @@
# 网络系统
## 分类
- 核心框架
## 目的
提供 WebSocket 连接、protobuf 消息收发、消息 ID / route 映射和错误响应处理。
---
## 核心路径
- protobuf 源:`GoogleProtobufNetwork/`
- 生成输出:`Assets/Game/GameHFScripte/CopyToHF/Network/`
- WebSocket`Assets/Game/GameHFScripte/GameFunction/WebSocket/`
---
## 核心类
| 类 | 职责 |
|---|---|
| `HFSeverManager` | 网络管理器单例 |
| `WSSession` | WebSocket 会话 |
| `HFM_Socket_WS` | WebSocket 底层连接 |
| `HFWebSocket_Manager_AutoMsgMap` | 自动生成消息映射 |
| `HFNetWorkMsgIdMap` | 网络消息 ID 映射 |
| `MessagePage` | 数据包编解码 |
---
## C2S 发送链路
```text
IMessage.Dispatch()
-> HFSeverManager.Instance.SendMsg(message)
-> HFSeverManager.OnAuto_OnGetProtocNo(message)
-> HFSeverManager.OnAuto_OnGetRoute(Network_MsgId)
-> m_Session.SendMessage(protoNo, route, PacketType.Data, Request, message)
```
`IMessage.Dispatch()` 定义在 `Assets/Game/GameHFScripte/GameFunction/WebSocket/Extends/HFServer_Extend.cs`
---
## WebSocket 连接流程
```text
登录管理器.ConnectServer
-> HFSeverManager.Connect(MainConnect, WebSocket, ip, port, token)
-> WSSession.Connect
-> HFM_Socket_WS.Connect
-> Handshake -> HandshakeAck -> 连接成功回调
```
---
## S2C 收包和派发
```text
WSSession.ReceiveMessages(PacketType.Data)
-> MessagePage.Decode(packet.Body)
-> Response.MessageId / Push.Route -> Network_MsgId
-> HFSeverManager.OnAuto_OnMergeFrom(msgid, body)
-> protobuf Parser.ParseFrom
-> 反射创建 HFEvent_STC_{MessageTypeName}
-> 写入 Request / ErrorCode
-> AbsHFEventArg<enHFSTCEvent>.Dispatch()
-> HFEventManager.Instance.STCEvent.Dispatch(arg)
```
---
## 自动生成映射
- `HFWebSocket_Manager_AutoMsgMap.cs`:提供 `OnAuto_OnGetProtocNo``OnAuto_OnGetRoute``OnAuto_OnMergeFrom``msrDicRouteToMsg`、S2C/C2S 类型字典。
- `HFNetWorkMsgIdMap.cs`:为 protobuf partial class 标记 `[HFNetworkMsg(...)]`
---
## 使用规则
- 新增协议先改 `.proto``msg.proto``route.txt`
- 不手动修改生成输出。
- 不硬编码 route 或数字 msgId。
- UI 监听 S2C 必须在 `OnClose` 移除。
- 错误响应会触发 `HFEvent_ErrorCode`
---
## 协议变更清单
1. 修改 `.proto``msg.proto`
2. 登记 msgId / route。
3. 重新生成 C# 代码。
4. 检查 `HFNetWorkMsgIdMap.cs` / `HFWebSocket_Manager_AutoMsgMap.cs`
5. 更新业务入口和监听。
6. 验证。
---
## 相关模块
- [事件系统](事件系统/README.md)
- [数据/配置框架](数据配置框架.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/事件系统/README.md](事件系统/README.md) | S2C 消息派发为 STC 事件 |
| [modules/frameworks/数据配置框架.md](数据配置框架.md) | 配置表与网络消息可能联动 |
| [rules/10-架构说明.md](../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,136 @@
# 资源管理
## 分类
- 核心框架
## 目的
管理 AssetBundle、语言资源、图集、prefab、DllBytes 等资源的加载、缓存和释放。
---
## 核心路径
- AssetBundle`Assets/Game/GameHFScripte/CopyToHF/AssetBundle/`
- 图集:`Assets/Game/GameHFScripte/CopyToHF/SpriteAtlas/`
- 资源目录:`Assets/Game/GameHFResource/CN/``EN/``DllBytes/`
---
## 核心类
| 类 | 职责 |
|---|---|
| `HFABMgr` | AssetBundle 资源管理器单例 |
| `HFAssetLoader` | 具体资源加载器 |
| `HFAssetRequest` | 资源加载请求 |
| `HFAssetBundlePathAttribute` | 资源路径属性,标记资源根目录、相对路径、语言包规则等 |
| `HFGlobal_SpriteAtlas` | 图集全局管理 |
---
## 资源路径规则
`HFAssetBundlePathAttribute` 记录:
- 资源根目录
- 相对路径
- 是否忽略语言包
- 是否场景
- 线程优先级
`ChangeLanguage` 生成:
- `editorPath`
- `assetBundlePath`
- `assetBundleKey`
- `assetName`
`HFABMgr.OnCheckAssetBundlePathAttributeLanguage` 在当前语言无资源时回退默认语言。
---
## 加载流程
```text
HFABMgr.LoadInMemory(pathAttr)
-> OnReadAssetBundleManifest()
-> OnCheckAssetBundlePathAttributeLanguage()
-> new HFAssetRequest(...)
-> OnStartLoad()
-> 获取或创建 HFAssetLoader
-> HFAssetLoader.LoadAsset()
```
---
## 加载模式
### Editor 模式
```text
enHFAbLoadModel.Editor
-> SFAssetUtility_Unity.LoadFromFile(editorPath)
-> 直接读取编辑器资源
```
### 非 Editor 模式
```text
读取 AssetBundleManifest
-> 解析依赖
-> AssetBundle.LoadFromMemoryAsync 读取主资源
```
### CDN / 边玩边下
```text
缺失资源进入 WaitDownload
-> HFAssetsDownLoadManager 下载
-> HFABMgr.SaveFile 保存到本地
-> 加载完成
```
---
## 图集加载
```text
SpriteAtlasManager.atlasRequested
-> HFGlobal_SpriteAtlas.Instance.GetSpriteAtlasPath(spriteAtlasName)
-> HFABMgr.OnLoadInMemory(spriteAtlasAttribute)
-> callback(SpriteAtlas)
```
---
## 使用规则
- 资源路径统一通过 `HFAssetBundlePathAttribute` 配置。
- 加载资源统一走 `HFFunCatalogue.Asset.LoadAsset()``HFABMgr.LoadInMemory()`
- 语言资源按 `GameHFResource/<lang>/` 分目录存放。
- 图集按业务或 UI 模块划分。
---
## 相关模块
- [UI 窗口系统](../UI窗口系统/README.md)
- [实体系统](../实体系统/README.md)
- [场景系统](../场景系统.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/UI窗口系统/README.md](../UI窗口系统/README.md) | UI 资源加载依赖资源管理 |
| [modules/frameworks/实体系统/README.md](../实体系统/README.md) | 实体资源加载依赖资源管理 |
| [modules/frameworks/场景系统.md](../场景系统.md) | 场景资源加载依赖资源管理 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,102 @@
# 资源管理使用指南
## 一、AssetBundle 资源加载
### 1.1 加载资源示例
```csharp
public class ResourceLoader : MonoBehaviour
{
public void LoadPlayerModel(int resId, Action<GameObject> onLoaded)
{
// 通过 HFFunCatalogue 统一入口加载资源
// resId: 从 Res 配置表中读取的资源 ID
HFFunCatalogue.Asset.LoadAsset(resId, (res, obj) =>
{
if (obj != null)
{
GameObject model = res.GetAsset<GameObject>();
onLoaded?.Invoke(model);
}
else
{
Debug.LogError($"加载失败: resId={resId}");
}
});
}
}
```
### 1.2 实体资源加载示例
```csharp
// 在 Entity 中加载资源
HFFunCatalogue.Asset.LoadAsset(data.EntityRes.Id, (res, obj) => {
if (!entity.isDeath)
{
var Boddy = res.GetAsset<GameObject>();
data.SetBoddy(Boddy);
}
});
```
---
## 二、资源管理 API
### 2.1 常用方法
| 类别 | 管理器 | 常用方法 |
|-----|-------|---------|
| **资源** | `HFABMgr` | `LoadAsset()`, `OnStartLoad()` |
### 2.2 资源加载流程
```
资源加载流程:
OnStartLoad(request)
检查缓存 → 获取/创建 HFAssetLoader
loader.AddRequest(request)
loader.LoadAsset() → 异步加载
加载完成 → 回调处理
```
---
## 三、资源管理注意事项
### 3.1 性能优化建议
- 及时释放不再使用的资源
- 使用异步加载避免卡顿
- 合理使用资源缓存
---
**版本**: v1.0
**生效日期**: 2026年
**适用范围**: GiftGameX 项目业务开发人员
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [资源管理入口](README.md) | 本文件是资源管理模块的子文档 |
| [UI 窗口系统](../UI窗口系统/README.md) | UI 资源加载依赖资源管理 |
| [实体系统](../实体系统/README.md) | 实体资源加载依赖资源管理 |
| [场景系统](../场景系统.md) | 场景资源加载依赖资源管理 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,70 @@
# 通用框架
## 分类
- 项目框架
## 目的
记录 general/shared 代码、生成框架、StrayFog 核心和跨模块通用能力。
## 已知线索
- General`Assets/Game/GameGeneralScripte/`
- 生成 General`Assets/AutoBuildScript/General/`
- StrayFog Core`Assets/StrayFog/Core/`
- 程序集:`GameGeneral``ZY.General.ZYCore``StrayFogCore`
## 当前结构
- `GameGeneralScripte/`:项目通用运行时代码,包含 `CopyToGeneral`、状态机扩展、`GameGeneral.asmdef``SEHFCommon.dll`
- `AutoBuildScript/General/`:生成框架的 General 层,包含 `ZYCore``ZYXlsx``ZYUGUI` 的通用模板和属性。
- `StrayFog/Core/`:底层核心程序集和模拟行为桥接,包含 `SFCore.dll``SFGlobalData``SFAssetUtility_Unity``SimulateBehaviour`
- `GameGeneralScripte/CopyToGeneral/AssetBundle/HFAssetBundleHelper.cs`资源路径、StreamingAssets、PersistentData、CDNAssets、微信小游戏文件读写适配。
- `GameGeneralScripte/CopyToGeneral/DllHelper/HFDllHelper.cs`Hotfix DLL、PDB、HybridCLR AOT DLL bytes 路径和读取。
- `GameGeneralScripte/CopyToGeneral/Enum/HFEnumForAssetBundle.cs`AssetBundle 加载/更新模式枚举。
- `GameGeneralScripte/StateMachineBehaviour/`Animator 状态机扩展和只读属性 Drawer。
## 边界判断
- `GameGeneralScripte` 更像项目运行时可依赖的通用层。
- `AutoBuildScript/General` 更像代码生成框架的通用模板层。
- `StrayFog/Core` 提供更底层的工具、全局路径和模拟行为桥接能力。
- 资源框架、配置框架、Hotfix 启动都会依赖这里的通用能力;反向依赖 hotfix 业务逻辑应避免。
## 梳理任务
- [x] 梳理 `GameGeneralScripte``AutoBuildScript/General` 的边界。
- [x] 梳理 StrayFog Core 提供的基础能力。
- [x] 梳理通用 attributes、enum、dll helper、asset bundle helper。
- [x] 梳理哪些功能/框架允许依赖 general/shared。
- [x] 梳理生成代码和手写代码的边界。
## 待确认
- `StrayFog` 是项目自研框架还是外部框架。
- `CopyToGeneral` 的复制/生成来源需要继续追 `AutoBuildScript` 配置。
- General 层建议保持底层通用,不依赖 hotfix 业务层。
## 相关模块
- [Hotfix 启动/目录](../Hotfix启动.md)
- [资源框架](../资源管理/README.md)
- [数据/配置框架](../数据配置框架.md)
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [modules/frameworks/数据配置框架.md](../数据配置框架.md) | 数据/配置框架 |
| [modules/frameworks/Hotfix启动.md](../Hotfix启动.md) | Hotfix 启动/目录 |
| [modules/frameworks/资源管理/README.md](../资源管理/README.md) | 资源框架 |
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | 开发规则与框架/生成规则互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
| TODO补充依赖或使用本框架的模块文档 | 如依赖本框架的功能模块 |

View File

@@ -0,0 +1,398 @@
# StrayFog 框架架构分析
> 本文档从实现视角分析 StrayFog 框架的整体架构。高层架构规则与程序集边界见 [rules/10-架构说明.md](../../../rules/10-架构说明.md)。
## 一、框架整体架构
### 1.1 模块划分
| 模块层级 | 路径 | 职责描述 |
|---------|------|---------|
| **Core 层** | `Assets/StrayFog/Core` | 核心基础框架,模拟行为系统、全局数据管理 |
| **HF 层** | `Assets/Game/GameHFScripte/CopyToHF` | 游戏核心业务框架,资源管理、事件系统、相机管理等 |
| **General 层** | `Assets/Game/GameGeneralScripte` | 通用工具层,状态机、辅助工具等 |
### 1.2 架构图
```
┌─────────────────────────────────────────────────────────────────┐
│ StrayFog Framework │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ │
│ │ GameGeneral │ │ GameHF │ │ Core │ │
│ │ 状态机/工具类 │ │ 业务核心模块 │ │ 基础框架层 │ │
│ └────────┬─────────┘ └────────┬─────────┘ └───────┬───────┘ │
├───────────┼─────────────────────┼───────────────────┼───────────┤
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Unity Engine │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 二、Core 层架构
### 2.1 SimulateBehaviour 模拟行为系统
#### 2.1.1 核心接口
```csharp
public interface ISimulateBehaviour
{
long simulateGlobalId { get; } // 模拟行为全局 ID
int bindGameObjectInstanceId { get; } // 绑定对象实例 ID
void BindGameObject(GameObject _go); // 绑定 GameObject
void UnBindGameObject(); // 解绑 GameObject
// ... 完整的 MonoBehaviour 生命周期方法
}
```
#### 2.1.2 设计目的
| 设计目标 | 实现方式 |
|---------|---------|
| **解耦 MonoBehaviour** | 将逻辑从 MonoBehaviour 中剥离,便于测试和复用 |
| **生命周期控制** | 完整模拟 Unity 生命周期,支持自定义调度 |
| **对象池优化** | 配合对象池实现高效对象复用 |
#### 2.1.3 目录结构
```
SimulateBehaviour/
├── ISimulateBehaviour.cs # 核心接口定义
├── Bind/ # 绑定实现
│ ├── IBindSimulateBehaviour.cs
│ ├── AbsBindSimulateBehaviour_Mono.cs # Mono 绑定基类
│ ├── AbsBindSimulateBehaviour_UI.cs # UI 绑定基类
│ └── BindSimulateBehaviour_Collect.cs # 收集器
├── MonoBehaviour/ # 各生命周期模拟实现
│ ├── SimulateMonoBehaviour_Awake__xxx.cs
│ ├── SimulateMonoBehaviour_Update__xxx.cs
│ └── ... (40+ 生命周期方法)
└── UIBehaviour/ # UI 生命周期模拟
├── SimulateUIBehaviour_Awake__xxx.cs
└── ...
```
### 2.2 全局数据管理
**SFGlobalData.cs** - 全局路径和平台管理
```csharp
public sealed class SFGlobalData
{
public static string assetBundleRoot; // AB 包根路径
public static string streamingAssetsRoot; // 流式资源根路径
public static string cdnassetBundleRoot; // CDN 资源路径
public static RuntimePlatform Platform(); // 当前平台判断
}
```
---
## 三、HF 层架构
### 3.1 接口体系
#### 3.1.1 核心接口继承链
```
IHFObject (基础对象接口)
├── IHFRecycle (回收接口)
│ └── void Recycle();
├── IHFGlobal (全局对象接口)
│ └── long globalId { get; }
├── IHFClone (克隆接口)
├── IHFLanguage (语言接口)
├── IHFScene (场景接口)
└── IHFSerializable (序列化接口)
```
#### 3.1.2 接口职责表
| 接口 | 核心职责 | 关键字段/方法 |
|-----|---------|--------------|
| `IHFObject` | 基础对象标识 | 无 |
| `IHFRecycle` | 对象回收机制 | `Recycle()` |
| `IHFGlobal` | 全局唯一标识 | `globalId` |
| `IHFClone` | 对象克隆能力 | 系列 Clone 方法 |
| `IHFLanguage` | 多语言支持 | 语言切换 |
| `IHFScene` | 场景管理 | 场景加载/卸载 |
### 3.2 对象池系统
#### 3.2.1 HFObjectMemPool - GameObject 对象池
```csharp
public class HFObjectMemPool
{
private Queue<GameObject> m_UnoccupiedPool; // 空闲队列
private Dictionary<int, GameObject> m_UsingPool; // 使用中字典
public GameObject GetItem(out bool isnew); // 获取对象
public void GiveBack(GameObject item); // 归还对象
public void Recycle(); // 完全回收
}
```
#### 3.2.2 HFScriptMemPool<T> - 泛型脚本对象池
```csharp
public class HFScriptMemPool<T> : IHFRecycle
where T : IHFRecycle
{
private Queue<T> m_UnoccupiedPool;
private List<T> m_UsingPool;
// 支持 MonoBehaviour 和普通类的自动创建
}
```
#### 3.2.3 对象池设计特点
| 特性 | 实现方式 | 优势 |
|-----|---------|-----|
| **双池结构** | 空闲池 + 使用池 | 高效管理对象状态 |
| **内存泄漏检测** | 归还时校验来源 | 及时发现问题 |
| **泛型支持** | `HFScriptMemPool<T>` | 类型安全 |
| **Mono 支持** | 自动创建 GameObject | 无缝集成 Unity |
### 3.3 事件系统
#### 3.3.1 核心接口
```csharp
public interface IHFEventHandle<K,T> : IHFRecycle
{
void AddListener<T1>(K key, Action<T> callback); // 添加监听
void RemoveListener<T1>(K key, Action<T> action); // 移除监听
void Dispatch(T arg); // 分发事件
void RemoveAll(K key); // 清空监听
}
```
#### 3.3.2 AbsHFEventHandle 实现机制
```csharp
public abstract class AbsHFEventHandle<K,T> : IHFEventHandle<K,T>
where K : struct, Enum
where T : IHFEventArg<K>
{
// 多级索引: EventId -> TypeHashCode -> Action 列表
HFMultiKeyDictionary<K, int, IHFCacheEventAction<K,T>> mICacheEventActionMaping;
}
```
#### 3.3.3 事件系统架构
```
事件分发流程:
Dispatch(arg)
获取 EventId 对应的 Action 字典
获取 参数类型 HashCode 对应的 CacheEventAction
遍历执行所有 Action<T>
调用 arg.Recycle() 回收事件参数
```
### 3.4 AssetBundle 资源管理器
#### 3.4.1 HFABMgr 核心结构
```csharp
public partial class HFABMgr : AbsHFSingleMonoBehaviour<HFABMgr>
{
public float progress { get; } // 加载进度
Dictionary<int, HFAssetLoader> mHFAssetLoaderMaping; // 加载器映射
Dictionary<int, HFAssetBundlePathAttribute> mHFAssetBundlePathAttributeMaping; // 属性映射
}
```
#### 3.4.2 资源加载流程
```
资源加载流程:
OnStartLoad(request)
检查缓存 → 获取/创建 HFAssetLoader
loader.AddRequest(request)
loader.LoadAsset() → 异步加载
加载完成 → 回调处理
```
#### 3.4.3 依赖管理
通过 Manifest 文件解析资源依赖关系:
```csharp
HFAssetBundlePathAttribute OnGetDependencyAbpAttr(string _depManifestName)
{
// 解析依赖项名称 → 获取属性 → 缓存
}
```
### 3.5 相机管理系统
#### 3.5.1 HFCameraManager 架构
```csharp
public partial class HFCameraManager : AbsHFSingleRunTime<HFCameraManager>
{
private Dictionary<int, AbsHFCameraItem> m_List_CameraItem; // 相机列表
public HFMainCamera mainCamera; // 主相机
public void AddCamera(AbsHFCameraItem cameraItem); // 添加相机
public void RemoveCamera(AbsHFCameraItem cameraItem); // 移除相机
public AbsHFEventHandle GetCamera(string _cameraName); // 获取相机
}
```
#### 3.5.2 相机堆栈管理
```
相机层级架构:
HFMainCamera (主相机)
├── CameraStack (相机堆栈)
│ ├── CameraItem1 (Weight=10)
│ ├── CameraItem2 (Weight=20)
│ └── CameraItem3 (Weight=5)
└── 按 Weight 排序 → 渲染顺序控制
```
---
## 四、General 层架构
### 4.1 状态机系统
#### 4.1.1 核心类层次
```
IGFStateMachineBehaviour (接口)
├── AbsGFStateMachineBehaviour (抽象基类)
│ ├── AbsGFAttachStateBehaviour (状态行为)
│ ├── AbsGFAttachLayerBehaviour (层行为)
│ └── AbsGFAttachMachineBehaviour (机行为)
└── IGFStateMachineBehaviourClassify (分类接口)
```
#### 4.1.2 状态机特性
| 特性 | 说明 |
|-----|------|
| **层级化设计** | 支持状态、层、机三个层级的行为绑定 |
| **参数化驱动** | 通过参数控制状态切换 |
| **Inspector 扩展** | 自定义编辑器支持 |
### 4.2 工具类
#### 4.2.1 扩展方法集合
**HFUGUIExtendEngine.cs** - UGUI 扩展方法40+ 方法)
| 功能分类 | 方法示例 |
|---------|---------|
| **坐标转换** | `HFWorldToLocalPointInRectangle` |
| **布局操作** | `HFFixedPositionInCanvas`, `HFForceRebuildLayoutImmediate` |
| **RectTransform** | `HFIdentityStreech`, `HFCopyRectTransformFrom` |
| **事件监听** | `HFAddListener` |
#### 4.2.2 其他工具
| 工具类 | 职责 |
|-------|-----|
| `HFAssetBundleHelper` | AB 包辅助工具 |
| `HFDllHelper` | DLL 加载辅助 |
| `HFEnumForAssetBundle` | AB 包相关枚举 |
| `MinGameModel` | 微信小游戏适配 |
---
## 五、架构设计特点
### 5.1 设计模式应用
| 模式 | 应用场景 | 实现类 |
|-----|---------|-------|
| **单例模式** | 全局管理器 | `AbsHFSingleMonoBehaviour`, `AbsHFSingleRunTime` |
| **工厂模式** | 对象创建 | 对象池的 GetItem |
| **观察者模式** | 事件系统 | `IHFEventHandle` |
| **策略模式** | 多平台适配 | `SFGlobalData.Platform()` |
| **模板方法** | 生命周期 | `SimulateBehaviour` 系列 |
### 5.2 架构优势
| 优势 | 体现 |
|-----|------|
| **解耦性** | 逻辑与 MonoBehaviour 分离 |
| **可测试性** | 纯逻辑类易于单元测试 |
| **性能优化** | 对象池减少 GC |
| **扩展性** | 接口驱动,易于扩展 |
| **可维护性** | 清晰的模块划分 |
### 5.3 潜在改进点
| 改进方向 | 说明 |
|---------|------|
| **异步加载优化** | 可引入协程池或 Job System |
| **资源依赖可视化** | 可增加依赖关系图工具 |
| **性能监控** | 可增加加载时间、内存占用统计 |
| **热更新支持** | 可考虑集成 Lua 或 ILRuntime |
---
## 六、总结
StrayFog 框架是一个结构清晰、设计完善的 Unity 游戏框架,核心特点包括:
1. **模拟行为系统** - 实现了完整的 MonoBehaviour 生命周期解耦
2. **对象池机制** - 高效的内存管理策略
3. **事件驱动架构** - 灵活的事件分发系统
4. **资源管理体系** - 完善的 AssetBundle 加载和依赖管理
5. **模块化设计** - 清晰的分层和职责划分
该框架适合中大型 Unity 项目使用,具备良好的扩展性和维护性。
---
**版本**: v1.0
**分析日期**: 2024年
**适用项目**: OnlineGame
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [通用框架入口](README.md) | 本文件是通用框架模块的子文档 |
| [架构说明](../../../rules/10-架构说明.md) | 高层架构规则与程序集边界 |
| [实体系统](../实体系统/README.md) | 实体系统架构在本文件中分析 |
| [对象池与运行时对象管理](../内存对象管理/README.md) | 对象池架构在本文件中分析 |
| [事件系统](../事件系统/README.md) | 事件系统架构在本文件中分析 |
| [资源管理](../资源管理/README.md) | 资源管理架构在本文件中分析 |
| [相机系统](../相机系统/README.md) | 相机系统架构在本文件中分析 |
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
| [rules/20-开发规则.md](../../../rules/20-开发规则.md) | 开发规则与框架架构互相引用 |
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |

View File

@@ -0,0 +1,84 @@
# 模块名称
## 分类
- 项目功能 / 项目框架 / 编辑器工具
## 目的
TODO一句话说明这个模块是做什么的从玩家/产品/技术视角写。
## 主要路径
| 类型 | 路径 | 说明 |
|---|---|---|
| 逻辑代码 | `Assets/...` | TODO |
| UI 代码 | `Assets/...` | TODO |
| 资源 | `Assets/...` | TODO |
| 配置 | `XLSX_SRC/...``GoogleProtobufNetwork/...` | TODO |
## 入口
- 运行入口TODO
- 编辑器入口TODO
- 资源入口TODO
## 数据流
```text
TODO用箭头图描述关键流程
```
## 关键类 / API
| 类/方法 | 用途 |
|---|---|
| `ClassName` | TODO |
## 依赖
- 依赖的框架模块TODO
- 依赖的其他功能模块TODO
- 依赖的第三方插件TODO
## 当前状态
- TODO
## 变更记录
| 日期 | 变更内容 | 关联 spec/change | 影响范围 |
|---|---|---|---|
| YYYY-MM-DD | 简要描述 | `[<change-name>]`(对应 `specs/openspec/<change-name>/业务参考总入口.md` | 相关模块/文件 |
## 文档关联
本模块发生变更时,以下文档必须同步更新:
- [模块索引](模块索引.md) — 如果新增/删除模块,或模块分类/状态改变
- [模块登记规则](../rules/30-模块登记规则.md) — 如果新增模块、分类口径或依赖关系变化
- [架构说明](../rules/10-架构说明.md) — 如果涉及架构变化
- TODO链接相关功能/框架模块文档(如依赖方或被依赖方)
- TODO链接相关 spec 目录(如 `../../specs/openspec/<change-name>/业务参考总入口.md`
> 注意:本模板位于 `modules/` 根目录。如果复制到 `modules/features/` 或 `modules/frameworks/` 下,请把 `../rules/` 和 `模块索引.md` 调整为 `../../rules/` 和 `../模块索引.md`。
## 下一步
- [ ] TODO
## 相关文档
- [模块索引](模块索引.md)
- [模块登记规则](../rules/30-模块登记规则.md)
- [架构说明](../rules/10-架构说明.md)
- TODO链接相关功能/框架模块文档
## 是否需要拆分文档
轻量模块可以只保留这个 README。
当模块出现独立计划、持续进度或多项待办时,再增加:
- `progress.md`
- `tasks.md`

85
modules/模块索引.md Normal file
View File

@@ -0,0 +1,85 @@
# 模块索引
这里是 UnityAI / StrayFog 框架知识库的项目知识总入口。
模块清单、模块状态和模块入口以本文件为准;`rules/` 只维护规则和约束,不重复维护模块事实表。
---
## 重要口径
- **核心框架**支撑游戏运行的内部系统例如实体系统、UI 窗口系统、事件系统、资源管理、对象池、场景管理。
- **编辑器工具**Unity Editor 内使用的生成器、Inspector、菜单、配置工具。
- **业务参考**:外部实际项目的业务实现示例,仅作参考,不作为框架规范。见 [business-reference](../business-reference/playballoons/业务参考总入口.md)。
轻量模块先在本文件登记;复杂模块再拆独立目录。
---
## 状态口径
见 [模块登记规则](../rules/30-模块登记规则.md#状态定义)。
不要使用“已开始”,优先使用“已识别 / 初步梳理 / 待验证 / 已完成 / 未发现实现”。
---
## 核心框架
| 框架模块 | 主要路径 | 当前判断 | 状态 |
|---|---|---|---|
| [模拟行为系统](frameworks/通用框架/README.md) | `Assets/StrayFog/Core/SimulateBehaviour/` | 解耦 MonoBehaviour模拟完整生命周期 | 已完成 |
| [对象池](frameworks/内存对象管理/README.md) | `Assets/Game/GameHFScripte/CopyToHF/MemoryManager/` | GameObject 对象池、脚本对象池、类实例池 | 已完成 |
| [事件系统](frameworks/事件系统/README.md) | `Assets/Game/GameHFScripte/CopyToHF/Event/` | CTC/STC 事件参数和事件派发 | 已完成 |
| [资源管理](frameworks/资源管理/README.md) | `Assets/Game/GameHFScripte/CopyToHF/AssetBundle/` | AssetBundle、语言资源、图集、加载规则 | 已完成 |
| [相机系统](frameworks/相机系统/README.md) | `Assets/Game/GameHFScripte/CopyToHF/` | 多相机管理、相机堆栈、渲染顺序 | 初步梳理 |
| [实体系统](frameworks/实体系统/README.md) | `Assets/Game/GameHFScripte/GameFunction/GameEntity/` | 实体基类、模型/系统、实体池 | 已完成 |
| [UI 窗口系统](frameworks/UI窗口系统/README.md) | `Assets/Game/GameHFScripte/CopyToHF/UIWindowMgr/` | UI 窗口管理、窗口基类、生命周期 | 已完成 |
| [技能系统](frameworks/技能系统/README.md) | `Assets/Game/GameHFScripte/GameFunction/GameEntity/` | 技能触发器、释放器、配置联动 | 初步梳理 |
| [场景系统](frameworks/场景系统.md) | `Assets/Game/GameHFScripte/GameFunction/SceneManager/` | 场景加载、资源配置、生命周期 | 初步梳理 |
| [网络系统](frameworks/网络系统.md) | `Assets/Game/GameHFScripte/CopyToHF/Network/` | WebSocket、protobuf、消息映射 | 初步梳理 |
| [配置生成](frameworks/数据配置框架.md) | `XLSX_SRC/``Assets/AutoBuildScript/` | 表格配置源、解析、生成代码 | 初步梳理 |
| [Hotfix 启动](frameworks/Hotfix启动.md) | `Assets/Game/GameHFScripte/GameFunction/GameRoot/` | hotfix 侧根入口和功能目录聚合 | 初步梳理 |
---
## 编辑器工具
| 工具模块 | 主要路径 | 当前判断 | 状态 |
|---|---|---|---|
| [SF Editor](frameworks/编辑器工具/README.md#sf-editor) | `Assets/StrayFog/Editor/` | StrayFog 编辑器扩展 | 待验证 |
| [项目 Editor Assets](frameworks/编辑器工具/README.md#project-editor-assets) | `Assets/Editor/` | 项目编辑器资源与脚本 | 待验证 |
---
## 文档拆分规则
- 核心框架按技术职责命名,可以对应 `GameEntity``UIWindowMgr``Network``AssetBundle``Event` 这类内部系统。
- 轻量模块只在本文件登记。
- 复杂模块再拆 `modules/<module>/`
- `模块索引.md` 写稳定说明。
- `progress.md` 写阶段状态。
- `tasks.md` 写下一步待办。
- 跨模块计划放到 [plans](../plans/)。
---
## 相关文档
- [项目规则入口](../rules/项目规则入口.md)AI 工作方式、扫描范围、归档规则和规则索引。
- [模块登记规则](../rules/30-模块登记规则.md):分类口径、状态定义和新增模块登记规则。
- [架构说明](../rules/10-架构说明.md):总体分层、程序集边界和依赖方向。
---
## 文档关联
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
| 文档 | 关联原因 |
|---|---|
| [rules/30-模块登记规则.md](../rules/30-模块登记规则.md) | 模块索引是模块登记规则的唯一事实来源 |
| [rules/10-架构说明.md](../rules/10-架构说明.md) | 模块分类、分层与架构说明互相影响 |
| [rules/项目规则入口.md](../rules/项目规则入口.md) | 模块索引是项目规则入口引用的项目知识总入口 |
| [ai/AI接入入口.md](../ai/AI接入入口.md) | AI 接入入口引用模块索引作为必读文档 |
| [modules/frameworks/*](frameworks/) | 框架模块文档新增/拆分时需同步索引 |