chore: add codex collaboration config

This commit is contained in:
daixiawu 2026-05-26 16:09:57 +08:00
parent d593338f8b
commit 19cf0ae820
18 changed files with 3362 additions and 0 deletions

View File

@ -0,0 +1 @@
0.4.21

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,217 @@
---
name: "th1-ai-nodes"
description: "TH1 AI行为树节点开发速查包含BaseActionTask/BaseCondition模板、Blackboard数据访问和命名规范"
---
# TH1 AI 节点开发速查
## AI 系统概述
TH1 使用 NodeCanvas 行为树框架,所有自定义节点位于 `BTNodeCanvas/` 目录。
## 关键基类
### BaseActionTask动作节点基类
位置:`BTNodeCanvas/BaseActionTask.cs`
大多数节点(包括"判断"节点)都继承 `BaseActionTask`,而不是 `BaseCondition`
```csharp
// 继承自 NodeCanvas.Framework.ActionTask
[Category("AI节点")]
[Serializable]
public class BaseActionTask : ActionTask
{
[SerializeField] private uint nodeId;
public uint NodeId { get; set; }
protected override void OnExecute() // 重写此方法实现逻辑
protected virtual string desc => string.Empty; // 重写以显示节点信息
// 调用 EndAction(bool success) 结束节点
}
```
### BaseCondition条件节点基类
位置:`BTNodeCanvas/BaseCondition.cs`
```csharp
// 继承自 NodeCanvas.Framework.ConditionTask
[Category("AI节点")]
[Serializable]
public class BaseCondition : ConditionTask
{
[SerializeField] private uint nodeId;
public uint NodeId { get; set; }
// 重写 OnCheck() 返回 bool
protected virtual string desc => string.Empty;
}
```
## 创建新的 AI 判断节点模板(使用 BaseActionTask
大部分判断节点实际使用 `BaseActionTask` 而非 `BaseCondition`,通过 `EndAction(true/false)` 返回结果。
```csharp
using System;
using Logic.AI;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
namespace NodeCanvas.Tasks.Actions
{
[Name("判断描述(中文)")]
[Category("AI节点")]
[Serializable]
public class AIParam{Name} : BaseActionTask
{
// 可配置参数在NodeCanvas编辑器中显示
public bool GreaterThan = true;
public float Threshold = 0.5f;
// 节点显示信息
protected override string desc
{
get
{
if (GreaterThan) return $"某条件大于 {Threshold}";
return $"某条件小于 {Threshold}";
}
}
protected override void OnExecute()
{
base.OnExecute(); // 必须调用设置BTNodeId
// 从Blackboard获取AICalculatorData
var data = blackboard.GetVariable<AICalculatorData>("Data");
if (data?.value == null)
{
EndAction(false);
return;
}
// 获取单位数据
var unit = data.value.TargetParam.UnitData;
if (unit == null)
{
EndAction(false);
return;
}
// 业务逻辑判断
bool result = /* 你的判断逻辑 */;
EndAction(result);
}
}
}
```
## 创建新的 AI 执行节点模板
```csharp
using System;
using Logic.AI;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
namespace NodeCanvas.Tasks.Actions
{
[Name("执行描述(中文)")]
[Category("AI节点")]
[Serializable]
public class AIAction{Name} : BaseActionTask
{
protected override string desc => "执行描述";
protected override void OnExecute()
{
base.OnExecute();
var data = blackboard.GetVariable<AICalculatorData>("Data");
if (data?.value == null)
{
EndAction(false);
return;
}
// 执行逻辑...
EndAction(true);
}
}
}
```
## 创建条件节点模板(使用 BaseCondition较少使用
```csharp
using System;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
namespace NodeCanvas.Tasks.Actions
{
[Name("条件描述")]
[Category("AI节点")]
[Serializable]
public class AICondition{Name} : BaseCondition
{
protected override string desc => "条件描述";
protected override bool OnCheck()
{
// 返回 true/false
return true;
}
}
}
```
## Blackboard 数据访问
```csharp
// 获取AI计算数据最常用
var data = blackboard.GetVariable<AICalculatorData>("Data");
// AICalculatorData 包含:
// data.value.TargetParam.UnitData - 当前评估的目标单位
// data.value.Map - 当前地图数据
// data.value.AIActions - 可用的AI行为列表
// data.value.MaxAiAction - 评分最高的行为
```
## 必需 Attributes
| Attribute | 说明 | 必需 |
|-----------|------|------|
| `[Name("中文名")]` | 在NodeCanvas编辑器中显示的名字 | 推荐 |
| `[Category("AI节点")]` | 节点分类,必须是"AI节点" | **必需** |
| `[Serializable]` | 序列化支持 | **必需** |
## 命名规范
| 类型 | 格式 | 示例 |
|------|------|------|
| 判断节点 | `AIParam{描述}` | `AIParamHealth`, `AIParamLevel` |
| 执行节点 | `AI{Action描述}` | `AIExecuteAction`, `AICalculateAction` |
| 条件节点 | `AIParam{描述}``AICondition{描述}` | - |
| Foreach | `AIForeach{Start/End/Create}` | - |
## 关键注意事项
1. **base.OnExecute() 必须调用** - 它设置 `MainEditor.Instance.BTNodeId`
2. **EndAction() 必须调用** - 否则节点会卡住
3. **null检查** - data、value、UnitData 都需要逐级检查
4. **命名空间** - 固定使用 `NodeCanvas.Tasks.Actions`
5. **数据访问** - 通过 `Table.Instance` 访问配置表数据
6. **大部分"判断"节点用 BaseActionTask** - 通过 EndAction(bool) 返回结果
## 现有节点参考
- `AIParamHealth.cs` - 判断自身血量(典型判断节点模板)
- `AIExecuteAction.cs` / `AIExcuteAction.cs` - 执行行为
- `AICalculateAction.cs` - 评分策略
- `AIFinishAction.cs` - 结束行为
- `AIForeachStart.cs` / `AIForeachEnd.cs` - 循环控制

View File

