chore: add codex collaboration config
This commit is contained in:
parent
d593338f8b
commit
19cf0ae820
1
.agents/skills/graphify/.graphify_version
Normal file
1
.agents/skills/graphify/.graphify_version
Normal file
@ -0,0 +1 @@
|
||||
0.4.21
|
||||
1319
.agents/skills/graphify/SKILL.md
Normal file
1319
.agents/skills/graphify/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
217
.agents/skills/th1-ai-nodes/SKILL.md
Normal file
217
.agents/skills/th1-ai-nodes/SKILL.md
Normal 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` - 循环控制
|
||||
209
.agents/skills/th1-anim-scope/SKILL.md
Normal file
209
.agents/skills/th1-anim-scope/SKILL.md
Normal 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 |
|
||||
110
.agents/skills/th1-architecture/SKILL.md
Normal file
110
.agents/skills/th1-architecture/SKILL.md
Normal 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` 文件必须提交到版本控制
|
||||
- 注释使用中文
|
||||
173
.agents/skills/th1-config-guide/SKILL.md
Normal file
173
.agents/skills/th1-config-guide/SKILL.md
Normal 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 列对应
|
||||
216
.agents/skills/th1-ui-patterns/SKILL.md
Normal file
216
.agents/skills/th1-ui-patterns/SKILL.md
Normal 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: 注册到 ViewControllerManager(3处修改)
|
||||
|
||||
文件:`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 语句
|
||||
163
.codex/agents/ai-designer.toml
Normal file
163
.codex/agents/ai-designer.toml
Normal 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"
|
||||
97
.codex/agents/code-reviewer.toml
Normal file
97
.codex/agents/code-reviewer.toml
Normal 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 完成3处注册(属性getter、创建调用、私有字段)\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
|
||||
- 问题数:Y(严重:A / 警告: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"
|
||||
121
.codex/agents/config-manager.toml
Normal file
121
.codex/agents/config-manager.toml
Normal 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
127
.codex/agents/debugger.toml
Normal 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 为 null(prefab 未加载完就访问)\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 而非 1(导致伤害为0)\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"
|
||||
216
.codex/agents/logic-developer.toml
Normal file
216
.codex/agents/logic-developer.toml
Normal 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; // 注意:乘法基准是1,不是0\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"
|
||||
81
.codex/agents/ui-builder.toml
Normal file
81
.codex/agents/ui-builder.toml
Normal 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 的 Category(Top/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
3
.gitattributes
vendored
@ -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
83
AGENTS.md
Normal 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.
|
||||
174
Tools/GraphifyPostCommit.ps1
Normal file
174
Tools/GraphifyPostCommit.ps1
Normal 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."
|
||||
}
|
||||
39
Tools/InstallGraphifyHook.ps1
Normal file
39
Tools/InstallGraphifyHook.ps1
Normal 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
13
Tools/README.md
Normal 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/`.
|
||||
Loading…
x
Reference in New Issue
Block a user