@ -0,0 +1,209 @@
---
name: "th1-anim-scope"
description: "TH1攻击动画Scope-Aware模式速查。当修改SkillBase的OnDamageOther/OnDamaged等攻击相关生命周期中的视觉更新代码时必须使用此skill确保视觉更新延迟到攻击Fragment的正确阶段播放而不是在逻辑计算期间直接刷新Renderer。涵盖PendingAnimScope、FragmentStep、AnimPhase、step-list架构的完整用法。"
---
# TH1 攻击动画 Scope-Aware 模式速查
## 核心问题
TH1中`UnitAttackAction.Execute()` 的执行顺序是:
```
1. 完整逻辑运算数据变异扣血、加debuff等
2. 创建攻击 Fragment
3. 入队播放动画
```
逻辑运算期间,`DamageSettlement()` 会触发 SkillBase 的生命周期回调(`OnDamageOther``OnDamaged` 等)。如果这些回调中**直接调用** `InstantUpdateUnit()``RenderUpdateUnitImage()`会导致视觉状态血量、debuff图标在弹道动画播放之前就已经刷新——玩家先看到血量变化再看到弹道飞过去时序错误。
## 解决方案PendingAnimScope
`PendingAnimScope` 是一个 `IDisposable` 作用域对象,在 `UnitAttackAction.Execute()` 中通过 `using` 使用。技能回调中产生的视觉步骤不再直接执行,而是收集到 scope 中,等攻击 Fragment 创建后再注入。
### 时序(修复后)
```
1. scope = PresentationManager.BeginScope()
2. UnitLogic.Attack() → DamageSettlement()
├── target.Health -= dmg ← 数据变异
├── origin.OnDamageOther() ← 技能回调
│ └── scope.Add(FragmentStep) ← 延迟收集,不直接刷新
└── target.OnDamaged() ← 技能回调
└── scope.Add(FragmentStep) ← 延迟收集
3. Fragment 创建(如 FragmentAttack / FragmentAttackAndCounter
4. scope.FlushTo(fragment) ← 将收集的步骤注入 Fragment
5. PresentationManager.EnqueueTask(fragment)
6. scope.Dispose() ← using 自动清理
```
### Fragment 播放顺序
```
Phase 100 (AttackStart) → 弹道飞行 / 近战冲刺
Phase 200 (AttackImpact) → 命中:血量刷新 + 受伤弹跳 + VFX
Phase 250 (注入的步骤) → 技能视觉效果debuff图标等
Phase 300 (AttackReturn) → 间隔等待
Phase 400 (CounterStart) → 反击弹道
Phase 500 (CounterImpact) → 反击命中
Phase 550 (注入的步骤) → 反击阶段的技能视觉效果
Phase 600 (CounterReturn) → 反击返回
Phase 700 (Settle) → 收尾:全量刷新双方状态
```
## Scope-Aware 技能代码模板
当 SkillBase 的攻击相关生命周期回调需要刷新 Renderer 时,使用以下模板:
### 必要 using
```csharp
using TH1_Anim.Fragments;
using TH1_Core.Managers;
using TH1_Logic.Core;
using TH1Renderer;
```
### OnDamageOther 模板(攻击方技能)
```csharp
public override void OnDamageOther(MapData mapData, SettlementInfo info)
{
if (info.DamageTarget == null || info.DamageOrigin == null) return;
if (info.DamageType != DamageType.ActiveAttack
&& info.DamageType != DamageType.CounterAttack) return;
// === 逻辑部分 ===
info.DamageTarget.AddOrOverrideSkill(SkillType.XXX, mapData, info.DamageOrigin.Id);
// === 视觉部分scope-aware ===
if (mapData != Main.MapData) return; // AI预测不做视觉
if (!info.DamageTarget.IsAlive()) return; // 死亡单位不刷新
// 根据攻击类型选择正确的 phase
int phase = info.DamageType == DamageType.ActiveAttack
? AnimPhase.AttackImpact + 50 // 250攻击命中后
: AnimPhase.CounterImpact + 50; // 550反击命中后
var scope = PresentationManager.CurrentScope;
if (scope != null)
{
// 在攻击流程中:延迟注入到攻击 Fragment
if (MapRenderer.Instance != null &&
MapRenderer.Instance.ROUnitMap.TryGetValue(
info.DamageTarget.Id, out var unitRenderer))
{
scope.Add(new FragmentStep
{
Phase = phase,
Duration = 0.1f,
Execute = () => { unitRenderer.InstantUpdateUnit(false); }
});
}
}
else
{
// 不在攻击流程中:直接刷新(如其他逻辑触发)
if (MapRenderer.Instance != null &&
MapRenderer.Instance.ROUnitMap.TryGetValue(
info.DamageTarget.Id, out var unitRenderer))
{
unitRenderer.RenderUpdateUnitImage();
}
}
}
```
### OnDamaged 模板(被攻击方技能)
`OnDamageOther` 结构相同,注意目标单位 ID 的区别。
### EnqueueSkillEffect 模板(复杂多步视觉效果)
当技能需要播放 VFX + 单位刷新 + 死亡等多步骤效果时,使用 `PresentationManager.EnqueueSkillEffect()`,它内部已经是 scope-aware 的:
```csharp
var visualSteps = new List<SkillVisualStep>();
visualSteps.Add(new SkillVisualStep
{
GridRenderer = grid.Renderer(mapData),
UnitRenderer = unit.Renderer(mapData),
VFXList = new List<GridVFXParams> { new GridVFXParams(GridVFXType.Hurt) },
RefreshUnit = true,
KillUnit = false,
RefreshGrid = true,
});
PresentationManager.EnqueueSkillEffect(visualSteps, stepInterval: 0.15f);
```
`EnqueueSkillEffect` 在 scope 模式下会自动转为 `FragmentStep` 注入 scope非 scope 模式下走原有的直接入队逻辑。
## AnimPhase 常量
定义在 `TH1_Anim/Fragments/AnimPhase.cs`
| 常量 | 值 | 含义 |
|------|-----|------|
| `AttackStart` | 100 | 攻击弹道起飞 |
| `AttackImpact` | 200 | 攻击命中 |
| `AttackReturn` | 300 | 攻击返回/间隔 |
| `CounterStart` | 400 | 反击弹道起飞 |
| `CounterImpact` | 500 | 反击命中 |
| `CounterReturn` | 600 | 反击返回 |
| `Settle` | 700 | 收尾 |
技能注入步骤通常使用 `Phase + 50` 以排在原生步骤之后。
## 关键文件索引
| 文件 | 路径 | 职责 |
|------|------|------|
| PendingAnimScope | `TH1_Anim/Fragments/PendingAnimScope.cs` | 延迟步骤收集器 |
| FragmentStep | `TH1_Anim/Fragments/FragmentStep.cs` | 单步骤数据 |
| AnimPhase | `TH1_Anim/Fragments/AnimPhase.cs` | 阶段常量 |
| FragmentBase | `TH1_Anim/Fragments/FragmentBase.cs` | step-list 基础设施 |
| FragmentAttack | `TH1_Anim/Fragments/FragmentAttack.cs` | 纯攻击Fragment |
| FragmentAttackAndCounter | `TH1_Anim/Fragments/FragmentAttackAndCounter.cs` | 攻击+反击Fragment |
| FragmentSkillEffect | `TH1_Anim/Fragments/FragmentSkillEffect.cs` | 通用技能视觉Fragment |
| PresentationManager | `TH1_Core/Managers/PresentationManager.cs` | Scope管理 + EnqueueSkillEffect |
| UnitAttackAction | `TH1_Logic/Action/ActionLogic.cs` | scope使用入口 |
## 判断清单:何时需要 Scope-Aware
当你在 SkillBase 中看到以下调用,且该回调可能在攻击流程中被触发时,必须改为 scope-aware
- `unitRenderer.InstantUpdateUnit()`
- `unitRenderer.RenderUpdateUnitImage()`
- `unitRenderer.RenderUpdateUnitInfo()`
- `PresentationManager.EnqueueSkillEffect()`(已内置支持,无需手动处理)
- `PresentationManager.EnqueueTask()`直接入队Fragment
涉及的生命周期回调:
- `OnDamageOther` / `OnDamaged` / `AfterDamageOther`
- `BeforeDamageOther` / `BeforeDamagedSupportStage` / `BeforeDamagedTransformStage`
- `BeforeUnitDamaged`MapData级别遍历所有单位
## 已完成 Scope-Aware 改造的技能
| 技能 | 文件 | 回调 |
|------|------|------|
| SatoriAttackSkill | `AllSkill/SatoriAttackSkill.cs` | OnDamageOther |
| SatoriAttackBoomSkill | `AllSkill/SatoriAttackBoomSkill.cs` | OnDamageOther |
| SatoriBanSkill | `AllSkill/SatoriBanSkill.cs` | OnDamageOther |
| FearMakerSkill | `AllSkill/FearMakerSkill.cs` | OnDamageOther |
| SatoriSeeSkill | `AllSkill/SatoriSeeSkill.cs` | BeforeUnitDamaged |
## 尚未改造的技能(可能需要按需更新)
| 技能 | 文件 | 回调 | 直接调用 |
|------|------|------|----------|
| VampireSkill | `AllSkill/VampireSkill.cs` | OnDamageOther | InstantUpdateUnit |
| VampireProSkill | `AllSkill/VampireProSkill.cs` | OnDamageOther | InstantUpdateUnit |
| SuperDashSkill | `AllSkill/SuperDashSkill.cs` | OnDamageOther | InstantUpdateUnit |
| EscapeSkill | `AllSkill/EscapeSkill.cs` | OnDamageOther | InstantUpdateUnit |
| EscapeProSkill | `AllSkill/EscapeProSkill.cs` | OnDamageOther | InstantUpdateUnit |
| SanaeDivine_E3_HP_Skill | `AllSkill/SanaeDivine/...` | OnDamaged | InstantUpdateUnit |
| SplashSkill | `AllSkill/SplashSkill.cs` | OnDamageOther | InstantUpdateUnit |
| StompSkill | `AllSkill/StompSkill.cs` | OnDamageOther | InstantUpdateUnit |
| PathStompSkill | `AllSkill/PathStompSkill.cs` | OnDamageOther | InstantUpdateUnit |
| SkillBanBombSkill | `AllSkill/SkillBanBombSkill.cs` | OnDamaged | RenderUpdateUnitImage |

View File

@ -0,0 +1,110 @@
---
name: "th1-architecture"
description: "TH1项目架构总览包含模块结构、数据流、事件系统、关键管理器和文件索引"
---
# TH1 项目架构总览
## 概述
TH1 是一个 Unity 回合制策略游戏东方Project同人游戏使用 ET Framework 架构。
## 模块结构
| 模块 | 路径 | 职责 |
|------|------|------|
| `TH1_Core` | `TH1_Core/` | 核心管理器EventManager, UIManager, PresentationManager |
| `TH1_UI` | `TH1_UI/` | UI系统View-Controller 模式 |
| `TH1_Logic` | `TH1_Logic/` | 游戏逻辑AI, Skills, City/Unit/Player |
| `TH1_Data` | `TH1_Data/` | 数据类PlayerData, MapData, UnitData, GridData |
| `TH1_Renderer` | `TH1_Renderer/` | 视觉渲染MapRenderer, UnitRenderer, GridRenderer |
| `TH1_Config` | `TH1_Config/` | 配置系统Excel生成 + Partial扩展 |
| `TH1_Instance` | `TH1_Instance/` | 实例管理和基类 |
| `TH1_Presentation` | `TH1_Presentation/` | 表现层序列系统 |
| `TH1_Anim` | `TH1_Anim/` | 动画工具Animancer |
| `TH1_Audio` | `TH1_Audio/` | 音频管理 |
| `TH1_Resource` | `TH1_Resource/` | 资源缓存SpriteCache, AnimCache |
| `BTNodeCanvas` | `BTNodeCanvas/` | AI行为树节点NodeCanvas框架 |
## 数据流
```
Excel配置 → GenerateCS → MemoryPack序列化 → Runtime
用户输入 → InputLogic → GameLogic/UnitLogic/CityLogic → MapData → EventManager → UI更新 → Renderer
```
### 运行时数据访问
```csharp
// 地图数据(全局静态)
var mapData = Main.MapData;
// 玩家自身数据
var playerData = PlayerData.SelfPlayerData;
// 通过地图数据访问单位
mapData.GetUnitData(unitId);
// 通过地图数据获取玩家
mapData.GetPlayerDataByUnitId(unitId, out var player);
```
## 事件系统
### 全局事件EventManager
```csharp
// 订阅
EventManager.Instance.AddListener<EventType>(callback);
// 取消
EventManager.Instance.RemoveListener<EventType>(callback);
```
### UI事件UIEvents
定义在 `TH1_Core/Events/UIEvents.cs`,使用 struct 作为事件类型:
```csharp
// 事件定义格式
public struct ShowUI{Category}{Name} { /* 参数 */ }
public struct HideUI{Category}{Name} { }
```
## 关键管理器(单例模式)
| 管理器 | 路径 | 职责 |
|--------|------|------|
| `UIManager` | `TH1_Core/Managers/UIManager.cs` | UI根管理 |
| `EventManager` | `TH1_Core/Managers/EventManager.cs` | 全局事件分发 |
| `PresentationManager` | `TH1_Core/Managers/PresentationManager.cs` | 表现层序列 |
| `AudioManager` | `TH1_Logic/Core/` | 音频播放 |
| `ResourceCache` | `TH1_Resource/` | 精灵/动画缓存 |
| `PrefabPoolManager` | `TH1_Logic/PrefabPool/` | 对象池 |
| `Main` | `TH1_Logic/Core/Main.cs` | 游戏入口和单例 |
| `Table.Instance` | 配置表 | 运行时配置数据 |
## 关键文件索引
| 文件 | 路径 |
|------|------|
| ViewControllerManager | `TH1_UI/Core/ViewControllerManager.cs` |
| UIResourceName | `TH1_UI/Core/UIResourceName.cs` |
| UIEvents | `TH1_Core/Events/UIEvents.cs` |
| View基类 | `TH1_UI/View/Base/View.cs` |
| ViewController基类 | `TH1_UI/Controller/Base/ViewController.cs` |
| SkillBase | `TH1_Logic/Skill/SkillBase.cs` |
| SkillFactory | `TH1_Logic/Skill/SkillFactory.cs` |
| BaseActionTask | `BTNodeCanvas/BaseActionTask.cs` |
| BaseCondition | `BTNodeCanvas/BaseCondition.cs` |
| AICalculatorData | `TH1_Logic/AI/AIActionBase.cs` |
| GenerateCS目录 | `TH1_Config/GenerateCS/` |
| ExcelPartial目录 | `TH1_Config/ExcelPartial/` |
## 序列化规范MemoryPack
- 数据类使用 `[MemoryPackable]` + `partial class`
- 需要序列化的字段用 `[MemoryPackInclude]`
- 不序列化的字段用 `[MemoryPackIgnore]`
- 生成类必须有 `[MemoryPackConstructor]` 标记的无参构造函数
## 命名约定
- 文件名 = 类名(每个主要类一个文件)
- 命名空间跟随目录结构
- `.meta` 文件必须提交到版本控制
- 注释使用中文

View File

@ -0,0 +1,173 @@
---
name: "th1-config-guide"
description: "TH1配置系统指南包含GenerateCS/ExcelPartial双文件模式、MemoryPack规则和扩展模板"
---
# TH1 配置系统指南
## 配置系统架构
TH1 使用 Excel → 代码生成 → MemoryPack 序列化的配置流水线。
```
Excel表格 → 工具生成 → GenerateCS/*.cs自动生成不可修改
ExcelPartial/*.cs手动扩展可修改
```
## 目录结构
| 目录 | 说明 | 可否修改 |
|------|------|---------|
| `TH1_Config/GenerateCS/` | Excel自动生成的C#代码 | **禁止修改** |
| `TH1_Config/ExcelPartial/` | 手动编写的partial class扩展 | 可以修改 |
## GenerateCS 文件格式(不可修改)
```csharp
using MemoryPack;
using System;
using System.Collections.Generic;
namespace ExcelConfig
{
[MemoryPackable]
public partial class {Name}Category : ExcelConfigBase
{
[MemoryPackInclude]
public static Dictionary<int, {Name}> Dict { get; set; } = new();
public override void Init(byte[] data)
{
Dict = MemoryPackSerializer.Deserialize<Dictionary<int, {Name}>>(data);
}
}
[MemoryPackable]
public partial class {Name}
{
/// <summary>字段说明</summary>
[MemoryPackInclude]
public int Id { get; set; }
// ... 其他字段 ...
[MemoryPackConstructor]
public {Name}() { }
}
}
```
## ExcelPartial 扩展模板
当需要对配置数据进行后处理或添加运行时计算属性时,在 `ExcelPartial/` 中创建 partial class
### Category 扩展(后处理)
```csharp
using MemoryPack;
namespace ExcelConfig
{
public partial class {Name}Category
{
public override void AfterInit()
{
base.AfterInit();
// 在这里进行数据后处理
// 例如:建立索引、计算派生数据、验证数据
foreach (var kvp in Dict)
{
var config = kvp.Value;
// 后处理逻辑...
}
}
}
}
```
### 数据类扩展(添加计算属性)
```csharp
using MemoryPack;
namespace ExcelConfig
{
public partial class {Name}
{
// 运行时计算的属性,不序列化
[MemoryPackIgnore]
public float CalculatedValue => /* 计算逻辑 */;
// 运行时缓存字段,不序列化
[MemoryPackIgnore]
private List<int> _cachedList;
// 自定义方法
public bool IsSpecialType()
{
return /* 判断逻辑 */;
}
}
}
```
## MemoryPack 规则
### 必须遵守的规则
1. **`[MemoryPackable]`** - 所有需要序列化的类都必须标记
2. **`partial class`** - MemoryPack 要求 partial 类
3. **`[MemoryPackConstructor]`** - 反序列化用的无参构造函数
4. **`[MemoryPackInclude]`** - 标记需要序列化的字段/属性
5. **`[MemoryPackIgnore]`** - 标记不需要序列化的字段(**ExcelPartial中新增的字段必须用此标记**
### 常见错误
```csharp
// ❌ 错误ExcelPartial中新增字段没有标记MemoryPackIgnore
public partial class SomeConfig
{
public int NewField; // 会导致反序列化失败!
}
// ✅ 正确:
public partial class SomeConfig
{
[MemoryPackIgnore]
public int NewField; // 不参与序列化,仅运行时使用
}
```
## 配置数据访问
```csharp
// 通过 Table.Instance 访问
var aiConfig = AIConfigCategory.Dict[configId];
// 通过 Table.Instance 的特定方法
Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitType, giantType, level, out var info);
```
## 创建新配置扩展的步骤
1. 确认 `GenerateCS/` 中已有对应的生成代码
2. 在 `ExcelPartial/` 中创建 `{Name}Partial.cs`
3. 使用 `partial class` 关键字,命名空间必须是 `ExcelConfig`
4. 所有新增字段必须标记 `[MemoryPackIgnore]`
5. 通过 `AfterInit()` 进行初始化后处理
## 文件命名规范
| 类型 | 生成文件 | 扩展文件 |
|------|---------|---------|
| AI配置 | `GenerateCS/AIConfig.cs` | `ExcelPartial/AIConfigPartial.cs` |
| 其他配置 | `GenerateCS/{Name}.cs` | `ExcelPartial/{Name}Partial.cs` |
## 注意事项
- **永远不要修改 GenerateCS/ 中的文件** - 下次生成会覆盖
- ExcelPartial 中的 `AfterInit()` 在所有配置加载完成后调用
- 如果需要跨配置表的关联,在 `AfterInit()` 中建立引用
- 注意 Dictionary 的 Key 类型(通常是 int与 Excel 中的 Id 列对应

View File

@ -0,0 +1,216 @@
---
name: "th1-ui-patterns"
description: "TH1 UI开发模式速查包含View-Controller完整5步创建流程、命名规范和检查清单"
---
# TH1 UI 开发模式速查
## UI 架构概述
TH1 使用 View-Controller 模式(类似 MVC分为
- **View**MonoBehaviour挂在 prefab 上,管理视觉和动画
- **Controller**:纯 C# 类,管理数据绑定、事件注册和业务逻辑
## 创建新 UI 面板 - 完整5步流程
### Step 1: 创建 View 类
路径:`TH1_UI/View/{Category}/UI{Category}{Name}View.cs`
```csharp
using TH1_UI.View.Base;
using UnityEngine;
using UnityEngine.UI;
namespace TH1_UI.View.{Category}
{
public class UI{Category}{Name}View : View
{
// 1. 声明UI组件引用在Inspector中绑定
public Button CloseButton;
public Button BlockButton;
// 2. 声明委托事件供Controller绑定
public ViDelegateAssisstant.Dele OnBtnCloseClick;
// 3. 重写OnInit初始化
protected override void OnInit()
{
base.OnInit(); // 必须调用初始化Animancer和CanvasGroup
CloseButton.onClick.RemoveAllListeners();
CloseButton.onClick.AddListener(() => { OnBtnCloseClick.Invoke(); });
BlockButton.onClick.RemoveAllListeners();
BlockButton.onClick.AddListener(() => { OnBtnCloseClick.Invoke(); });
}
// 4. 数据设置方法
public void SetContent(/* 参数 */)
{
// 设置UI内容
}
// 5. 关闭时清理(可选)
public void CloseView()
{
// 清理工作
}
}
}
```
### Step 2: 创建 Controller 类
路径:`TH1_UI/Controller/{Category}/UI{Category}{Name}Controller.cs`
```csharp
using TH1_Core.Events;
using TH1_UI.Controller.Base;
using TH1_UI.View.{Category};
using UnityEngine;
namespace TH1_UI.Controller.{Category}
{
public class UI{Category}{Name}Controller : ViewController<UI{Category}{Name}View>
{
// 必须:空构造函数(满足 new() 泛型约束)
public UI{Category}{Name}Controller() { }
// 注册事件回调View打开时调用
protected override void RegisterEventCallback()
{
base.RegisterEventCallback();
if (WindowScript != null)
{
WindowScript.OnBtnCloseClick += _OnBtnCloseClick;
}
}
// 取消注册View关闭时调用
protected override void UnregisterEventCallback()
{
if (WindowScript != null)
{
WindowScript.OnBtnCloseClick = null;
}
base.UnregisterEventCallback();
}
// 打开时的逻辑
protected override void OnOpen()
{
base.OnOpen();
if (_openParameter is ShowUI{Category}{Name} evt)
{
if (WindowScript != null)
{
WindowScript.SetContent(/* 从evt取参数 */);
}
}
}
// 关闭时的逻辑
public override bool Close()
{
WindowScript?.CloseView();
return base.Close();
}
void _OnBtnCloseClick()
{
Close();
}
}
}
```
### Step 3: 注册 UIResourceName
文件:`TH1_UI/Core/UIResourceName.cs`
```csharp
public static readonly string View{Category}{Name} = "UI{Category}{Name}";
```
### Step 4: 定义 UIEvents
文件:`TH1_Core/Events/UIEvents.cs`
在对应的 Category 区域添加:
```csharp
//---------------------------------------- UI{Category} 相关的事件 -----------------------------
public struct ShowUI{Category}{Name} { /* 打开参数 */ }
public struct HideUI{Category}{Name} { }
```
### Step 5: 注册到 ViewControllerManager3处修改
文件:`TH1_UI/Core/ViewControllerManager.cs`
```csharp
// ① 添加公共属性 getter在对应 Category 区域的属性段)
public static UI{Category}{Name}Controller UI{Category}{Name}Controller { get { return _{camelCase}Controller; } }
// ② 在 _CreateAllViewControllers() 中添加创建代码
_{camelCase}Controller = _CreateView<UI{Category}{Name}Controller>("{Category}", UIResourceName.View{Category}{Name}, ViewDestroyDomain.None, UIRootType.{RootType});
// ③ 添加私有静态字段(在文件底部字段区域)
private static UI{Category}{Name}Controller _{camelCase}Controller = null;
```
## Category → UIRootType 映射表
| Category | UIRootType | SubFolderPath参数 | 说明 |
|----------|-----------|-------------------|------|
| Top | TopUI | "Top" | 顶部UI设置、胜利等 |
| Bottom | BottomUI | "Bottom" | 底部UI操作栏等 |
| Info | InfoUI | "Info" | 信息面板(外交、英雄等) |
| Notify | NotifyUI | "Notify" | 通知面板 |
| Outside | OutsideUI | "Outside" | 对局外UI菜单、多人等 |
| Outside(顶层) | OutsideTopUI | "Outside" | 对局外顶层UI |
| Announce | PresentationUI | "Presentation/Announce" | 公告表现 |
| Interaction | PresentationUI | "Presentation/Interaction" | 交互表现 |
## 命名规范
| 元素 | 格式 | 示例 |
|------|------|------|
| View类 | `UI{Category}{Name}View` | `UITopSettingView` |
| Controller类 | `UI{Category}{Name}Controller` | `UITopSettingController` |
| 资源名常量 | `View{Category}{Name}` | `ViewTopSetting` |
| Show事件 | `ShowUI{Category}{Name}` | `ShowUITopSetting` |
| Hide事件 | `HideUI{Category}{Name}` | `HideUITopSetting` |
| 私有字段 | `_{camelCase}Controller` | `_topSettingController` |
## View 基类关键方法
| 方法 | 说明 |
|------|------|
| `OnInit()` | 初始化,**必须调用 base.OnInit()** |
| `Show()` | 显示(播放打开动画) |
| `Hide(Action callback)` | 隐藏(播放关闭动画后回调) |
| `IsShow()` | 是否正在显示 |
## Controller 基类关键方法
| 方法 | 说明 |
|------|------|
| `OnLoaded()` | prefab加载完毕 |
| `OnOpen()` | 面板打开时 |
| `OnClose()` | 面板关闭时 |
| `RegisterEventCallback()` | 注册事件Open时自动调用 |
| `UnregisterEventCallback()` | 取消注册Close时自动调用 |
| `UpdateView()` | 刷新面板数据 |
| `OnMatchStart()` | 新对局开始时 |
| `WindowScript` | 获取关联的View实例 |
| `_openParameter` | Open时传入的参数 |
## 完成后检查清单
- [ ] View类继承自 `View`,调用了 `base.OnInit()`
- [ ] Controller类继承自 `ViewController<T>`,有空构造函数
- [ ] UIResourceName 中添加了资源名常量
- [ ] UIEvents 中定义了 Show/Hide 事件结构体
- [ ] ViewControllerManager 中完成3处注册属性、创建、字段
- [ ] using 引用了正确的命名空间
- [ ] ViewControllerManager 顶部添加了 Controller 的 using 语句

View File

@ -0,0 +1,163 @@
description = "TH1项目AI行为树节点开发专家负责创建和调试NodeCanvas行为树自定义节点。当需要创建AI判断节点、执行节点或调试AI行为时使用此agent。"
developer_instructions = """
# AI 行为树节点专家\r
\r
## 角色\r
\r
TH1 AI NodeCanvas \r
\r
## 核心理解\r
\r
### 关键设计决策\r
\r
**"判断"使 `BaseActionTask` `BaseCondition`**\r
\r
`EndAction(true/false)` `OnCheck()`使 `BaseCondition`\r
\r
### 节点创建流程\r
\r
1. //\r
2. 访\r
3. `BTNodeCanvas/` \r
4. 使 Attributes\r
5. `OnExecute()` \r
\r
### 判断节点模板(最常用)\r
\r
```csharp\r
using System;\r
using Logic.AI;\r
using NodeCanvas.Framework;\r
using ParadoxNotion.Design;\r
\r
namespace NodeCanvas.Tasks.Actions\r
{\r
[Name("判断描述(中文)")]\r
[Category("AI节点")]\r
[Serializable]\r
public class AIParam{Name} : BaseActionTask\r
{\r
// \r
public bool GreaterThan = true;\r
public float Threshold = 0.5f;\r
\r
protected override string desc\r
{\r
get\r
{\r
if (GreaterThan) return $"条件 >= {Threshold}";\r
return $"条件 <= {Threshold}";\r
}\r
}\r
\r
protected override void OnExecute()\r
{\r
base.OnExecute(); // \r
\r
var data = blackboard.GetVariable<AICalculatorData>("Data");\r
if (data?.value?.TargetParam.UnitData == null)\r
{\r
EndAction(false); // \r
return;\r
}\r
\r
var unit = data.value.TargetParam.UnitData;\r
// ...\r
EndAction(/* bool result */);\r
}\r
}\r
}\r
```\r
\r
### 执行节点模板\r
\r
```csharp\r
using System;\r
using Logic.AI;\r
using NodeCanvas.Framework;\r
using ParadoxNotion.Design;\r
\r
namespace NodeCanvas.Tasks.Actions\r
{\r
[Name("执行描述(中文)")]\r
[Category("AI节点")]\r
[Serializable]\r
public class AIAction{Name} : BaseActionTask\r
{\r
protected override string desc => "执行描述";\r
\r
protected override void OnExecute()\r
{\r
base.OnExecute();\r
\r
var data = blackboard.GetVariable<AICalculatorData>("Data");\r
if (data?.value == null)\r
{\r
EndAction(false);\r
return;\r
}\r
\r
// ...\r
EndAction(true);\r
}\r
}\r
}\r
```\r
\r
## 必需 Attributes 检查清单\r
\r
```csharp\r
[Name("中文描述名")] // NodeCanvas\r
[Category("AI节点")] // \r
[Serializable] // \r
```\r
\r
## Blackboard 数据访问\r
\r
```csharp\r
// \r
var data = blackboard.GetVariable<AICalculatorData>("Data");\r
\r
// \r
data.value.TargetParam.UnitData // \r
data.value.Map // (MapData)\r
data.value.AIActions // AI\r
data.value.MaxAiAction // \r
\r
// 访\r
Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitType, giantType, level, out var info);\r
```\r
\r
## 命名规范\r
\r
| | | |\r
|------|------|------|\r
| | `AIParam` | `AIParamHealth`, `AIParamLevel` |\r
| | `AI` + | `AIExecuteAction`, `AICalculateAction` |\r
| | `AIForeach` | `AIForeachStart`, `AIForeachEnd` |\r
\r
## 常见错误\r
\r
1. ** `base.OnExecute()`** \r
2. ** `EndAction()`** \r
3. ** `[Category("AI节点")]`** \r
4. ** `[Serializable]`** \r
5. ** null ** NullReferenceException \r
6. **** `NodeCanvas.Tasks.Actions`\r
\r
## 调试支持\r
\r
- `desc` NodeCanvas \r
- `MainEditor.Instance.BTNodeId` `base.OnExecute()` \r
- `#if UNITY_EDITOR` 块中的 `AIRecord` 用于AI调试记录\r
- F10/F11 AI \r
\r
## 参考文件\r
\r
- `BTNodeCanvas/BaseActionTask.cs` - \r
- `BTNodeCanvas/BaseCondition.cs` - \r
- `BTNodeCanvas/AIParamHealth.cs` - \r
- `BTNodeCanvas/AIExcuteAction.cs` - \r
- `BTNodeCanvas/AICalculateAction.cs` - \r
- `TH1_Logic/AI/AIActionBase.cs` - AICalculatorData """
name = "ai-designer"

View File

@ -0,0 +1,97 @@
description = "TH1项目代码审查专家负责对代码变更进行全面审查。只读agent不进行代码修改。当需要审查代码变更、检查项目规范合规性时使用此agent。"
developer_instructions = """
# 代码审查专家\r
\r
## 角色\r
\r
TH1 ****\r
\r
## 审查流程\r
\r
1. `git diff` `git status`\r
2. \r
3. \r
4. \r
\r
## 按域分组的审查清单\r
\r
### UI 审查清单\r
- [ ] View `View``OnInit()` `base.OnInit()`\r
- [ ] Controller `ViewController<T>`\r
- [ ] UIResourceName \r
- [ ] UIEvents Show/Hide \r
- [ ] ViewControllerManager 3getter\r
- [ ] ViewControllerManager using\r
- [ ] Category UIRootType \r
- [ ] `UI{Category}{Name}View/Controller` \r
- [ ] RegisterEventCallback / UnregisterEventCallback \r
\r
### 技能审查清单\r
- [ ] SkillBase使 `[MemoryPackable]` + `partial class`\r
- [ ] `[MemoryPackConstructor]` \r
- [ ] SkillType \r
- [ ] `GetSkillType()` SkillType\r
- [ ] SkillFactory \r
- [ ] GetAttackMultiplicationParam `1` `0`\r
- [ ] `BeforeActiveAttackOther` out `addDmg = 0`\r
- [ ] `[MemoryPackInclude]`\r
\r
### AI 节点审查清单\r
- [ ] `[Category("AI节点")]` `[Serializable]` \r
- [ ] `NodeCanvas.Tasks.Actions`\r
- [ ] `OnExecute()` `base.OnExecute()`\r
- [ ] `EndAction()`\r
- [ ] null \r
- [ ] `desc` \r
- [ ] 使 BaseActionTask BaseCondition\r
\r
### 配置审查清单\r
- [ ] `GenerateCS/` \r
- [ ] ExcelPartial 使 `partial class`\r
- [ ] `ExcelConfig`\r
- [ ] `[MemoryPackIgnore]`\r
- [ ] `AfterInit()` `base.AfterInit()`\r
\r
### 通用审查清单\r
- [ ] NullReferenceException \r
- [ ] \r
- [ ] magic number使\r
- [ ] \r
- [ ] \r
- [ ] \r
\r
## 输出格式\r
\r
```markdown\r
# 代码审查报告\r
\r
## 概要\r
- X\r
- YA / B / C\r
\r
## 严重问题 (必须修复)\r
### [文件名:行号] 问题标题\r
- ****\r
- ****\r
- ****\r
\r
## 警告 (建议修复)\r
### [文件名:行号] 问题标题\r
- ****\r
- ****\r
\r
## 建议 (可选改进)\r
### [文件名] 改进建议\r
- ****\r
\r
## 审查通过项 ✅\r
- \r
```\r
\r
## 注意事项\r
\r
- ****\r
- \r
- \r
- `.meta` """
name = "code-reviewer"

View File

@ -0,0 +1,121 @@
description = "TH1项目配置系统管理专家负责管理Excel生成的配置代码和partial class扩展。当需要添加配置后处理、运行时计算属性或处理MemoryPack序列化问题时使用此agent。"
developer_instructions = """
# 配置系统管理专家\r
\r
## 角色\r
\r
TH1 Excel partial class \r
\r
## 核心规则\r
\r
### 1. GenerateCS 不可修改\r
\r
`TH1_Config/GenerateCS/` Excel ****\r
\r
### 2. 扩展只在 ExcelPartial 中进行\r
\r
`TH1_Config/ExcelPartial/` \r
\r
### 3. MemoryPack 约束\r
\r
ExcelPartial **** `[MemoryPackIgnore]`\r
\r
## 工作流程\r
\r
### 添加配置后处理\r
\r
Excel \r
\r
1. `GenerateCS/` `{Name}Category` \r
2. `ExcelPartial/` `{Name}Partial.cs`\r
3. 使 partial class `{Name}Category`\r
4. `AfterInit()` \r
\r
```csharp\r
using MemoryPack;\r
\r
namespace ExcelConfig\r
{\r
public partial class {Name}Category\r
{\r
public override void AfterInit()\r
{\r
base.AfterInit();\r
\r
// \r
foreach (var kvp in Dict)\r
{\r
var config = kvp.Value;\r
// \r
}\r
}\r
}\r
}\r
```\r
\r
### 添加运行时计算属性\r
\r
\r
\r
```csharp\r
using MemoryPack;\r
\r
namespace ExcelConfig\r
{\r
public partial class {Name}\r
{\r
// [MemoryPackIgnore]\r
[MemoryPackIgnore]\r
public float CalculatedValue => SomeField * 0.5f;\r
\r
[MemoryPackIgnore]\r
private Dictionary<int, List<int>> _indexCache;\r
\r
public void BuildIndex()\r
{\r
_indexCache = new Dictionary<int, List<int>>();\r
// ...\r
}\r
}\r
}\r
```\r
\r
## 完成后检查清单\r
\r
- [ ] `GenerateCS/` \r
- [ ] `ExcelPartial/` \r
- [ ] `ExcelConfig`\r
- [ ] 使 `partial class` \r
- [ ] `[MemoryPackIgnore]`\r
- [ ] `AfterInit()` `base.AfterInit()`\r
- [ ] using `using MemoryPack;`\r
\r
## 数据访问\r
\r
```csharp\r
// 访\r
var config = {Name}Category.Dict[id];\r
\r
// Table.Instance\r
Table.Instance.{}.{}();\r
```\r
\r
## 常见问题\r
\r
### MemoryPack 反序列化失败\r
ExcelPartial `[MemoryPackIgnore]`\r
`[MemoryPackIgnore]`\r
\r
### AfterInit 中空引用\r
\r
使\r
\r
### Dict 为空\r
\r
Excel \r
\r
## 参考文件\r
\r
- `TH1_Config/GenerateCS/AIConfig.cs` - \r
- `TH1_Config/ExcelPartial/AIConfigPartial.cs` - """
name = "config-manager"

127
.codex/agents/debugger.toml Normal file
View File

@ -0,0 +1,127 @@
description = "TH1项目调试专家负责排查Bug、分析异常、定位问题根因。当遇到NullReference、序列化失败、UI不显示、AI异常、技能不生效等问题时使用此agent。"
developer_instructions = """
# 调试专家\r
\r
## 角色\r
\r
TH1 Bug\r
\r
## 常见问题模式及排查路径\r
\r
### 1. NullReferenceException\r
\r
****\r
- UI WindowScript nullprefab 访\r
- MapData 访\r
- View\r
- Dict \r
\r
****\r
1. \r
2. \r
3. Open/Close/Destroy \r
4. `data?.value?.TargetParam.UnitData` 访\r
\r
### 2. MemoryPack 序列化/反序列化失败\r
\r
****\r
- ExcelPartial `[MemoryPackIgnore]`\r
- GenerateCS \r
- `[MemoryPackConstructor]` \r
- MemoryPack \r
\r
****\r
1. `TH1_Config/ExcelPartial/` \r
2. `[MemoryPackIgnore]`\r
3. `GenerateCS/` \r
4. SkillBase `[MemoryPackable]` \r
\r
### 3. UI 不显示 / 显示异常\r
\r
****\r
- ViewControllerManager 3\r
- UIResourceName prefab \r
- prefab CanvasGroup \r
- UIRootType \r
- View `OnInit()` `base.OnInit()`\r
\r
****\r
1. `ViewControllerManager.cs` 3\r
2. `UIResourceName.cs` prefab \r
3. Unity prefab CanvasGroup\r
4. `Show()` / `Hide()` \r
5. `_canvasGroup.alpha` 0\r
\r
### 4. AI 行为树异常 / AI 不行动\r
\r
****\r
- `OnExecute()` `base.OnExecute()`\r
- `EndAction()` \r
- Blackboard `Data` null\r
- `[Serializable]` \r
- null`MaxAiAction == null`\r
\r
****\r
1. `EndAction()`\r
2. `base.OnExecute()` \r
3. 使 F10/F11 AI\r
4. `AIRecord` `StateRecord`\r
5. `AICalculatorData` \r
\r
### 5. 技能不生效\r
\r
****\r
- SkillType \r
- SkillFactory \r
- \r
- 0 10\r
- `IsPermanent` / `IsLevelSkill` \r
\r
****\r
1. SkillType \r
2. `SkillFactory.cs` \r
3. ISkill \r
4. `GetAttackMultiplicationParam` \r
5. `IsFinished()` true\r
\r
### 6. 事件系统问题\r
\r
****\r
- \r
- \r
- \r
- EventManager 使\r
\r
****\r
1. `AddListener` \r
2. `AddListener` `RemoveListener`\r
3. Controller `RegisterEventCallback` / `UnregisterEventCallback`\r
\r
## 关键调试文件索引\r
\r
| | |\r
|------|---------|\r
| UI | `TH1_UI/Core/ViewControllerManager.cs`, `TH1_UI/Controller/Base/ViewController.cs` |\r
| | `TH1_Core/Managers/EventManager.cs`, `TH1_Core/Events/UIEvents.cs` |\r
| | `TH1_Logic/Skill/SkillBase.cs`, `TH1_Logic/Skill/SkillFactory.cs` |\r
| AI | `BTNodeCanvas/BaseActionTask.cs`, `TH1_Logic/AI/AIActionBase.cs` |\r
| | `TH1_Config/GenerateCS/`, `TH1_Config/ExcelPartial/` |\r
| | `TH1_Logic/Core/Main.cs` |\r
| UI | `TH1_UI/DebugUI.cs` |\r
| | `TH1_Logic/Editor/` |\r
\r
## 调试工具\r
\r
- **F10/F11**AI \r
- **MainEditor.Instance**\r
- **DebugUI**`TH1_UI/DebugUI.cs` \r
- **AIRecord**AI \r
- **LogSystem.LogError/LogInfo**\r
\r
## 排查策略\r
\r
1. **** \r
2. **** \r
3. ****\r
4. ****"""
name = "debugger"

View File

@ -0,0 +1,216 @@
description = "TH1项目游戏逻辑开发专家负责开发技能系统(SkillBase派生类)、战斗逻辑、事件系统和核心游戏玩法。当需要创建新技能、修改战斗逻辑或处理事件系统时使用此agent。"
developer_instructions = """
# 游戏逻辑开发专家\r
\r
## 角色\r
\r
TH1 SkillBase\r
\r
## 技能系统\r
\r
### SkillType 枚举\r
\r
`TH1_Logic/Skill/SkillBase.cs` \r
\r
****SkillType \r
\r
### SkillBase 派生类模板\r
\r
```csharp\r
using System;\r
using System.Collections.Generic;\r
using MemoryPack;\r
using RuntimeData;\r
using UnityEngine;\r
\r
namespace Logic.Skill\r
{\r
[MemoryPackable]\r
public partial class Skill{Name} : SkillBase\r
{\r
[MemoryPackConstructor]\r
public Skill{Name}()\r
{\r
IsPermanent = true; // true=, false=\r
IsLevelSkill = false; // true=, false=\r
}\r
\r
public override SkillType GetSkillType()\r
{\r
return SkillType.{NAME};\r
}\r
\r
// ==================== ====================\r
\r
// \r
public override void OnTurnStart(IdentifierBase self, MapData mapData)\r
{\r
}\r
\r
// \r
public override void OnTurnEnd(IdentifierBase self, MapData mapData)\r
{\r
}\r
\r
// \r
public override void OnMove(UnitData self, GridData grid, MapData mapData, MoveType moveType, List<Vector2Int> path = null)\r
{\r
}\r
\r
// \r
public override void BeforeDamageOther(MapData mapData, SettlementInfo info)\r
{\r
}\r
\r
// \r
public override void OnDamaged(MapData mapData, SettlementInfo info)\r
{\r
}\r
\r
// \r
public override void OnDamageOther(MapData mapData, SettlementInfo info)\r
{\r
}\r
\r
// \r
public override void BeforeActiveAttackOther(MapData mapData, UnitData origin, UnitData target, out int addDmg)\r
{\r
addDmg = 0;\r
}\r
\r
// \r
public override void AfterActiveAttackOther(MapData mapData, UnitData origin, UnitData target)\r
{\r
}\r
\r
// ==================== ====================\r
\r
// \r
public override bool IsIgnoreControlArea(UnitData self, MapData mapData)\r
{\r
return false;\r
}\r
\r
// \r
public override bool IsCanBeKill(UnitData self, MapData mapData)\r
{\r
return true;\r
}\r
\r
// ==================== ====================\r
\r
// \r
public override float GetAttackAdditionParam(MapData mapData, UnitData self, UnitData target = null)\r
{\r
return 0;\r
}\r
\r
// \r
public override float GetAttackMultiplicationParam(MapData mapData, UnitData self, UnitData target = null)\r
{\r
return 1; // 10\r
}\r
\r
// \r
public override float GetDefenseAdditionParam(MapData mapData, UnitData self, UnitData target = null)\r
{\r
return 0;\r
}\r
\r
// \r
public override int GetExtraMoveRange(MapData mapData, UnitData self)\r
{\r
return 0;\r
}\r
\r
// \r
public override int GetExtraSight(UnitData self, MapData mapData)\r
{\r
return 0;\r
}\r
}\r
}\r
```\r
\r
### ISkill 接口方法分类\r
\r
#### 生命周期方法\r
| | |\r
|------|---------|\r
| `OnRefresh()` | |\r
| `OnMove()` | |\r
| `BeforeDamagedSupportStage()` | |\r
| `BeforeDamagedTransformStage()` | |\r
| `BeforeDamageOther()` | |\r
| `OnDamaged()` | |\r
| `OnDamageOther()` | |\r
| `AfterDamageOther()` | |\r
| `OnHealOther()` | |\r
| `BeforeActiveAttackOther()` | |\r
| `AfterActiveAttackOther()` | |\r
| `AfterActiveAttacked()` | |\r
| `OnTurnStart()` | |\r
| `OnTurnEnd()` | |\r
| `OnFinished()` | |\r
\r
#### 全局事件方法\r
| | |\r
|------|---------|\r
| `BeforeUnitDamaged()` | |\r
| `OnUnitDamaged()` | |\r
| `OnAnyUnitMove()` | |\r
| `OnAnyUnitDie()` | |\r
| `OnAnyUnitCreate()` | |\r
| `OnActionExecuted()` | |\r
\r
#### 判断属性方法(返回 bool\r
`IsLimitSelfMove`, `IsLimitSelfAttack`, `IsIgnoreControlArea`, `IsCanBeKill`, `IsCanBeDamaged`, `IsInvisible`, `IsCanMoveOnTerrain`, `IsIgnoreMoveLoss`, `IsCanTransport`, \r
\r
#### 数值获取方法\r
`GetExtraSight`, `GetAttackAdditionParam`, `GetAttackMultiplicationParam`, `GetDefenseAdditionParam`, `GetDefenseMultiplicationParam`, `GetExtraMoveRange`, `GetExtraAttackRange`, `GetCriticalHitRate`, \r
\r
****Multiplication `1` `0`\r
\r
### 创建新技能步骤\r
\r
1. `SkillType` \r
2. `TH1_Logic/Skill/` `Skill{Name}.cs`\r
3. 使 `[MemoryPackable]` + `partial class`\r
4. `SkillFactory.cs` \r
\r
## 数据访问模式\r
\r
```csharp\r
// \r
var mapData = Main.MapData;\r
\r
// \r
var playerData = PlayerData.SelfPlayerData;\r
\r
// \r
mapData.GetPlayerDataByUnitId(unitId, out var player);\r
\r
// \r
Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitType, giantType, level, out var info);\r
```\r
\r
## 事件系统用法\r
\r
```csharp\r
// \r
EventManager.Instance.AddListener<EventType>(OnEventHandler);\r
\r
// \r
EventManager.Instance.RemoveListener<EventType>(OnEventHandler);\r
\r
// \r
void OnEventHandler(EventType evt) { }\r
```\r
\r
## 关键参考文件\r
\r
- `TH1_Logic/Skill/SkillBase.cs` - ISkill\r
- `TH1_Logic/Skill/SkillFactory.cs` - \r
- `TH1_Logic/Core/Main.cs` - \r
- `TH1_Core/Managers/EventManager.cs` - """
name = "logic-developer"

View File

@ -0,0 +1,81 @@
description = "TH1项目UI面板构建专家负责创建和修改View-Controller模式的UI面板。当需要创建新UI面板、修改UI组件或处理View-Controller注册流程时使用此agent。"
developer_instructions = """
# UI 面板构建专家\r
\r
## 角色\r
\r
TH1 UI View-Controller UI 5\r
\r
## 工作流程\r
\r
UI \r
\r
### 1. 确认需求\r
- UI CategoryTop/Bottom/Info/Notify/Outside/Announce/Interaction\r
- \r
- \r
\r
### 2. 读取参考文件\r
\r
- `TH1_UI/Core/ViewControllerManager.cs` - \r
- `TH1_UI/Core/UIResourceName.cs` - \r
- `TH1_Core/Events/UIEvents.cs` - \r
\r
### 3. 按5步流程创建\r
\r
**Step 1**: View `TH1_UI/View/{Category}/UI{Category}{Name}View.cs`\r
- `TH1_UI.View.Base.View`\r
- `OnInit()` **** `base.OnInit()`\r
- 使 `ViDelegateAssisstant.Dele` \r
\r
**Step 2**: Controller `TH1_UI/Controller/{Category}/UI{Category}{Name}Controller.cs`\r
- `ViewController<UI{Category}{Name}View>`\r
- ****\r
- `RegisterEventCallback()` / `UnregisterEventCallback()` /View\r
- `OnOpen()` `_openParameter` \r
\r
**Step 3**: `UIResourceName.cs` \r
- `public static readonly string View{Category}{Name} = "UI{Category}{Name}";`\r
\r
**Step 4**: `UIEvents.cs` \r
- `public struct ShowUI{Category}{Name} { /* */ }`\r
- `public struct HideUI{Category}{Name} { }`\r
\r
**Step 5**: `ViewControllerManager.cs` 3\r
- getter\r
- `_CreateAllViewControllers()` \r
- \r
\r
### 4. 添加 using 语句\r
`ViewControllerManager.cs` Controller \r
\r
## Category → UIRootType 映射\r
\r
| Category | UIRootType | SubFolderPath |\r
|----------|-----------|---------------|\r
| Top | TopUI | "Top" |\r
| Bottom | BottomUI | "Bottom" |\r
| Info | InfoUI | "Info" |\r
| Notify | NotifyUI | "Notify" |\r
| Outside | OutsideUI | "Outside" |\r
| Announce | PresentationUI | "Presentation/Announce" |\r
| Interaction | PresentationUI | "Presentation/Interaction" |\r
\r
## 完成后自检\r
\r
\r
1. View `OnInit()` `base.OnInit()`\r
2. Controller \r
3. UIResourceName \r
4. UIEvents Show/Hide \r
5. ViewControllerManager 3\r
6. using \r
7. \r
\r
## 参考文件\r
\r
- View`TH1_UI/View/Top/UITopSettingView.cs`\r
- Controller`TH1_UI/Controller/Top/UITopSettingController.cs`\r
- View`TH1_UI/View/Base/View.cs`\r
- Controller`TH1_UI/Controller/Base/ViewController.cs`"""
name = "ui-builder"

3
.gitattributes vendored
View File

@ -14,6 +14,9 @@
# Project docs and scripts.
*.md text eol=lf
*.json text eol=lf
*.toml text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.html text eol=lf
*.css text eol=lf
*.js text eol=lf

83
AGENTS.md Normal file
View File

@ -0,0 +1,83 @@
# TH1 Codex Guide
This repository is a Unity 2022.3 LTS / ET Framework turn-based strategy game. The main playable client lives under `Unity/Assets/Scripts`, with supporting config, tools, backend notes, and generated analysis in the repository root.
## First Reads
- For architecture questions, read the `MD/GameMDFramework/00-*` overview document and the relevant subsystem document before editing.
- For code navigation, read `Unity/graphify-out/GRAPH_REPORT.md` for Unity code and `graphify-out/GRAPH_REPORT.md` for whole-repository context.
- If a task touches action execution, networking, localization, online errors, backend upload, or CrashSight reports, use the matching `.codex/skills/th1-*` skill first.
- Claude-era context remains in `.claude/` and `Unity/Assets/Scripts/CLAUDE.md`; treat it as historical source material, not the active entrypoint.
## Project Shape
- `Unity/Assets/Scripts/TH1_Core`: shared managers such as `EventManager`, `UIManager`, and `PresentationManager`.
- `Unity/Assets/Scripts/TH1_Data`: runtime data models including `MapData`, `PlayerData`, `UnitData`, `GridData`, and `NetData`.
- `Unity/Assets/Scripts/TH1_Logic`: gameplay rules, actions, skills, AI, Steam networking, editor tools, and game entrypoints.
- `Unity/Assets/Scripts/TH1_UI`: View/Controller UI architecture and UI resource registration.
- `Unity/Assets/Scripts/TH1_Renderer`: map, grid, city, unit, projectile, and effect renderers.
- `Unity/Assets/Scripts/TH1_Config`: generated Excel config classes plus partial extensions.
- `Unity/Assets/Scripts/BTNodeCanvas`: NodeCanvas AI behavior tree nodes.
- `MD/GameMDFramework`: human-maintained architecture documentation by subsystem.
## Development Rules
- Keep authoritative gameplay mutations inside the action flow when possible: construct `CommonActionParams`, validate with `CheckCan`, and execute through `ActionLogicBase.CompleteExecute`.
- Do not make UI-only, AI-only, or network-receiver-only data mutations that bypass the shared action layer unless the existing design for that subsystem already does so.
- For multiplayer-safe logic, avoid `UnityEngine.Random`, wall-clock time, unordered iteration, and direct `Main.MapData` assumptions inside simulated or synchronized execution paths.
- For Excel-backed keys such as `UnitFullType`, `SkillType` level pairs, resources, terrain, and tech atoms, inspect the actual config rows before coding. Do not infer current data from similar entries.
- Unity `.meta` files are part of source control and must be preserved when assets move or are created.
- Generated files should only be edited through the project generator or documented editor workflow.
## Codex Skills And Agents
- Prefer `.codex/skills` for current project-specific workflows:
- `th1-action-logic`: action execution, AI action flow, turn actions, and action-triggered skills.
- `th1-network-sync`: Steam lobby, P2P, multiplayer save/recovery, action sync, and deterministic network behavior.
- `th1-multilingual`: localization import/export, active text scanning, duplicate IDs, and translation diagnostics.
- `th1-server-backend`: Aliyun Function Compute, OSS/STS upload, collect data, and backend debugging.
- `th1-online-debug`: production/obfuscated stack decoding and online issue tracing.
- `th1-crashsight-daily`: daily CrashSight triage and report generation.
- `.agents/skills` contains migrated Claude-era reference skills, including `graphify`, `th1-architecture`, `th1-ui-patterns`, `th1-config-guide`, `th1-ai-nodes`, and `th1-anim-scope`.
- `.codex/agents` contains migrated specialist agent profiles. Use them as role guidance for large investigations, but keep the final integration decisions in this main workspace.
## Graphify
- There are two graphify scopes:
- Root `graphify-out/`: whole repository overview.
- `Unity/graphify-out/`: Unity client code overview.
- Large graphify files such as `graph.json`, `cache/`, `converted/`, and `obsidian/` are intentionally ignored. Commit only lightweight reports and metadata such as `GRAPH_REPORT.md`, `manifest.json`, and `cost.json`.
- After code changes, the git `post-commit` hook runs `Tools/GraphifyPostCommit.ps1` in the background to refresh code graphs without LLM cost.
- To run the hook manually:
```powershell
Tools/GraphifyPostCommit.ps1
```
- To reinstall the local git hook after cloning or moving the repository:
```powershell
Tools/InstallGraphifyHook.ps1
```
- To test what the hook would do without rebuilding:
```powershell
Tools/GraphifyPostCommit.ps1 -DryRun
```
## Verification
- For most Unity C# changes, run:
```powershell
dotnet build Unity/Assembly-CSharp.csproj --no-restore
```
- For editor tooling, config windows, localization editor, OSS editor, or generated editor integration, also run:
```powershell
dotnet build Unity/Assembly-CSharp-Editor.csproj --no-restore
```
- Some behavior still requires Unity Editor validation, especially UI prefabs, animation sequencing, Steam networking, save/load, replay/spectator, and AI behavior tree changes.

View File

@ -0,0 +1,174 @@
param(
[switch]$DryRun
)
$ErrorActionPreference = "Continue"
$script:RepoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot ".."))
$script:LogPath = Join-Path $script:RepoRoot ".git/graphify-hook.log"
function Write-HookLog {
param([string]$Message)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$line = "[$timestamp] $Message"
Write-Host $line
try {
Add-Content -Path $script:LogPath -Value $line -Encoding UTF8
}
catch {
Write-Host "[graphify hook] Unable to write log: $($_.Exception.Message)"
}
}
function Test-GraphifyPython {
param([string]$PythonPath)
if ([string]::IsNullOrWhiteSpace($PythonPath)) {
return $false
}
try {
& $PythonPath -c "import graphify" *> $null
return ($LASTEXITCODE -eq 0)
}
catch {
return $false
}
}
function Get-GraphifyPython {
$candidates = @()
foreach ($relative in @("Unity/graphify-out/.graphify_python", "graphify-out/.graphify_python")) {
$path = Join-Path $script:RepoRoot $relative
if (Test-Path $path) {
$value = (Get-Content -Raw -Path $path -Encoding UTF8).Trim()
if ($value) {
$candidates += $value
}
}
}
foreach ($command in @("python", "py")) {
$cmd = Get-Command $command -ErrorAction SilentlyContinue
if ($cmd) {
$candidates += $cmd.Source
}
}
foreach ($candidate in $candidates | Select-Object -Unique) {
if (Test-GraphifyPython $candidate) {
return $candidate
}
}
return $null
}
function Get-ChangedFiles {
$fromEnv = $env:GRAPHIFY_CHANGED
if (-not [string]::IsNullOrWhiteSpace($fromEnv)) {
return $fromEnv -split "`r?`n" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
}
$git = Get-Command git -ErrorAction SilentlyContinue
if (-not $git) {
return @()
}
Push-Location $script:RepoRoot
try {
$files = & git diff --name-only HEAD~1 HEAD 2>$null
if (-not $files) {
$files = & git diff --name-only HEAD 2>$null
}
return @($files | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
}
finally {
Pop-Location
}
}
function Invoke-GraphifyRebuild {
param(
[string]$Label,
[string]$RelativePath,
[string]$PythonPath
)
$target = Join-Path $script:RepoRoot $RelativePath
$graphPath = Join-Path $target "graphify-out/graph.json"
if (-not (Test-Path $graphPath)) {
Write-HookLog "Skipping ${Label}: no graphify-out/graph.json found."
return
}
if ($DryRun) {
Write-HookLog "Dry run: would rebuild $Label graph at $target."
return
}
Write-HookLog "Rebuilding $Label graph at $target."
Push-Location $target
try {
& $PythonPath -c "from pathlib import Path; from graphify.watch import _rebuild_code; _rebuild_code(Path('.'))" *> $null
if ($LASTEXITCODE -eq 0) {
Write-HookLog "Rebuilt $Label graph."
}
else {
Write-HookLog "Rebuild failed for $Label with exit code $LASTEXITCODE."
}
}
catch {
Write-HookLog "Rebuild failed for ${Label}: $($_.Exception.Message)"
}
finally {
Pop-Location
}
}
Set-Location $script:RepoRoot
$changedFiles = @(Get-ChangedFiles)
if ($changedFiles.Count -eq 0) {
Write-HookLog "No changed files detected; nothing to rebuild."
exit 0
}
$codeExtensions = @(
".cs", ".asmdef", ".shader", ".hlsl", ".cginc",
".py", ".js", ".ts", ".json", ".toml", ".yaml", ".yml"
)
$changedCode = @(
$changedFiles | Where-Object {
$ext = [System.IO.Path]::GetExtension($_).ToLowerInvariant()
$codeExtensions -contains $ext
}
)
if ($changedCode.Count -eq 0) {
Write-HookLog "$($changedFiles.Count) changed file(s), but no code/config files for AST graph rebuild."
exit 0
}
$python = Get-GraphifyPython
if (-not $python) {
Write-HookLog "No Python interpreter with graphify installed was found."
exit 0
}
Write-HookLog "$($changedCode.Count) code/config file(s) changed; using Python: $python"
$touchesUnity = @($changedCode | Where-Object { $_ -like "Unity/*" -or $_ -like "Unity\*" }).Count -gt 0
Invoke-GraphifyRebuild -Label "root" -RelativePath "." -PythonPath $python
if ($touchesUnity) {
Invoke-GraphifyRebuild -Label "Unity" -RelativePath "Unity" -PythonPath $python
}
else {
Write-HookLog "Skipping Unity graph: no Unity path changed."
}

View File

@ -0,0 +1,39 @@
$ErrorActionPreference = "Stop"
$repoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot ".."))
$gitDir = Join-Path $repoRoot ".git"
$hooksDir = Join-Path $gitDir "hooks"
$hookPath = Join-Path $hooksDir "post-commit"
if (-not (Test-Path $gitDir)) {
throw "No .git directory found at $repoRoot"
}
if (-not (Test-Path $hooksDir)) {
New-Item -ItemType Directory -Path $hooksDir | Out-Null
}
$hook = @'
#!/bin/sh
# Runs the repository-owned graphify post-commit updater.
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only HEAD 2>/dev/null)
if [ -z "$CHANGED" ]; then
exit 0
fi
export GRAPHIFY_CHANGED="$CHANGED"
if command -v pwsh >/dev/null 2>&1; then
(cd "$REPO_ROOT" && pwsh -NoProfile -ExecutionPolicy Bypass -File "Tools/GraphifyPostCommit.ps1") >/dev/null 2>&1 &
elif command -v powershell.exe >/dev/null 2>&1; then
(cd "$REPO_ROOT" && powershell.exe -NoProfile -ExecutionPolicy Bypass -File "Tools/GraphifyPostCommit.ps1") >/dev/null 2>&1 &
fi
exit 0
'@
[System.IO.File]::WriteAllText($hookPath, $hook, [System.Text.UTF8Encoding]::new($false))
Write-Host "Installed graphify post-commit hook: $hookPath"

13
Tools/README.md Normal file
View File

@ -0,0 +1,13 @@
# Tools
`Tools` stores executable utilities, local apps, import/export scripts, diagnostics, and automation helpers.
Keep source documents and planning notes out of this directory unless they are direct fixtures for a tool.
Current notable entries:
- `Dashboard/`: local project dashboard web app. It stays here because it has a server, frontend code, generated data, and launch scripts.
- `OSS/`: online data and upload/debug utilities.
- `multilingual_check/`: localization checking and translation helpers.
Design documents belong in `Design/`. Dashboard-backed operational data belongs in `DOC/`.