Merge branch 'main' of http://10.27.17.121:3000/kawagiri/TH1 into main
This commit is contained in:
commit
a6450ad8b8
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"
|
||||
44
.gitattributes
vendored
Normal file
44
.gitattributes
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
* text=auto
|
||||
|
||||
# Unity text assets should keep stable line endings for readable diffs.
|
||||
*.cs text eol=lf
|
||||
*.asmdef text eol=lf
|
||||
*.shader text eol=lf
|
||||
*.cginc text eol=lf
|
||||
*.mat text eol=lf
|
||||
*.prefab text eol=lf
|
||||
*.unity text eol=lf
|
||||
*.asset text eol=lf
|
||||
*.meta text eol=lf
|
||||
|
||||
# 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
|
||||
*.py text eol=lf
|
||||
*.ps1 text eol=lf
|
||||
*.bat text eol=crlf
|
||||
|
||||
# Binary assets.
|
||||
*.bytes binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.webp binary
|
||||
*.ico binary
|
||||
*.ttf binary
|
||||
*.otf binary
|
||||
*.dll binary
|
||||
*.exe binary
|
||||
*.pdb binary
|
||||
*.xlsx binary
|
||||
*.xls binary
|
||||
*.zip binary
|
||||
*.7z binary
|
||||
*.rar binary
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -108,3 +108,8 @@ Tools/apply_translations.py
|
||||
Tools/reapply_all.py
|
||||
Tools/revert_inactive.py
|
||||
Tools/Multilingual_p1.xlsx
|
||||
|
||||
# Python caches
|
||||
__pycache__/
|
||||
**/__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
14
DOC/README.md
Normal file
14
DOC/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# DOC
|
||||
|
||||
`DOC` stores project operation data and documents that are consumed by the local dashboard.
|
||||
|
||||
Keep these data files here unless `Tools/Dashboard/serve.py` is updated at the same time:
|
||||
|
||||
- `bugs.json`
|
||||
- `devplan.json`
|
||||
- `marketing/*.json`
|
||||
- `sns/*.json`
|
||||
|
||||
Marketing drafts, Steam announcement copies, public-opinion incident notes, and dashboard-backed task data also belong here.
|
||||
|
||||
Gameplay design documents should live under `Design/gameplay/`. Architecture and engineering documents should live under `MD/`.
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"nextId": 258,
|
||||
"nextId": 267,
|
||||
"bugs": [
|
||||
{
|
||||
"id": 2,
|
||||
@ -2550,6 +2550,96 @@
|
||||
"module": "",
|
||||
"createdAt": 1779506906721,
|
||||
"updatedAt": 1779506906721
|
||||
},
|
||||
{
|
||||
"id": 258,
|
||||
"title": "我联机打的时候,楼陀罗火车打守矢的那个大白蛇会有bug",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"createdAt": 1779523517289,
|
||||
"updatedAt": 1779523517289
|
||||
},
|
||||
{
|
||||
"id": 259,
|
||||
"title": "所有巨人单位应该是LV6以上升级可选",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"createdAt": 1779537607662,
|
||||
"updatedAt": 1779537607662
|
||||
},
|
||||
{
|
||||
"id": 260,
|
||||
"title": "间谍攻城的时候城里也有间谍怎么办?",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"createdAt": 1779538468082,
|
||||
"updatedAt": 1779538468082
|
||||
},
|
||||
{
|
||||
"id": 261,
|
||||
"title": "玩其他游戏时好像邀请不了",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"createdAt": 1779539073924,
|
||||
"updatedAt": 1779539073924
|
||||
},
|
||||
{
|
||||
"id": 262,
|
||||
"title": "话说什么时候恋升级不可视 升个级还能被人看到(",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"createdAt": 1779539151076,
|
||||
"updatedAt": 1779539151076
|
||||
},
|
||||
{
|
||||
"id": 263,
|
||||
"title": "咲夜/妹红的Lv3 技能描述问题",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"createdAt": 1779597876284,
|
||||
"updatedAt": 1779597876284
|
||||
},
|
||||
{
|
||||
"id": 264,
|
||||
"title": "早苗行动点问题",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"createdAt": 1779599217683,
|
||||
"updatedAt": 1779599217683
|
||||
},
|
||||
{
|
||||
"id": 265,
|
||||
"title": "热插拔",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"createdAt": 1779610731847,
|
||||
"updatedAt": 1779610731847
|
||||
},
|
||||
{
|
||||
"id": 266,
|
||||
"title": "遇到個挺神秘的bug,在戀戀被擊殺的被動觸發以後,我的單位同時被觸發的被動擊殺後,她們部隊兵棋還會留在地圖上,(p1p2) 在此之後整個地靈殿陣營的部隊都會隱身,直到占領我方地區的時候才會出現,有點像瞬移一樣XD,bug出現後游戲回合在超過30回合也不會結束(p3p4)",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"createdAt": 1779776751092,
|
||||
"updatedAt": 1779776751092
|
||||
}
|
||||
]
|
||||
}
|
||||
4
DOC/suggestions.json
Normal file
4
DOC/suggestions.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"nextId": 1,
|
||||
"suggestions": []
|
||||
}
|
||||
13
Design/README.md
Normal file
13
Design/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Design
|
||||
|
||||
`Design` is the source area for TH1 design documents. Prefer HTML for durable design documents so they can be opened directly and embedded in the Dashboard.
|
||||
|
||||
## Structure
|
||||
|
||||
- `final/mechanics/`: completed or accepted game-mechanic designs.
|
||||
- `final/narrative/`: completed or accepted worldbuilding, story, copywriting, and flavor text designs.
|
||||
- `drafts/`: work in progress, discussion notes, temporary records, and planning material.
|
||||
- `art/`: art direction and asset planning.
|
||||
- `audio/`: music and sound direction.
|
||||
|
||||
Dashboard-backed operational data belongs in `DOC/`. Engineering architecture belongs in `MD/`.
|
||||
37
Design/final/mechanics/core-loop.html
Normal file
37
Design/final/mechanics/core-loop.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 核心循环</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrow">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Mechanics / Core</p>
|
||||
<h1>核心循环</h1>
|
||||
<p class="lead">核心循环必须同时服务玩家、AI、联机和回放。任何能改变战局的设计,最终都要落到可验证的行动、数据变更和表现反馈。</p>
|
||||
</header>
|
||||
<section class="doc-section">
|
||||
<h2>回合结构</h2>
|
||||
<ol class="rule-list">
|
||||
<li><strong>回合开始:</strong>刷新城市产出、技能状态、可行动单位和回合事件。</li>
|
||||
<li><strong>玩家决策:</strong>移动、攻击、训练、建造、研发、外交、城市升级。</li>
|
||||
<li><strong>行动执行:</strong>构造参数、校验合法性、进入 ActionLogic 执行并广播事件。</li>
|
||||
<li><strong>表现播放:</strong>动画、特效、UI 和 Renderer 根据事件与 Presentation 队列更新。</li>
|
||||
<li><strong>回合结束:</strong>处理持续状态、AI 队列、胜利条件、存档/上传等外围逻辑。</li>
|
||||
</ol>
|
||||
</section>
|
||||
<section class="doc-section">
|
||||
<h2>设计约束</h2>
|
||||
<div class="callout-grid">
|
||||
<article><h3>规则要短</h3><p>每个行动最好能用一句话解释,同时允许技能层叠形成深度。</p></article>
|
||||
<article><h3>状态要可见</h3><p>恐惧、满月、金刚身、粗糙身等状态必须在 UI、伤害预览和战斗表现中一致。</p></article>
|
||||
<article><h3>行为要同步</h3><p>联机、AI 和玩家输入都不应绕过共享 Action 层。</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="doc-footer"><a href="index.html">返回机制总览</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
36
Design/final/mechanics/design-principles.html
Normal file
36
Design/final/mechanics/design-principles.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 设计原则</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell principles-shell">
|
||||
<section class="principles-page">
|
||||
<header class="principles-header">
|
||||
<p class="eyebrow">Design Principles</p>
|
||||
<h1>产品原则</h1>
|
||||
</header>
|
||||
|
||||
<ol class="principles-list">
|
||||
<li>
|
||||
<span class="principle-index">01</span>
|
||||
<strong>东方 Project 角色展示优先</strong>
|
||||
</li>
|
||||
<li>
|
||||
<span class="principle-index">02</span>
|
||||
<strong>保守设计,以 Polytopia 为基本原则,小步迭代,不在一次设计内做大幅度创新</strong>
|
||||
</li>
|
||||
<li>
|
||||
<span class="principle-index">03</span>
|
||||
<strong>极简,任意时刻让玩家保持 3 个及以下的决策选项</strong>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<footer class="doc-footer"><a href="index.html">返回设计树</a></footer>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
33
Design/final/mechanics/faction-system.html
Normal file
33
Design/final/mechanics/faction-system.html
Normal file
@ -0,0 +1,33 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 阵营与文明系统</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrow">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Mechanics / Factions</p>
|
||||
<h1>阵营与文明系统</h1>
|
||||
<p class="lead">TH1 的文明基底继承 Polytopia 的地图、城市和科技结构;势力层负责东方主题、英雄名单、特色单位和专属机制。</p>
|
||||
</header>
|
||||
<section class="doc-section">
|
||||
<h2>两层模型</h2>
|
||||
<table class="doc-table">
|
||||
<thead><tr><th>层级</th><th>负责内容</th><th>设计风险</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>文明 Civ</td><td>城市名、基础资源倾向、起始科技、地图适应性。</td><td>如果差异过大,会破坏 Polytopia 式简洁。</td></tr>
|
||||
<tr><td>势力 Force</td><td>东方角色、阵营主题、英雄、特色单位、专属建筑和技能。</td><td>如果技能过多,会造成不可读和难平衡。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section class="doc-section">
|
||||
<h2>当前规模</h2>
|
||||
<p>Dashboard 摘要导出显示当前包含 17 个玩家阵营、20 名英雄、55 项科技。第一版设计框架先记录系统关系,后续再逐个阵营补全完整设计页。</p>
|
||||
</section>
|
||||
<footer class="doc-footer"><a href="index.html">返回机制总览</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
183
Design/final/mechanics/hero-foundation.html
Normal file
183
Design/final/mechanics/hero-foundation.html
Normal file
@ -0,0 +1,183 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 英雄基础定位</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell hero-foundation-shell">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Game Mechanics / Foundation / Heroes</p>
|
||||
<h1>英雄</h1>
|
||||
</header>
|
||||
|
||||
<section class="doc-section">
|
||||
<h2>1. 基础定位:职阶设计</h2>
|
||||
<div class="class-grid">
|
||||
<article class="class-card class-xiang">
|
||||
<div class="class-rank">相</div>
|
||||
<h3>探索型</h3>
|
||||
<p>开局任务:探索。核心收益在探索信息和机动展开,适合无防主发展。</p>
|
||||
</article>
|
||||
<article class="class-card class-hou">
|
||||
<div class="class-rank">后</div>
|
||||
<h3>防守探索型</h3>
|
||||
<p>开局任务:探索。兼具探索与防御,能支撑无防或有防主发展。</p>
|
||||
</article>
|
||||
<article class="class-card class-wang">
|
||||
<div class="class-rank">王</div>
|
||||
<h3>发展防御型</h3>
|
||||
<p>开局任务:特色。发展能力高,适合有防主发展或防御型主战争。</p>
|
||||
</article>
|
||||
<article class="class-card class-che">
|
||||
<div class="class-rank">车</div>
|
||||
<h3>正面战争型</h3>
|
||||
<p>开局任务:伤害。防御和进攻都强,适合主战争策略。</p>
|
||||
</article>
|
||||
<article class="class-card class-ma">
|
||||
<div class="class-rank">马</div>
|
||||
<h3>扩张突击型</h3>
|
||||
<p>开局任务:扩张。进攻与移动突出,适合主动扩张和压迫。</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc-section">
|
||||
<h2>职阶定位矩阵</h2>
|
||||
<div class="table-wrap">
|
||||
<table class="doc-table role-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">职阶</th>
|
||||
<th colspan="4">开局整体表现收益</th>
|
||||
<th colspan="4">开局策略</th>
|
||||
<th rowspan="2">开局任务</th>
|
||||
<th colspan="5">开局属性</th>
|
||||
<th colspan="5">满级属性</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>发展</th><th>探索</th><th>防御</th><th>进攻</th>
|
||||
<th>主发展<br>无防</th><th>主发展<br>有防</th><th>主战争<br>防御</th><th>主战争<br>进攻</th>
|
||||
<th>生命</th><th>防御</th><th>攻击</th><th>射程</th><th>移动</th>
|
||||
<th>生命</th><th>防御</th><th>攻击</th><th>射程</th><th>移动</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>相</strong></td>
|
||||
<td>C</td><td class="grade-s">S</td><td>C</td><td>C</td>
|
||||
<td class="check">√</td><td></td><td></td><td></td>
|
||||
<td>探索</td>
|
||||
<td>A(15)</td><td>B(2)</td><td>C(1)</td><td>C(1)</td><td>A(2)</td>
|
||||
<td>A(45)</td><td>B(3)</td><td>B(3)</td><td>A(2)</td><td>B(2)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>后</strong></td>
|
||||
<td>C</td><td>A</td><td>A</td><td>C</td>
|
||||
<td class="check">√</td><td class="check">√</td><td></td><td></td>
|
||||
<td>探索</td>
|
||||
<td>C(10)</td><td class="grade-s">S(3)</td><td>B(2)</td><td>A(2)</td><td>B(1.5)</td>
|
||||
<td>C(30)</td><td>B(3)</td><td>B(3)</td><td class="grade-s">S(3)</td><td>B(2)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>王</strong></td>
|
||||
<td>A</td><td>C</td><td>B</td><td>B</td>
|
||||
<td></td><td class="check">√</td><td class="check">√</td><td></td>
|
||||
<td>特色</td>
|
||||
<td class="grade-s">S(20)</td><td>B(2)</td><td>B(2)</td><td>C(1)</td><td>C(1)</td>
|
||||
<td>B(35)</td><td class="grade-s">S(4)</td><td>B(3)</td><td>C(1)</td><td>C(1)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>车</strong></td>
|
||||
<td>C</td><td>C</td><td class="grade-s">S</td><td class="grade-s">S</td>
|
||||
<td></td><td></td><td class="check">√</td><td class="check">√</td>
|
||||
<td>伤害</td>
|
||||
<td class="grade-s">S(20)</td><td class="grade-s">S(3)</td><td class="grade-s">S(3)</td><td>C(1)</td><td>C(1)</td>
|
||||
<td class="grade-s">S(50)</td><td class="grade-s">S(4)</td><td class="grade-s">S(5)</td><td>C(1)</td><td>C(1)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>马</strong></td>
|
||||
<td>C</td><td>C</td><td>C</td><td>A</td>
|
||||
<td></td><td></td><td></td><td class="check">√</td>
|
||||
<td>扩张</td>
|
||||
<td>C(10)</td><td>C(1)</td><td class="grade-s">S(3)</td><td>C(1)</td><td class="grade-s">S(2.5)</td>
|
||||
<td>C(30)</td><td>B(3)</td><td>A(4)</td><td>C(1)</td><td class="grade-s">S(3)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="table-note">√ 表示该职阶能满足对应开局策略,并能顺势升级。</p>
|
||||
</section>
|
||||
|
||||
<section class="doc-section">
|
||||
<h2>五阵营英雄职阶配置</h2>
|
||||
<div class="table-wrap">
|
||||
<table class="doc-table faction-class-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>职阶</th>
|
||||
<th>基础定位</th>
|
||||
<th>红魔馆</th>
|
||||
<th>永远亭</th>
|
||||
<th>守矢神社</th>
|
||||
<th>地灵殿</th>
|
||||
<th>博丽神社</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>王</strong></td>
|
||||
<td>发展防御型,承担阵营特色任务。</td>
|
||||
<td>蕾米莉亚</td>
|
||||
<td>辉夜</td>
|
||||
<td>神奈子</td>
|
||||
<td>觉</td>
|
||||
<td class="muted-cell">待补</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>后</strong></td>
|
||||
<td>防守探索型,兼顾安全开图和阵地保护。</td>
|
||||
<td>帕秋莉</td>
|
||||
<td>永琳</td>
|
||||
<td>诹访子</td>
|
||||
<td>恋</td>
|
||||
<td class="muted-cell">待补</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>相</strong></td>
|
||||
<td>探索型,承担开图、信息和机动收益。</td>
|
||||
<td>咲夜</td>
|
||||
<td>因幡帝</td>
|
||||
<td>早苗</td>
|
||||
<td>燐</td>
|
||||
<td class="muted-cell">待补</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>马</strong></td>
|
||||
<td>扩张突击型,负责压迫、追击和地图推进。</td>
|
||||
<td>芙兰朵露</td>
|
||||
<td>铃仙</td>
|
||||
<td>射命丸文</td>
|
||||
<td>空</td>
|
||||
<td class="muted-cell">待补</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>车</strong></td>
|
||||
<td>正面战争型,承担伤害、防线和硬碰硬作战。</td>
|
||||
<td>美铃</td>
|
||||
<td>妹红</td>
|
||||
<td>犬走椛</td>
|
||||
<td>勇仪</td>
|
||||
<td class="muted-cell">待补</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="doc-footer"><a href="index.html">返回设计树</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
35
Design/final/mechanics/hero-system.html
Normal file
35
Design/final/mechanics/hero-system.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 东方英雄系统</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrow">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Mechanics / Heroes</p>
|
||||
<h1>东方英雄系统</h1>
|
||||
<p class="lead">英雄是 TH1 相对 Polytopia 最大的机制扩展:它把东方角色设定转成高价值单位、技能触发器和阵营节奏。</p>
|
||||
</header>
|
||||
<section class="doc-section">
|
||||
<h2>英雄的三层职责</h2>
|
||||
<div class="flow-grid three">
|
||||
<article><h3>战术核心</h3><p>英雄通常具备更高属性、独特移动/攻击方式或强触发技能,是一局游戏中的局部战场焦点。</p></article>
|
||||
<article><h3>阵营识别</h3><p>每个势力通过英雄组合表达主题,例如红魔馆、永远亭、守矢、地灵殿等。</p></article>
|
||||
<article><h3>成长目标</h3><p>英雄等级、文化消耗、出战位和技能解锁构成中长期投入目标。</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section class="doc-section">
|
||||
<h2>设计原则</h2>
|
||||
<ul class="rule-list">
|
||||
<li><strong>强但不万能:</strong>英雄应改变局部最优解,而不是替代城市、科技和普通单位。</li>
|
||||
<li><strong>角色设定先转规则:</strong>先明确角色幻想,再落成移动、伤害、状态、召唤、复活、转换等规则动词。</li>
|
||||
<li><strong>技能描述必须可验证:</strong>文案里的关键词要能追到技能类型、触发时机和代码实现。</li>
|
||||
</ul>
|
||||
</section>
|
||||
<footer class="doc-footer"><a href="index.html">返回机制总览</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
98
Design/final/mechanics/index.html
Normal file
98
Design/final/mechanics/index.html
Normal file
@ -0,0 +1,98 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 设计树</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell tree-shell">
|
||||
<section class="design-tree" aria-label="TH1 设计树">
|
||||
<a class="tree-node tree-root" href="design-principles.html">设计原则</a>
|
||||
|
||||
<div class="tree-branches two">
|
||||
<section class="tree-card">
|
||||
<a class="tree-node tree-l1" href="design-principles.html#mechanics">1. 游戏机制</a>
|
||||
|
||||
<div class="tree-branches three">
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="design-principles.html#foundation">基础</a>
|
||||
<div class="tree-leaves">
|
||||
<a href="design-principles.html#farming">种田</a>
|
||||
<a href="design-principles.html#combat">战斗</a>
|
||||
<a href="design-principles.html#tempo">节奏</a>
|
||||
<a href="hero-foundation.html">英雄</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="design-principles.html#battlefield">战场</a>
|
||||
<div class="tree-group">
|
||||
<div class="tree-label">地形</div>
|
||||
<div class="tree-leaves inline">
|
||||
<a href="design-principles.html#dryland">旱地</a>
|
||||
<a href="design-principles.html#standard-map">标准</a>
|
||||
<a href="design-principles.html#sea-map">海图</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-group">
|
||||
<div class="tree-label">战场熵</div>
|
||||
<div class="tree-leaves inline">
|
||||
<a href="design-principles.html#duel">1v1</a>
|
||||
<a href="design-principles.html#ffa">FFA</a>
|
||||
<a href="design-principles.html#nvn">NvN</a>
|
||||
<a href="design-principles.html#conquest">征服</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="design-principles.html#factions">阵营</a>
|
||||
<div class="tree-leaves">
|
||||
<a href="design-principles.html#remilia">红魔馆</a>
|
||||
<a href="design-principles.html#kaguya">永远亭</a>
|
||||
<a href="design-principles.html#kanako">守矢神社</a>
|
||||
<a href="design-principles.html#satori">地灵殿</a>
|
||||
<a href="design-principles.html#reimu">博丽神社</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tree-card">
|
||||
<a class="tree-node tree-l1" href="design-principles.html#narrative">2. 文案剧情</a>
|
||||
|
||||
<div class="tree-branches three">
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="design-principles.html#story">剧情</a>
|
||||
</section>
|
||||
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="/api/design/narrative/heroes.html">英雄</a>
|
||||
<div class="tree-leaves">
|
||||
<a href="/api/design/narrative/heroes.html#remilia">红魔馆</a>
|
||||
<a href="/api/design/narrative/heroes.html#kaguya">永远亭</a>
|
||||
<a href="/api/design/narrative/heroes.html#kanako">守矢神社</a>
|
||||
<a href="/api/design/narrative/heroes.html#satori">地灵殿</a>
|
||||
<a href="/api/design/narrative/heroes.html#reimu">博丽神社</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tree-card compact">
|
||||
<a class="tree-node tree-l2" href="/api/design/narrative/civilizations.html">文明</a>
|
||||
<div class="tree-leaves">
|
||||
<a href="/api/design/narrative/civilizations.html#remilia">红魔馆</a>
|
||||
<a href="/api/design/narrative/civilizations.html#kaguya">永远亭</a>
|
||||
<a href="/api/design/narrative/civilizations.html#kanako">守矢神社</a>
|
||||
<a href="/api/design/narrative/civilizations.html#satori">地灵殿</a>
|
||||
<a href="/api/design/narrative/civilizations.html#reimu">博丽神社</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
370
Design/final/mechanics/ley-line-mechanism.md
Normal file
370
Design/final/mechanics/ley-line-mechanism.md
Normal file
@ -0,0 +1,370 @@
|
||||
# 地脉(Ley Line)机制 — 策划·程序综合开发文档
|
||||
|
||||
> 文档目录:`Design/final/mechanics/ley-line-mechanism.md`
|
||||
> 版本:v1.1 日期:2026-03-09 作者:待填写
|
||||
|
||||
---
|
||||
|
||||
## 一、策划需求
|
||||
|
||||
### 1.1 概述
|
||||
|
||||
地脉(Ley Line)是一种特殊地格属性(SpType),在地图生成阶段随机散布于地图各处。
|
||||
建造在地脉附近的建筑将获得初始等级加成,代表"灵气汇聚"对建筑的增幅效果。
|
||||
|
||||
**阵营限制**:地脉机制**仅对古明地帝国(Satori / Indian Empire)阵营生效**。
|
||||
- 只有 `CivEnum.Indian` 的玩家才能获得地脉建筑加成
|
||||
- 只有 `CivEnum.Indian` 的玩家才能在地图上看到地脉格的深紫色标识
|
||||
- 其他所有阵营(Egyptian、French、Germany等)的玩家**无法看到地脉,也不会获得任何地脉加成**
|
||||
- 地脉数据仍然存在于地图中(所有阵营共用同一张地图),只是非 Indian 阵营在渲染层和逻辑层都被跳过
|
||||
|
||||
### 1.2 建筑加成规则
|
||||
|
||||
| 条件 | 加成 | 备注 |
|
||||
|------|------|------|
|
||||
| 建筑建在地脉格上(isOnLeyLine) | 初始 buildingLevel **+3** | 冶炼厂(Forge)仅 **+2** |
|
||||
| 建筑建在地脉周围 1 格内(isAdjacentToLeyLine) | 初始 buildingLevel **+1** | 所有建筑类型相同 |
|
||||
| 两个条件叠加 | 不叠加,优先取在地脉上的规则 | 在地脉上不再享受周边加成 |
|
||||
|
||||
### 1.3 适用建筑范围
|
||||
|
||||
适用所有 `HasLevel = true` 的建筑,即当前的:
|
||||
- 风车(Windmill)
|
||||
- 冶炼厂(Forge)
|
||||
- 锯木厂(Sawmill)
|
||||
- 集市(Market)
|
||||
- 学院(Academy)
|
||||
- 保护区(Preserve)
|
||||
- 卡古亚法式农庄(KaguyaFrenchYard)
|
||||
- 埃及灌溉(EgyptianIrrigation)
|
||||
|
||||
> 等级上限(`resourceInfo.MaxLevel`)不变,地脉加成后仍受上限约束。
|
||||
|
||||
### 1.4 地脉格子的生成规则
|
||||
|
||||
地脉在 `MapGenerator.GenerateMap()` 的**最后阶段**随机生成,在所有地形与资源数据都确定之后进行。
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 生成时机 | 在 `InitGridMapData()` 和 `CivTerrainFeatureCountControl()` 之后,所有资源生成完毕后执行 |
|
||||
| 禁止位置 | 不得生成在 **CityCenter 格本身**上(无论首都还是村庄) |
|
||||
| 排除区 | 不得生成在**首都**(`PlayerCivOri`)的 **1 格范围内**;非首都村庄 1 格范围内**允许**生成 |
|
||||
| 生成数量 | 总数量 = 地图上城市中心总数(`_tribes.Count`)的 **1/3**(取整) |
|
||||
| 地形限制 | 仅限陆地格(`Terrain == TerrainType.Land`) |
|
||||
| 文明保底 | **古明地帝国**(`CivEnum.Indian`)首都周围 **2 格内必定有至少 1 个地脉**,随机放置未满足时强制补充 |
|
||||
| 地脉持久性 | 地脉属于地形属性层,建造建筑后依然保留(持久型 SpType,不消耗) |
|
||||
|
||||
### 1.5 视觉表现规则
|
||||
|
||||
地脉格子在视觉上**通过表格配置**,不再使用代码硬编码颜色。
|
||||
|
||||
- 地脉格子的 Sprite 和颜色走 `GridAndResourceDataAssets` 表格配置
|
||||
- `GetTerrainSprite()` 方法已支持根据 `GridSpType.LeyLine` 返回对应配置的 Sprite
|
||||
- 在 `TerrainInfo.SpriteList` 中添加 `IsGridSpType = true` 且 `GridSpType = LeyLine` 的配置项即可定义地脉格的地块外观
|
||||
- 视觉表现与 `RemiliaGrid`、`KaguyaGrid` 机制一致,完全由策划在表格中配置
|
||||
- **仅当本地玩家(SelfPlayer)为 `CivEnum.Indian` 时才渲染地脉特殊外观**;其他阵营玩家看到的地脉格显示为普通陆地地块
|
||||
|
||||
---
|
||||
|
||||
## 二、程序功能设计
|
||||
|
||||
### 2.1 涉及文件清单
|
||||
|
||||
| 文件路径 | 修改类型 | 说明 |
|
||||
|----------|----------|------|
|
||||
| `Assets/Scripts/TH1_Data/GridData.cs` | **枚举新增** | `GridSpType` 中加入 `LeyLine` |
|
||||
| `Assets/Scripts/TH1_Logic/City/CityLogic.cs` | **逻辑修改** | `CalcGridBuildingLevel` 中注入地脉加成 |
|
||||
| `Assets/Scripts/TH1_Logic/Map/MapGenerator.cs` | **新增方法** | `GenerateLeyLines()` 随机生成地脉格,在 `GenerateMap()` 末尾调用 |
|
||||
| `Assets/Scripts/TH1_Renderer/GridRenderer.cs` | **无需修改** | 地脉视觉走 `GetTerrainSprite()` 表格配置,自动生效 |
|
||||
| `Assets/Resources/DataAssets/GridAndResourceDataAssets.asset` | **表格配置** | 在 `TerrainInfo.SpriteList` 中添加 `LeyLine` 对应的美术资源配置(仅 Indian 阵营) |
|
||||
| `Assets/Scripts/TH1_Logic/Action/BuildActionLogic.cs` | 无需修改 | 建造流程已调用 `CalcGridBuildingLevel`,自动生效 |
|
||||
|
||||
---
|
||||
|
||||
### 2.2 第一步:枚举扩展(GridData.cs)
|
||||
|
||||
**文件**:`Assets/Scripts/TH1_Data/GridData.cs`,第 313 行
|
||||
|
||||
**当前代码**:
|
||||
```csharp
|
||||
public enum GridSpType { None, RemiliaGrid, KaguyaGrid, RemiliaGridDark, Max }
|
||||
```
|
||||
|
||||
**修改为**:
|
||||
```csharp
|
||||
public enum GridSpType { None, RemiliaGrid, KaguyaGrid, RemiliaGridDark, LeyLine, Max }
|
||||
```
|
||||
|
||||
> `Max` 始终放最后,`LeyLine` 插在 `RemiliaGridDark` 之后。
|
||||
> MemoryPack 对枚举按整数值序列化,新增枚举值放在 Max 之前不影响旧存档读取,完全向后兼容。
|
||||
|
||||
---
|
||||
|
||||
### 2.3 第二步:地脉加成注入(CityLogic.cs)
|
||||
|
||||
**文件**:`Assets/Scripts/TH1_Logic/City/CityLogic.cs`
|
||||
**函数**:`CalcGridBuildingLevel`(约第 317 行)
|
||||
|
||||
在函数末尾 `if (level > resourceInfo.MaxLevel)` **之前**注入:
|
||||
|
||||
```csharp
|
||||
// ===== 地脉加成(仅 Indian 阵营生效) =====
|
||||
if (mapData.GetPlayerDataByTerritoryGridId(gridData.Id, out var leyLinePlayer)
|
||||
&& leyLinePlayer.CivEnum == CivEnum.Indian)
|
||||
{
|
||||
// 规则1:建筑就建在地脉格上
|
||||
if (gridData.HasSpType(GridSpType.LeyLine))
|
||||
{
|
||||
int bonus = (buildingType == ResourceType.Forge) ? 2 : 3;
|
||||
level += bonus;
|
||||
ret = true;
|
||||
}
|
||||
// 规则2:建筑周围1格存在地脉格(自身不是地脉格时才检查)
|
||||
else
|
||||
{
|
||||
foreach (var grid in aroundGridList)
|
||||
{
|
||||
if (grid == gridData) continue;
|
||||
if (grid.HasSpType(GridSpType.LeyLine))
|
||||
{
|
||||
level += 1;
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ====================
|
||||
|
||||
if (level > resourceInfo.MaxLevel)
|
||||
level = resourceInfo.MaxLevel;
|
||||
return ret;
|
||||
```
|
||||
|
||||
**关键设计说明**:
|
||||
1. **阵营检查**——通过 `GetPlayerDataByTerritoryGridId` 获取该格所属玩家,仅 `CivEnum.Indian` 时才进入地脉加成逻辑
|
||||
2. `else` 保证互斥——在地脉上时不再检查周边,防止同格加成叠加
|
||||
3. `break` 保证邻格不叠加——相邻多个地脉格只加一次 +1
|
||||
4. 上限保护——地脉加成后仍受 `resourceInfo.MaxLevel` 约束
|
||||
5. `aroundGridList` 复用——函数顶部已初始化(`GetAroundGridData(1,1,gridData)`),含 center,无需额外查表
|
||||
|
||||
---
|
||||
|
||||
### 2.4 第三步:地脉地图生成(MapGenerator.cs)
|
||||
|
||||
**文件**:`Assets/Scripts/TH1_Logic/Map/MapGenerator.cs`
|
||||
|
||||
#### 2.4.1 新增私有方法
|
||||
|
||||
```csharp
|
||||
private void GenerateLeyLines(MapData mapData)
|
||||
{
|
||||
// Step #1 计算目标数量:地脉总数 = 城市中心总数 / 3(取整)
|
||||
int leyLineCount = _tribes.Count / 3;
|
||||
if (leyLineCount <= 0) return;
|
||||
|
||||
// Step #2 构建首都排除区(仅首都±1范围,非首都村庄不排除)
|
||||
var cradleExclude = new HashSet<uint>();
|
||||
foreach (var cradle in PlayerCivOri)
|
||||
for (int x = cradle.X - 1; x <= cradle.X + 1; x++)
|
||||
for (int y = cradle.Y - 1; y <= cradle.Y + 1; y++)
|
||||
cradleExclude.Add(MapPosition.CalculatePosId(x, y));
|
||||
|
||||
// Step #3 收集所有合法候选格子
|
||||
// 条件:陆地格 + 不是CityCenter + 不在首都1格范围内
|
||||
var candidates = new List<GridData>();
|
||||
foreach (var gridData in mapData.GridMap.GridList)
|
||||
{
|
||||
if (gridData.Terrain != TerrainType.Land) continue;
|
||||
if (gridData.Resource == ResourceType.CityCenter) continue;
|
||||
if (cradleExclude.Contains(gridData.Pos.PosId)) continue;
|
||||
candidates.Add(gridData);
|
||||
}
|
||||
|
||||
// Step #4 随机打乱候选列表(Fisher-Yates shuffle)
|
||||
for (int i = candidates.Count - 1; i > 0; i--)
|
||||
{
|
||||
int j = Random.Range(0, i + 1);
|
||||
(candidates[i], candidates[j]) = (candidates[j], candidates[i]);
|
||||
}
|
||||
|
||||
// Step #5 取前N个格子,标记为地脉
|
||||
int placed = 0;
|
||||
foreach (var grid in candidates)
|
||||
{
|
||||
if (placed >= leyLineCount) break;
|
||||
grid.SpTypeList.Add(GridSpType.LeyLine);
|
||||
placed++;
|
||||
}
|
||||
|
||||
// Step #6 古明地帝国保底:首都2格内必定有地脉
|
||||
for (int rk = 0; rk < mapData.PlayerMap.PlayerDataList.Count && rk < PlayerCivOri.Count; rk++)
|
||||
{
|
||||
var player = mapData.PlayerMap.PlayerDataList[rk];
|
||||
if (player.CivEnum != CivEnum.Indian) continue;
|
||||
if (!mapData.GridMap.GetGridDataByPos(PlayerCivOri[rk].X, PlayerCivOri[rk].Y, out var cradleGrid)) continue;
|
||||
|
||||
// 检查2格内是否已有地脉
|
||||
var aroundGrids = mapData.GridMap.GetAroundGridData(2, 2, cradleGrid);
|
||||
bool hasLeyLine = false;
|
||||
foreach (var g in aroundGrids)
|
||||
if (g.HasSpType(GridSpType.LeyLine)) { hasLeyLine = true; break; }
|
||||
if (hasLeyLine) continue;
|
||||
|
||||
// 没有则强制在2格内随机选一个合法格子放置
|
||||
for (int i = aroundGrids.Count - 1; i > 0; i--)
|
||||
{
|
||||
int j = Random.Range(0, i + 1);
|
||||
(aroundGrids[i], aroundGrids[j]) = (aroundGrids[j], aroundGrids[i]);
|
||||
}
|
||||
foreach (var g in aroundGrids)
|
||||
{
|
||||
if (g.Terrain != TerrainType.Land) continue;
|
||||
if (g.Resource == ResourceType.CityCenter) continue;
|
||||
if (cradleExclude.Contains(g.Pos.PosId)) continue;
|
||||
g.SpTypeList.Add(GridSpType.LeyLine);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4.2 调用时机
|
||||
|
||||
在 `GenerateMap()` 中,**`CivTerrainFeatureCountControl(mapData)` 之后**、`InitGeoData` 之前调用:
|
||||
|
||||
```csharp
|
||||
public void GenerateMap(MapData mapData)
|
||||
{
|
||||
// ... 原有流程 ...
|
||||
InitGridMapData(mapData);
|
||||
CivTerrainFeatureCountControl(mapData);
|
||||
|
||||
// ===== 新增:生成地脉 =====
|
||||
GenerateLeyLines(mapData);
|
||||
// =========================
|
||||
|
||||
Table.Instance.GeoDataAssets.InitGeoData(mapData);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4.3 排除区说明:仅排除首都,不排除村庄
|
||||
|
||||
`PlayerCivOri` 是玩家摇篮城市(首都)的坐标列表,是 `_tribes` 的子集。
|
||||
`GenerateLeyLines` 在运行时动态构建首都 ±1 的排除区 `HashSet<uint> cradleExclude`,
|
||||
使用 `MapPosition.CalculatePosId(x, y)` 作为 key,与 `gridData.Pos.PosId` 直接比较,无需 `Equals` 重载。
|
||||
|
||||
非首都城市(无人占领的村庄)1 格范围内允许生成地脉,使得村庄附近可以出现地脉,为玩家的扩张选择提供策略价值。但 CityCenter 格本身始终禁止生成地脉(`Resource == ResourceType.CityCenter` 被排除)。
|
||||
|
||||
#### 2.4.4 古明地帝国保底说明
|
||||
|
||||
随机放置完毕后,遍历 `PlayerDataList` 查找 `CivEnum.Indian` 的玩家,通过索引 `rk` 对应 `PlayerCivOri[rk]` 获取首都坐标。
|
||||
调用 `GetAroundGridData(2, 2, cradleGrid)` 获取 2 格范围内所有格子,若其中无地脉则随机选取一个合法格子强制放置。
|
||||
强制放置仍遵守所有约束(陆地、非 CityCenter、不在首都 1 格排除区内),因此保底地脉最近出现在首都切比雪夫距离 = 2 的位置。
|
||||
|
||||
---
|
||||
|
||||
### 2.5 第四步:地脉渲染(GridRenderer.cs,走表格配置)
|
||||
|
||||
**文件**:`Assets/Scripts/TH1_Renderer/GridRenderer.cs`
|
||||
**函数**:`UpdateLand()`(约第 425 行)
|
||||
|
||||
地脉格子的视觉表现**完全由表格配置**,代码层面**不需要**像 RemiliaGrid/KaguyaGrid 那样添加特殊颜色覆写逻辑。
|
||||
|
||||
#### 当前实现
|
||||
|
||||
`GetTerrainSprite()` 已自动支持 `GridSpType.LeyLine`:
|
||||
|
||||
```csharp
|
||||
private void UpdateLand()
|
||||
{
|
||||
if(_land == null) return;
|
||||
var com = _land.GetComponent<SpriteRenderer>();
|
||||
if (com == null) return;
|
||||
var oldSprite = com.sprite;
|
||||
if (oldSprite == null) return;
|
||||
|
||||
com.color = _gridData.Player(Main.MapData, out var _) && _gridData.Terrain == TerrainType.Land
|
||||
? terriColor : Color.white;
|
||||
|
||||
var newSprite = Table.Instance.GridAndResourceDataAssets.GetTerrainSprite(_gridData);
|
||||
com.sprite = newSprite;
|
||||
if (oldSprite != newSprite && _gridData.HasSpType(GridSpType.RemiliaGrid))
|
||||
PlayVFXInSight(new GridVFXParams(GridVFXType.RedMistCreate));
|
||||
}
|
||||
```
|
||||
|
||||
**设计说明**:
|
||||
- **无需代码修改**——`GetTerrainSprite()` 会自动根据 `GridSpType.LeyLine` 返回表格中配置的 Sprite
|
||||
- **阵营限制在表格层实现**——配置 `EmpireGridSpInfoPack` 时指定 `CivId` 为 Indian 对应的 CivId,非 Indian 玩家将匹配不到对应 Sprite
|
||||
- 颜色/图案完全由 `TerrainInfo.SpriteList` 中的美术资源决定
|
||||
|
||||
---
|
||||
|
||||
### 2.6 第五步:建造流程回顾(BuildActionLogic.cs,无需修改)
|
||||
|
||||
建造流程的核心路径如下:
|
||||
|
||||
```
|
||||
BuildAction.Execute()
|
||||
└─ Main.CityLogic.CalcGridBuildingLevel(...) // ← 地脉加成已在此注入,自动生效
|
||||
gridData.buildingLevel = buildingLevel
|
||||
└─ Main.PlayerLogic.UpdateTerritoryAllBuildingLevel(...)
|
||||
└─ UpdateGridBuildingData_LogicView(...)
|
||||
└─ CalcGridBuildingLevel(...) // ← 全量刷新时同样生效
|
||||
```
|
||||
|
||||
第 62-64 行的 cityExp 计算使用最终的 `buildingLevel`,地脉加成后 cityExp 自动跟随正确数值,无需额外修改。
|
||||
|
||||
---
|
||||
|
||||
## 三、数值示例
|
||||
|
||||
Forge(冶炼厂)示例(周围有 Mine,基础 level = 2,MaxLevel = 4):
|
||||
|
||||
| 格子情况 | 基础 level | 地脉加成 | 最终 level |
|
||||
|----------|-----------|---------|-----------|
|
||||
| 无地脉 | 2 | 0 | **2** |
|
||||
| 建在地脉上 | 2 | +2(Forge专用) | **4**(触上限) |
|
||||
| 周边1格有地脉 | 2 | +1 | **3** |
|
||||
|
||||
Windmill(风车)示例(周围有 Farm,基础 level = 1,MaxLevel = 4):
|
||||
|
||||
| 格子情况 | 基础 level | 地脉加成 | 最终 level |
|
||||
|----------|-----------|---------|-----------|
|
||||
| 无地脉 | 1 | 0 | **1** |
|
||||
| 建在地脉上 | 1 | +3 | **4**(触上限) |
|
||||
| 周边1格有地脉 | 1 | +1 | **2** |
|
||||
|
||||
---
|
||||
|
||||
## 四、开发任务拆解
|
||||
|
||||
### 必做
|
||||
- [ ] `GridData.cs`:`GridSpType` 枚举加入 `LeyLine`
|
||||
- [ ] `CityLogic.cs`:`CalcGridBuildingLevel` 注入地脉加成逻辑
|
||||
- [ ] `MapGenerator.cs`:新增 `GenerateLeyLines()` 方法,并在 `GenerateMap()` 末尾调用
|
||||
- [ ] `GridRenderer.cs`:**无需修改**,地脉视觉走表格配置
|
||||
- [ ] `GridAndResourceDataAssets.asset`:在 `TerrainInfoList` → `Land` → `SpriteList` 中添加 `LeyLine` 对应的 `EmpireGridSpInfoPack` 配置(指定印度阵营对应的 CivId)
|
||||
|
||||
### 可选(视需求)
|
||||
- [ ] UI Tooltip:建造时预先显示地脉加成数值(需在 `CheckCan` 或 UI Controller 调用 `CalcGridBuildingLevel` 做预算)
|
||||
- [ ] `AddSpType`:为 `LeyLine` 添加专属校验规则(如只能在陆地格)
|
||||
|
||||
---
|
||||
|
||||
## 五、风险与注意事项
|
||||
|
||||
1. **MemoryPack 兼容性**:`GridSpType.LeyLine` 是新枚举值,旧存档不含此值,反序列化时 `SpTypeList` 不包含 `LeyLine` 即视为无地脉,**完全向后兼容**。
|
||||
|
||||
2. **`_innerCityGrid` 含中心格**:`GenerateTribes()` 中 `_innerCityGrid` 的循环范围是 `±1`,已包含城市中心本身(x==cent.X, y==cent.Y),因此 Contains 检查已覆盖中心格,无需额外处理。
|
||||
|
||||
3. **`_innerCityGrid` 使用 `MapPosition` 相等比较**:`MapPosition` 已重载 `Equals`/`GetHashCode`/`==`(见 GridData.cs 第 873-901 行),`List<MapPosition>.Contains` 语义正确,可放心使用。
|
||||
|
||||
4. **地脉生成在 `CivTerrainFeatureCountControl` 之后**:保底地形修正可能会改变某些格子的 Terrain,`GenerateLeyLines` 在其之后执行,读取的 `Terrain` 是最终值,不存在顺序问题。
|
||||
|
||||
5. **`aroundGridList` 含 center**:`CalcGridBuildingLevel` 中 `GetAroundGridData(1,1,gridData)` 返回含 center 的列表,邻格检查时已用 `grid == gridData` 跳过,逻辑正确。
|
||||
|
||||
6. **UpdateTerritoryAllBuildingLevel 刷新**:占领城市、新建城市等场景会触发全量刷新,地脉加成会被自动重新计算,无需额外处理。
|
||||
|
||||
7. **AI 评分**:AI 在 `AITechScoreCalculator` / `AIActionBase` 中评估建筑价值,若需 AI 感知地脉优势,需额外修改 AI 评估逻辑(属于后续优化,非核心需求)。
|
||||
37
Design/final/mechanics/map-city-tech.html
Normal file
37
Design/final/mechanics/map-city-tech.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 地图、城市与科技</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrow">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Mechanics / Economy</p>
|
||||
<h1>地图、城市与科技</h1>
|
||||
<p class="lead">经营层负责把地形、资源、城市升级和科技选择串成清晰的长期规划。东方机制应尽量附着在这条主线上,而不是另开一套难以理解的资源系统。</p>
|
||||
</header>
|
||||
<section class="doc-section">
|
||||
<h2>经营层主线</h2>
|
||||
<ol class="rule-list">
|
||||
<li><strong>地块:</strong>提供移动、建造和资源限制。</li>
|
||||
<li><strong>资源:</strong>通过科技解锁采集或建筑转换。</li>
|
||||
<li><strong>城市:</strong>将资源转化为发展度、生产能力和升级奖励。</li>
|
||||
<li><strong>科技:</strong>决定玩家能对地图做什么,也决定单位与建筑的可用集合。</li>
|
||||
<li><strong>文化卡:</strong>提供中后期方向选择,可承担部分东方化的国家制度表达。</li>
|
||||
</ol>
|
||||
</section>
|
||||
<section class="doc-section">
|
||||
<h2>设计检查项</h2>
|
||||
<div class="callout-grid">
|
||||
<article><h3>资源是否可读</h3><p>玩家看到地块时,应能判断它当前能做什么、研究科技后能做什么。</p></article>
|
||||
<article><h3>城市升级是否有选择</h3><p>升级奖励不能只是数值堆叠,应提供扩张、经济、军事或英雄成长方向。</p></article>
|
||||
<article><h3>科技是否改变行动</h3><p>科技最好解锁新的地图操作或战术路径,而不只是属性加成。</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="doc-footer"><a href="index.html">返回机制总览</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
39
Design/final/mechanics/unit-skill-system.html
Normal file
39
Design/final/mechanics/unit-skill-system.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>TH1 单位与技能系统</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrow">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Mechanics / Units & Skills</p>
|
||||
<h1>单位与技能系统</h1>
|
||||
<p class="lead">单位提供可被玩家直接操作的棋子,技能提供规则差异。TH1 的深度主要来自单位模板、英雄等级、状态技能和触发技能的组合。</p>
|
||||
</header>
|
||||
<section class="doc-section">
|
||||
<h2>单位分层</h2>
|
||||
<div class="flow-grid three">
|
||||
<article><h3>基础单位</h3><p>战士、骑兵、弓兵、船只等低复杂度单位,支撑 Polytopia 式基础战斗。</p></article>
|
||||
<article><h3>特色单位</h3><p>阵营替换或专属训练单位,用少量规则表达势力主题。</p></article>
|
||||
<article><h3>英雄/巨人单位</h3><p>拥有高权重技能和成长价值,是东方扩展层的主要承载者。</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section class="doc-section">
|
||||
<h2>技能分类建议</h2>
|
||||
<table class="doc-table">
|
||||
<thead><tr><th>分类</th><th>例子</th><th>文档需要记录</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>基础能力</td><td>移动、反击、射程、航海。</td><td>触发条件、是否影响 AI 评分。</td></tr>
|
||||
<tr><td>状态能力</td><td>恐惧、满月、金刚身、粗糙身。</td><td>持续时间、层数、刷新规则、显示方式。</td></tr>
|
||||
<tr><td>触发能力</td><td>攻击后、受伤后、死亡后、回合开始。</td><td>生命周期时机、动画表现、联机同步。</td></tr>
|
||||
<tr><td>转换/召唤</td><td>单位变体、生成单位、资源转换。</td><td>目标选择、合法性、失败回滚。</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<footer class="doc-footer"><a href="index.html">返回机制总览</a></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
149
Design/final/narrative/civilizations.html
Normal file
149
Design/final/narrative/civilizations.html
Normal file
@ -0,0 +1,149 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>文案剧情 / 文明</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrative-shell">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Narrative / Civilizations</p>
|
||||
<h1>文案剧情:文明</h1>
|
||||
<p class="lead">按当前 PlayerDataAssets 与 CivDataAssets 归类阵营文案。这里把东方势力层和 Polytopia 文明底板并列展示,方便后续决定哪些文案保留、替换或扩写。</p>
|
||||
<div class="source-strip" aria-label="DataAsset 来源">
|
||||
<span>PlayerDataAssets:CivName / ForceName / LeaderName / EmpireDesc / LeaderDesc</span>
|
||||
<span>CivDataAssets:CivName / CityInfoList / CityDescription</span>
|
||||
<span>LibraryDataAssets:奇观图鉴与英雄图鉴补充语气</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="faction-nav" aria-label="阵营导航">
|
||||
<a href="#remilia">红魔馆</a>
|
||||
<a href="#kaguya">永远亭</a>
|
||||
<a href="#kanako">守矢神社</a>
|
||||
<a href="#satori">地灵殿</a>
|
||||
<a href="#reimu">博丽神社</a>
|
||||
</nav>
|
||||
|
||||
<section id="remilia" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>红魔馆</h2>
|
||||
<p>ForceName:斯卡雷特帝国 / LeaderName:蕾米莉亚 / CivName:埃及</p>
|
||||
</div>
|
||||
<div class="civilization-grid">
|
||||
<article class="copy-card">
|
||||
<h3>阵营文案定位 <small>PlayerInfo</small></h3>
|
||||
<p class="copy-desc">红雾笼罩尼罗河两岸,生死、智慧与禁忌分别由咲夜、帕秋莉、芙兰等红魔馆成员承接。领袖形象是立于金字塔之巅的永生法老蕾米莉亚。</p>
|
||||
<p class="copy-diag">高傲的红魔馆大小姐抽到了埃及帝国卡。</p>
|
||||
<div class="copy-meta"><span>红雾</span><span>法老</span><span>馆主权威</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>文明底板 <small>CivDataAssets</small></h3>
|
||||
<p class="copy-desc">埃及文明城市名已经完整配置,城市描述目前是通用占位句。</p>
|
||||
<div class="city-list">
|
||||
<span>开罗</span><span>亚历山大</span><span>孟斐斯</span><span>底比斯</span><span>卢克索</span><span>阿斯旺</span><span>吉萨</span><span>西奈</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="kaguya" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>永远亭</h2>
|
||||
<p>ForceName:蓬莱山帝国 / LeaderName:蓬莱山辉夜 / CivName:法兰西</p>
|
||||
</div>
|
||||
<div class="civilization-grid">
|
||||
<article class="copy-card">
|
||||
<h3>阵营文案定位 <small>PlayerInfo</small></h3>
|
||||
<p class="copy-desc">妖怪兔近卫队、竹林狼上校与帝国元帅构成战队化军团叙事。辉夜以月之公主与电竞高手双重身份,站在凯旋门前写下最强陆军传说。</p>
|
||||
<p class="copy-diag">优雅而怠惰的月之公主抽到了法兰西帝国卡。</p>
|
||||
<div class="copy-meta"><span>永夜</span><span>法兰西荣耀</span><span>战队训练</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>文明底板 <small>CivDataAssets</small></h3>
|
||||
<p class="copy-desc">法兰西文明城市名已经完整配置,城市描述目前是通用占位句。</p>
|
||||
<div class="city-list">
|
||||
<span>巴黎</span><span>凡尔赛</span><span>里昂</span><span>马赛</span><span>波尔多</span><span>图卢兹</span><span>里尔</span><span>南特</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="kanako" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>守矢神社</h2>
|
||||
<p>ForceName:守矢帝国 / LeaderName:八坂神奈子 / CivName:德意志</p>
|
||||
</div>
|
||||
<div class="civilization-grid">
|
||||
<article class="copy-card">
|
||||
<h3>阵营文案定位 <small>PlayerInfo</small></h3>
|
||||
<p class="copy-desc">土著神奠基,风雨神推动工业齿轮。神奈子把技术与信仰熔铸成铁与血,整体调性是产业化、技术化、商业化的守矢集团。</p>
|
||||
<p class="copy-diag">野心勃勃的山神殿下抽到了德意志帝国卡。守矢神社已是比赛最大的冠名赞助商。</p>
|
||||
<div class="copy-meta"><span>信仰即流量</span><span>钢铁王座</span><span>技术变革</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>文明底板 <small>CivDataAssets</small></h3>
|
||||
<p class="copy-desc">德意志文明城市名已经完整配置,城市描述目前是通用占位句。</p>
|
||||
<div class="city-list">
|
||||
<span>柏林</span><span>哥尼斯堡</span><span>波茨坦</span><span>马格德堡</span><span>汉堡</span><span>不来梅</span><span>德累斯顿</span><span>莱比锡</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="satori" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>地灵殿</h2>
|
||||
<p>ForceName:古明地帝国 / LeaderName:古明地觉 / CivName:印度</p>
|
||||
</div>
|
||||
<div class="civilization-grid">
|
||||
<article class="copy-card">
|
||||
<h3>阵营文案定位 <small>PlayerInfo</small></h3>
|
||||
<p class="copy-desc">雷霆鬼王、苏利耶神子、无心神明与旧地狱业火共同拼成吠陀史诗式的地底帝国。觉的核心形象是洞彻万心的第三只眼。</p>
|
||||
<p class="copy-diag">沉静而可怖的地灵殿主人抽到了印度帝国卡。</p>
|
||||
<div class="copy-meta"><span>旧地狱</span><span>吠陀史诗</span><span>读心</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>文明底板 <small>CivDataAssets</small></h3>
|
||||
<p class="copy-desc">印度文明城市名已经完整配置,城市描述目前是通用占位句。</p>
|
||||
<div class="city-list">
|
||||
<span>德里</span><span>新德里</span><span>瓦拉纳西</span><span>巴特那</span><span>华氏城</span><span>阿约提亚</span><span>阿格拉</span><span>斋浦尔</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="reimu" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>博丽神社</h2>
|
||||
<p>ForceName:博丽帝国 / LeaderName:博丽灵梦 / CivName:维京</p>
|
||||
</div>
|
||||
<div class="civilization-grid">
|
||||
<article class="copy-card">
|
||||
<h3>阵营文案定位 <small>PlayerInfo</small></h3>
|
||||
<p class="copy-desc empty-copy">EmpireDesc 与 LeaderDesc 当前为空。LibraryDataAssets 中灵梦还是“代理木偶”占位,因此博丽神社暂时只有阵营壳和文明底板,没有正式剧情定位。</p>
|
||||
<div class="copy-meta"><span>待补</span><span>博丽结界</span><span>幻想乡中心</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>文明底板 <small>CivDataAssets</small></h3>
|
||||
<p class="copy-desc">维京文明城市名已经完整配置,城市描述目前是通用占位句。</p>
|
||||
<div class="city-list">
|
||||
<span>奥斯陆</span><span>卑尔根</span><span>特隆赫姆</span><span>斯塔万格</span><span>特罗姆瑟</span><span>克里斯蒂安桑</span><span>德拉门</span><span>博德</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc-section note">
|
||||
<h2>下一轮整理重点</h2>
|
||||
<ol class="rule-list">
|
||||
<li>博丽神社需要补齐 EmpireDesc、LeaderDesc、正式英雄组和图鉴文案。</li>
|
||||
<li>CivDataAssets 的 CityDescription 目前基本是通用占位句,可以后续按文明或阵营改写。</li>
|
||||
<li>LibraryDataAssets 的奇观文案现在更偏传统文明,需要决定是否要映射成东方阵营风格。</li>
|
||||
</ol>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
245
Design/final/narrative/heroes.html
Normal file
245
Design/final/narrative/heroes.html
Normal file
@ -0,0 +1,245 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>文案剧情 / 英雄</title>
|
||||
<link rel="stylesheet" href="/api/design/shared/design-doc.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc-shell narrative-shell">
|
||||
<header class="doc-hero compact">
|
||||
<p class="eyebrow">Narrative / Heroes</p>
|
||||
<h1>文案剧情:英雄</h1>
|
||||
<p class="lead">按阵营归档 Library、Hero、Player 三类 DataAsset 中已经存在的英雄文案。当前页只做归类和设计入口,不在这里重写正式文案。</p>
|
||||
<div class="source-strip" aria-label="DataAsset 来源">
|
||||
<span>LibraryDataAssets:Name / SubTitle / Desc / Diag</span>
|
||||
<span>HeroDataAssets:TaskList / Desc</span>
|
||||
<span>PlayerDataAssets:ForceName / LeaderName</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav class="faction-nav" aria-label="阵营导航">
|
||||
<a href="#remilia">红魔馆</a>
|
||||
<a href="#kaguya">永远亭</a>
|
||||
<a href="#kanako">守矢神社</a>
|
||||
<a href="#satori">地灵殿</a>
|
||||
<a href="#reimu">博丽神社</a>
|
||||
</nav>
|
||||
|
||||
<section id="remilia" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>红魔馆</h2>
|
||||
<p>PlayerInfo:斯卡雷特帝国 / 领袖 蕾米莉亚 / 文明底板 埃及</p>
|
||||
</div>
|
||||
<div class="hero-copy-grid">
|
||||
<article class="copy-card">
|
||||
<h3>蕾米莉亚 · 斯卡雷特 <small>GiantType 2</small></h3>
|
||||
<p class="copy-subtitle">帝国的血之君主</p>
|
||||
<p class="copy-desc">饰演奥西里斯的鲜红幼月。红魔馆至高无上的主人,命运与威严的化身,为了那座“高雅”的冠军奖杯而参赛。</p>
|
||||
<p class="copy-diag">哼哼,这可是我重金买通裁判选到的最强阵营……你说我被骗了?不、不可能!</p>
|
||||
<p class="copy-task">任务文案线索:帝国累计金币、创造红雾领地。</p>
|
||||
<div class="copy-meta"><span>领袖</span><span>王</span><span>红雾</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>帕秋莉 · 诺蕾姬 <small>GiantType 5</small></h3>
|
||||
<p class="copy-subtitle">帝国的魔女</p>
|
||||
<p class="copy-desc">饰演克里欧帕特拉的魔女。红魔馆地下大图书馆的贤者,对纷争兴趣不高,以扩建图书馆为条件答应参赛。</p>
|
||||
<p class="copy-diag">……用“魅力”交涉的女王??那真是找错人了呢……看在皇家图书馆的份上,我勉强接受。</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、消耗魔力石。</p>
|
||||
<div class="copy-meta"><span>后</span><span>知识</span><span>资源消耗</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>十六夜咲夜 <small>GiantType 3</small></h3>
|
||||
<p class="copy-subtitle">帝国的女仆长</p>
|
||||
<p class="copy-desc">饰演阿努比斯的人类少女。红魔馆女仆长,陪任性的大小姐一同加入游戏。</p>
|
||||
<p class="copy-diag">亡者引路人吗?不过大小姐是不会死的吧。我变得多余了呢。</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、承受伤害、击杀敌方单位。</p>
|
||||
<div class="copy-meta"><span>相</span><span>时间</span><span>护卫</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>芙兰朵露 · 斯卡雷特 <small>GiantType 1</small></h3>
|
||||
<p class="copy-subtitle">帝国的禁忌</p>
|
||||
<p class="copy-desc">饰演赛特的恶魔之妹。被囚禁于红魔馆地下室的终极秘密,纯粹的破坏化身。</p>
|
||||
<p class="copy-diag">诶!这个叫赛特的剧本上,好像说要把姐姐大人干掉啊!嘻嘻……</p>
|
||||
<p class="copy-task">任务文案线索:占领村庄或敌城、累计击杀单位。</p>
|
||||
<div class="copy-meta"><span>马</span><span>破坏</span><span>扩张</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>红美铃 <small>GiantType 4</small></h3>
|
||||
<p class="copy-subtitle">帝国之龙</p>
|
||||
<p class="copy-desc">饰演荷鲁斯的武术家。红魔馆忠诚的门卫,因工作期间睡觉,醒来时发现自己已经在比赛现场。</p>
|
||||
<p class="copy-diag">为大小姐复仇?赴汤蹈……你说复仇的对象是妹妹大人?!我、我还得去看门……</p>
|
||||
<p class="copy-task">任务文案线索:累计造成伤害。</p>
|
||||
<div class="copy-meta"><span>车</span><span>门卫</span><span>正面战斗</span></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="kaguya" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>永远亭</h2>
|
||||
<p>PlayerInfo:蓬莱山帝国 / 领袖 蓬莱山辉夜 / 文明底板 法兰西</p>
|
||||
</div>
|
||||
<div class="hero-copy-grid">
|
||||
<article class="copy-card">
|
||||
<h3>蓬莱山辉夜 <small>GiantType 6</small></h3>
|
||||
<p class="copy-subtitle">帝国的永远</p>
|
||||
<p class="copy-desc">饰演拿破仑的月之公主。永远亭主人,也是一位电竞高手,正在训练自家宠物打造战队。</p>
|
||||
<p class="copy-diag">这游戏目前的阵营太少了,平衡性非常糟糕,我的介绍页面也不够华丽,勉强给个好评吧。</p>
|
||||
<p class="copy-task">任务文案线索:累计造成伤害、施加状态。</p>
|
||||
<div class="copy-meta"><span>王</span><span>领袖</span><span>战队叙事</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>八意永琳 <small>GiantType 9</small></h3>
|
||||
<p class="copy-subtitle">帝国的月之头脑</p>
|
||||
<p class="copy-desc">饰演贝尔蒂埃元帅的月之头脑。永远亭最强大的贤者,配合辉夜参赛的同时借机推销新药。</p>
|
||||
<p class="copy-diag">拿破仑与贝尔蒂埃……这结局还真是令人唏嘘。突然有点怀念与公主的过往呢。</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、提供生命回复。</p>
|
||||
<div class="copy-meta"><span>后</span><span>医疗</span><span>月都贤者</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>因幡帝 <small>GiantType 8</small></h3>
|
||||
<p class="copy-subtitle">帝国的欺诈师</p>
|
||||
<p class="copy-desc">饰演红衣主教的妖怪兔。迷途竹林的主人,奸诈而狡猾,不久前刚冒充比赛裁判敲了一笔。</p>
|
||||
<p class="copy-diag">全体目光向我看齐!“幸运豁免券”都能让你的罪孽一笔勾销!</p>
|
||||
<p class="copy-task">任务文案线索:挖掘遗迹、累计获得金币。</p>
|
||||
<div class="copy-meta"><span>相</span><span>幸运</span><span>欺诈</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>铃仙·优昙华院·因幡 <small>GiantType 7</small></h3>
|
||||
<p class="copy-subtitle">帝国的狂气之瞳</p>
|
||||
<p class="copy-desc">饰演让·拉纳元帅的月兔。作为辉夜的宠物,正在接受严格的电竞训练。</p>
|
||||
<p class="copy-diag">呜呜……我、我不会再输了!</p>
|
||||
<p class="copy-task">任务文案线索:占领村庄或敌城、创造月兔幻象、施加状态。</p>
|
||||
<div class="copy-meta"><span>马</span><span>幻象</span><span>狂气</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>藤原妹红 <small>GiantType 10</small></h3>
|
||||
<p class="copy-subtitle">帝国的不死鸟</p>
|
||||
<p class="copy-desc">饰演贝尔纳多特元帅的不死鸟。辉夜的仇敌,因为找辉夜决斗时被哄骗着参赛。</p>
|
||||
<p class="copy-diag">不管辉夜你耍什么把戏,我都会正面把你……哈?不能攻击队友?我们是队友?</p>
|
||||
<p class="copy-task">任务文案线索:累计造成伤害。</p>
|
||||
<div class="copy-meta"><span>车</span><span>不死</span><span>正面战斗</span></div>
|
||||
</article>
|
||||
</div>
|
||||
<p class="table-note">数据问题:永远亭多名英雄的 EnglishName 字段当前仍是 HONG MEILING,需要在 LibraryDataAssets 中单独修正。</p>
|
||||
</section>
|
||||
|
||||
<section id="kanako" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>守矢神社</h2>
|
||||
<p>PlayerInfo:守矢帝国 / 领袖 八坂神奈子 / 文明底板 德意志</p>
|
||||
</div>
|
||||
<div class="hero-copy-grid">
|
||||
<article class="copy-card">
|
||||
<h3>八坂神奈子 <small>GiantType 11</small></h3>
|
||||
<p class="copy-subtitle">帝国的全能神</p>
|
||||
<p class="copy-desc">饰演腓特烈二世的全能之神。风神、山神、战神与科技变革之神,正试图将比赛权利转入守矢产业名下。</p>
|
||||
<p class="copy-diag">留存和日活都很完美。流量便是信仰,游戏市场就是新的战场,是时候让守矢集团接管了。</p>
|
||||
<p class="copy-task">任务文案线索:完成科技研发、累计造成伤害。</p>
|
||||
<div class="copy-meta"><span>王</span><span>科技</span><span>信仰商业化</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>洩矢诹访子 <small>GiantType 12</small></h3>
|
||||
<p class="copy-subtitle">帝国的土著神</p>
|
||||
<p class="copy-desc">饰演路易斯·冯·梅克伦堡王后的土著神。守矢神社供奉的神之一,看到比赛后擅自给队友报名。</p>
|
||||
<p class="copy-diag">虽说路易斯王后和神奈子的角色并非一对……不过我和她确实也只是商业伙伴的关系嘛。</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、召唤物等级、召唤物击杀。</p>
|
||||
<div class="copy-meta"><span>后</span><span>召唤</span><span>土著神</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>东风谷早苗 <small>GiantType 13</small></h3>
|
||||
<p class="copy-subtitle">帝国的现人神</p>
|
||||
<p class="copy-desc">饰演俾斯麦的现人神。守矢神社巫女、风祝与现人神,因为冠军限定机甲而参赛。</p>
|
||||
<p class="copy-diag">为了冠军机甲……咳,为了帝国的荣耀,从今天起,我就是“铁血风祝”了!</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、施加乘风、掷出大吉或大凶。</p>
|
||||
<div class="copy-meta"><span>相</span><span>风祝</span><span>随机</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>射命丸文 <small>GiantType 14</small></h3>
|
||||
<p class="copy-subtitle">帝国的最速记者</p>
|
||||
<p class="copy-desc">饰演克劳塞维茨的鸦天狗。妖怪之山新闻记者,似乎与神奈子达成交易后加入比赛。</p>
|
||||
<p class="copy-diag">黑哨操纵!权钱交易!想看更多比赛的惊天大瓜?请订阅《文文。新闻》特刊!</p>
|
||||
<p class="copy-task">任务文案线索:占领村庄或敌城、累计造成溅射伤害。</p>
|
||||
<div class="copy-meta"><span>马</span><span>高速</span><span>媒体</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>犬走椛 <small>GiantType 15</small></h3>
|
||||
<p class="copy-subtitle">帝国的狂犬</p>
|
||||
<p class="copy-desc">饰演条顿骑士团长的白狼天狗。妖怪之山巡逻警卫,受文邀请后来参赛。</p>
|
||||
<p class="copy-diag">这个“帝国”棋似乎和将棋有几分类似……应该也能磨练思维和意志吧。</p>
|
||||
<p class="copy-task">任务文案线索:累计造成伤害、异端死亡数量。</p>
|
||||
<div class="copy-meta"><span>车</span><span>巡逻</span><span>纪律</span></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="satori" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>地灵殿</h2>
|
||||
<p>PlayerInfo:古明地帝国 / 领袖 古明地觉 / 文明底板 印度</p>
|
||||
</div>
|
||||
<div class="hero-copy-grid">
|
||||
<article class="copy-card">
|
||||
<h3>古明地觉 <small>GiantType 16</small></h3>
|
||||
<p class="copy-subtitle">帝国的第三只眼</p>
|
||||
<p class="copy-desc">饰演罗摩的读心妖怪。原本在家看书,却发现家人突然出现在电视上,于是踏上“寻亲”之旅。</p>
|
||||
<p class="copy-diag">找回被魔王劫走的妻子悉多吗……恋是那个魔王才对吧?</p>
|
||||
<p class="copy-task">任务文案线索:施加恐惧、施加心理创伤。</p>
|
||||
<div class="copy-meta"><span>王</span><span>读心</span><span>恐惧</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>古明地恋 <small>GiantType 17</small></h3>
|
||||
<p class="copy-subtitle">帝国的本我</p>
|
||||
<p class="copy-desc">饰演奎师那的无意识妖怪。正在野外排练“大变活猫”,突然出现在比赛舞台中央。</p>
|
||||
<p class="copy-diag">…诶?大家为什么一副见鬼的表情呀?出场魔术太成功了吗,呼呼呼♪</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、累计造成伤害。</p>
|
||||
<div class="copy-meta"><span>后</span><span>隐身</span><span>无意识</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>火焰猫燐 <small>GiantType 20</small></h3>
|
||||
<p class="copy-subtitle">帝国的地狱火车</p>
|
||||
<p class="copy-desc">饰演阿周那的猫车。陪恋排练魔术节目,被装进魔术箱后出现在比赛现场聚光灯下。</p>
|
||||
<p class="copy-diag">恋大人!这个宴会好像要“报名”才能参加哦,交给我来搞定吧!</p>
|
||||
<p class="copy-task">任务文案线索:探索迷雾、获得粗钝身/金刚身层数。</p>
|
||||
<div class="copy-meta"><span>相</span><span>猫车</span><span>承伤</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>灵乌路空 <small>GiantType 18</small></h3>
|
||||
<p class="copy-subtitle">帝国的神之火</p>
|
||||
<p class="copy-desc">饰演迦尔纳的地狱鸦。陪恋排练魔术时睡在帽子里,醒来后已成为比赛队伍成员。</p>
|
||||
<p class="copy-diag">只要把这里烧得一干二净,我们的魔术就算大成功了吧!</p>
|
||||
<p class="copy-task">任务文案线索:占领村庄或敌城、累计击杀单位。</p>
|
||||
<div class="copy-meta"><span>马</span><span>核热</span><span>破坏</span></div>
|
||||
</article>
|
||||
<article class="copy-card">
|
||||
<h3>星熊勇仪 <small>GiantType 19</small></h3>
|
||||
<p class="copy-subtitle">帝国的雷霆鬼王</p>
|
||||
<p class="copy-desc">饰演因陀罗的鬼王。意外收到地灵殿主人的请求,还没听清楚就豪爽答应。</p>
|
||||
<p class="copy-diag">交给我吧!保护三个小姑娘罢了!呃……你是说保护比赛现场的观众是嘛……</p>
|
||||
<p class="copy-task">任务文案线索:累计造成伤害。</p>
|
||||
<div class="copy-meta"><span>车</span><span>鬼王</span><span>正面战斗</span></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="reimu" class="doc-section faction-section">
|
||||
<div class="faction-head">
|
||||
<h2>博丽神社</h2>
|
||||
<p>PlayerInfo:博丽帝国 / 领袖 博丽灵梦 / 文明底板 维京</p>
|
||||
</div>
|
||||
<div class="hero-copy-grid">
|
||||
<article class="copy-card">
|
||||
<h3>博丽灵梦(木偶) <small>GiantType 21</small></h3>
|
||||
<p class="copy-subtitle empty-copy">正式英雄文案待补</p>
|
||||
<p class="copy-desc">LibraryDataAssets 当前只有占位说明:大赛主办方提供的代理木偶。</p>
|
||||
<p class="copy-task">HeroDataAssets 当前未归档到正式任务线。PlayerDataAssets 的 EmpireDesc / LeaderDesc 为空。</p>
|
||||
<div class="copy-meta"><span>待设计</span><span>代理木偶</span></div>
|
||||
</article>
|
||||
</div>
|
||||
<p class="table-note">博丽神社目前只建立阵营入口,正式英雄组、图鉴描述和领袖剧情需要后续补齐。</p>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
646
Design/shared/design-doc.css
Normal file
646
Design/shared/design-doc.css
Normal file
@ -0,0 +1,646 @@
|
||||
:root {
|
||||
--bg: #f5f7fb;
|
||||
--paper: #ffffff;
|
||||
--ink: #1f2937;
|
||||
--muted: #64748b;
|
||||
--line: #dbe3ef;
|
||||
--blue: #2563eb;
|
||||
--cyan: #0891b2;
|
||||
--green: #059669;
|
||||
--orange: #d97706;
|
||||
--red: #dc2626;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", "Microsoft YaHei", Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
a { color: var(--blue); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
.doc-shell {
|
||||
width: min(1180px, calc(100vw - 40px));
|
||||
margin: 0 auto;
|
||||
padding: 28px 0 56px;
|
||||
}
|
||||
|
||||
.doc-shell.narrow { width: min(920px, calc(100vw - 40px)); }
|
||||
|
||||
.doc-hero {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.doc-hero.compact { padding: 24px 28px; }
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: var(--cyan);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .08em;
|
||||
}
|
||||
|
||||
h1, h2, h3 { line-height: 1.25; margin: 0; }
|
||||
h1 { font-size: 34px; }
|
||||
h2 { font-size: 22px; margin-bottom: 12px; }
|
||||
h3 { font-size: 16px; margin-bottom: 8px; }
|
||||
|
||||
.lead {
|
||||
margin: 14px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.meta-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.meta-grid div {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.meta-grid span {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.meta-grid strong { font-size: 14px; }
|
||||
|
||||
.doc-toc {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.doc-toc a {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.doc-section {
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.doc-section.note {
|
||||
border-left: 4px solid var(--orange);
|
||||
}
|
||||
|
||||
.flow-grid,
|
||||
.callout-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.flow-grid.three,
|
||||
.callout-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.flow-grid article,
|
||||
.callout-grid article,
|
||||
.system-map a {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
.flow-grid p,
|
||||
.callout-grid p { margin: 0; color: var(--muted); font-size: 14px; }
|
||||
|
||||
.split {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.split p { color: var(--muted); margin-bottom: 0; }
|
||||
|
||||
.pill-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.pill-list span {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.system-map {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.system-map strong,
|
||||
.system-map span { display: block; }
|
||||
.system-map span { color: var(--muted); font-size: 13px; margin-top: 4px; }
|
||||
|
||||
.doc-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.doc-table th,
|
||||
.doc-table td {
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.doc-table th {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.rule-list {
|
||||
margin: 0;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.rule-list li { margin: 8px 0; }
|
||||
|
||||
.doc-footer {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
padding: 8px 0 0;
|
||||
}
|
||||
|
||||
/* Design tree landing page */
|
||||
.tree-shell {
|
||||
width: min(1280px, calc(100vw - 36px));
|
||||
padding-top: 22px;
|
||||
}
|
||||
|
||||
.design-tree {
|
||||
min-height: calc(100vh - 44px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tree-node:hover {
|
||||
text-decoration: none;
|
||||
border-color: #93c5fd;
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.tree-root {
|
||||
align-self: center;
|
||||
min-width: 220px;
|
||||
padding: 16px 26px;
|
||||
font-size: 26px;
|
||||
border-color: #93c5fd;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.tree-l1 {
|
||||
width: 100%;
|
||||
padding: 12px 18px;
|
||||
font-size: 20px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.tree-l2 {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tree-branches {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tree-branches.two {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.tree-branches.three {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.tree-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: rgba(255,255,255,.74);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.tree-card.compact {
|
||||
background: var(--paper);
|
||||
}
|
||||
|
||||
.tree-leaves {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.tree-leaves.inline {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.tree-leaves a {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 7px;
|
||||
padding: 7px 10px;
|
||||
background: #fbfdff;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tree-leaves a:hover {
|
||||
border-color: #bfdbfe;
|
||||
background: #eff6ff;
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.tree-group {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.tree-label {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
/* Product principles */
|
||||
.principles-shell {
|
||||
width: min(980px, calc(100vw - 40px));
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.principles-page {
|
||||
width: 100%;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.principles-header {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.principles-header h1 {
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
.principles-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 20px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.principles-list li {
|
||||
display: grid;
|
||||
grid-template-columns: 72px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
min-height: 86px;
|
||||
padding: 18px 22px;
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.principle-index {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 8px;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.principles-list strong {
|
||||
font-size: 20px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* Hero foundation */
|
||||
.hero-foundation-shell {
|
||||
width: min(1280px, calc(100vw - 36px));
|
||||
}
|
||||
|
||||
.class-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.class-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
.class-rank {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
color: #ffffff;
|
||||
font-size: 20px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.class-card p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.class-xiang .class-rank { background: #0891b2; }
|
||||
.class-hou .class-rank { background: #db2777; }
|
||||
.class-wang .class-rank { background: #ca8a04; }
|
||||
.class-che .class-rank { background: #dc2626; }
|
||||
.class-ma .class-rank { background: #7c3aed; }
|
||||
|
||||
.role-table th,
|
||||
.role-table td,
|
||||
.faction-class-table th,
|
||||
.faction-class-table td {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.role-table td:first-child,
|
||||
.faction-class-table td:first-child {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.grade-s {
|
||||
color: #eab308;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.check {
|
||||
background: #86d47c;
|
||||
color: #ffffff;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.table-note {
|
||||
margin: 10px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.muted-cell {
|
||||
color: var(--muted);
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
/* Narrative design archive */
|
||||
.narrative-shell {
|
||||
width: min(1320px, calc(100vw - 36px));
|
||||
}
|
||||
|
||||
.source-strip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.source-strip span {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: #f8fafc;
|
||||
color: var(--muted);
|
||||
padding: 5px 9px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.faction-nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.faction-nav a {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.faction-nav a:hover {
|
||||
border-color: #bfdbfe;
|
||||
background: #eff6ff;
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.faction-section {
|
||||
scroll-margin-top: 18px;
|
||||
}
|
||||
|
||||
.faction-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.faction-head p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hero-copy-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.copy-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #fbfdff;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.copy-card h3 {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.copy-card small {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.copy-subtitle {
|
||||
margin: 0 0 8px;
|
||||
color: var(--cyan);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.copy-desc,
|
||||
.copy-diag,
|
||||
.copy-task {
|
||||
margin: 8px 0 0;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.copy-diag {
|
||||
border-left: 3px solid var(--line);
|
||||
color: #475569;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.copy-task {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.copy-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.copy-meta span {
|
||||
border-radius: 999px;
|
||||
background: #eef2ff;
|
||||
color: #3730a3;
|
||||
padding: 3px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.civilization-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, .9fr) minmax(0, 1.1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.city-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.city-list span {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
padding: 5px 8px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.empty-copy {
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.doc-shell,
|
||||
.doc-shell.narrow { width: min(100vw - 24px, 1180px); padding-top: 14px; }
|
||||
.doc-hero,
|
||||
.doc-section { padding: 18px; }
|
||||
h1 { font-size: 26px; }
|
||||
.meta-grid,
|
||||
.flow-grid,
|
||||
.flow-grid.three,
|
||||
.callout-grid,
|
||||
.split,
|
||||
.tree-branches.two,
|
||||
.tree-branches.three { grid-template-columns: 1fr; }
|
||||
.tree-root { width: 100%; font-size: 22px; }
|
||||
.principles-list li {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
.principles-list strong { font-size: 18px; }
|
||||
.class-grid { grid-template-columns: 1fr; }
|
||||
.civilization-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@ -645,6 +645,125 @@ body::after {
|
||||
.filter-select { cursor: pointer; }
|
||||
.filter-select option { background: var(--bg-card); color: var(--text-primary); }
|
||||
|
||||
/* ========== Mechanics Documents ========== */
|
||||
.mechanics-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 310px minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
height: calc(100vh - 100px);
|
||||
min-height: 620px;
|
||||
}
|
||||
|
||||
.mechanics-sidebar,
|
||||
.mechanics-reader {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mechanics-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mechanics-sidebar-head {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.mechanics-title {
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.mechanics-subtitle {
|
||||
margin-top: 3px;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.mechanics-doc-list {
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mechanics-doc-btn {
|
||||
width: 100%;
|
||||
display: block;
|
||||
text-align: left;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
padding: 11px 12px;
|
||||
margin-bottom: 8px;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mechanics-doc-btn:hover {
|
||||
background: rgba(59,130,246,0.05);
|
||||
}
|
||||
|
||||
.mechanics-doc-btn.active {
|
||||
border-color: rgba(59,130,246,0.35);
|
||||
background: rgba(59,130,246,0.09);
|
||||
}
|
||||
|
||||
.mechanics-doc-btn.disabled {
|
||||
opacity: .45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.mechanics-doc-name,
|
||||
.mechanics-doc-summary,
|
||||
.mechanics-doc-meta {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mechanics-doc-name {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.mechanics-doc-summary {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.mechanics-doc-meta {
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.mechanics-reader {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mechanics-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.mechanics-layout {
|
||||
grid-template-columns: 1fr;
|
||||
height: auto;
|
||||
}
|
||||
.mechanics-sidebar {
|
||||
max-height: 320px;
|
||||
}
|
||||
.mechanics-reader {
|
||||
height: 720px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== Story Module ========== */
|
||||
.story-hero-card {
|
||||
background: var(--bg-card); border: 1px solid var(--border-color);
|
||||
|
||||
@ -53,6 +53,11 @@
|
||||
BUG跟踪
|
||||
</button>
|
||||
|
||||
<button class="sidebar-tab" data-tab="suggestions">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a4 4 0 01-4 4H8l-5 3V7a4 4 0 014-4h10a4 4 0 014 4z"/><path d="M8 8h8M8 12h5"/></svg>
|
||||
建议跟踪
|
||||
</button>
|
||||
|
||||
<div class="nav-section-label">分析模块</div>
|
||||
<button class="sidebar-tab" data-tab="gamebalance">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v18M3 12h18M5.63 5.63l12.74 12.74M18.37 5.63L5.63 18.37"/></svg>
|
||||
@ -66,6 +71,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="20" x2="12" y2="10"/><line x1="18" y1="20" x2="18" y2="4"/><line x1="6" y1="20" x2="6" y2="16"/></svg>
|
||||
数值设计
|
||||
</button>
|
||||
<button class="sidebar-tab" data-tab="mechanics">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v16H4z"/><path d="M9 4v16M4 9h16"/><path d="M14 14h3v3h-3z"/></svg>
|
||||
游戏机制
|
||||
</button>
|
||||
<button class="sidebar-tab" data-tab="story">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
|
||||
文案剧情
|
||||
@ -246,6 +255,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="panel-mechanics" class="tab-panel">
|
||||
<div class="mechanics-layout">
|
||||
<aside class="mechanics-sidebar">
|
||||
<div class="mechanics-sidebar-head">
|
||||
<div class="mechanics-title">游戏机制文档</div>
|
||||
<div class="mechanics-subtitle">Design/final/mechanics</div>
|
||||
</div>
|
||||
<div id="mechanics-doc-list" class="mechanics-doc-list">
|
||||
<div class="loading-inline">正在加载机制文档...</div>
|
||||
</div>
|
||||
</aside>
|
||||
<section class="mechanics-reader">
|
||||
<iframe id="mechanics-frame" class="mechanics-frame" title="游戏机制文档"></iframe>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="panel-story" class="tab-panel">
|
||||
<!-- Sub-tabs -->
|
||||
<div class="sub-tabs" id="story-sub-tabs">
|
||||
@ -436,6 +462,35 @@
|
||||
<div id="bugs-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- ===== Suggestion Tracker Panel ===== -->
|
||||
<div id="panel-suggestions" class="tab-panel">
|
||||
<!-- Header bar -->
|
||||
<div class="bug-header-bar">
|
||||
<div class="bug-header-left">
|
||||
<span class="bug-header-title">建议跟踪</span>
|
||||
<span class="bug-header-count" id="suggestions-count">加载中...</span>
|
||||
</div>
|
||||
<button class="bug-btn bug-btn-primary" onclick="suggestionsShowAddModal()">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||||
新增建议
|
||||
</button>
|
||||
</div>
|
||||
<!-- Toolbar -->
|
||||
<div class="toolbar">
|
||||
<input type="text" id="suggestions-search" class="search-input" placeholder="搜索编号、标题或描述...">
|
||||
<select id="suggestions-status-filter" class="filter-select">
|
||||
<option value="">全部状态</option>
|
||||
<option value="open">未处理</option>
|
||||
<option value="processed">已处理</option>
|
||||
</select>
|
||||
<select id="suggestions-module-filter" class="filter-select">
|
||||
<option value="">全部模块</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Suggestion List -->
|
||||
<div id="suggestions-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- ===== SNS Assistant Panel ===== -->
|
||||
<div id="panel-sns" class="tab-panel">
|
||||
<!-- Sub-tabs -->
|
||||
@ -775,6 +830,7 @@
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/balance.js"></script>
|
||||
<script src="js/mechanics.js?v=20260526-1"></script>
|
||||
<script src="js/story.js"></script>
|
||||
<script src="js/art.js"></script>
|
||||
<script src="js/sentiment.js"></script>
|
||||
@ -783,6 +839,7 @@
|
||||
<script src="js/devplan.js"></script>
|
||||
<script src="js/version.js"></script>
|
||||
<script src="js/bugs.js"></script>
|
||||
<script src="js/suggestions.js"></script>
|
||||
<script src="js/gamebalance.js"></script>
|
||||
<script src="js/sns.js"></script>
|
||||
<script src="js/quick_replies.js"></script>
|
||||
|
||||
@ -683,6 +683,9 @@ function initTabs() {
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
const target = document.getElementById('panel-' + tab.dataset.tab);
|
||||
if (target) target.classList.add('active');
|
||||
if (tab.dataset.tab === 'mechanics' && typeof mechanicsLoad === 'function') {
|
||||
mechanicsLoad();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
93
Tools/Dashboard/js/mechanics.js
Normal file
93
Tools/Dashboard/js/mechanics.js
Normal file
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* TH1 Dashboard - Mechanics Design Documents
|
||||
*/
|
||||
|
||||
let mechanicsLoaded = false;
|
||||
let mechanicsDocs = [];
|
||||
|
||||
function mechanicsEscHtml(str) {
|
||||
return String(str ?? '').replace(/[&<>"']/g, ch => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}[ch]));
|
||||
}
|
||||
|
||||
async function mechanicsLoad() {
|
||||
if (mechanicsLoaded) return;
|
||||
mechanicsLoaded = true;
|
||||
|
||||
const list = document.getElementById('mechanics-doc-list');
|
||||
const frame = document.getElementById('mechanics-frame');
|
||||
if (!list || !frame) return;
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/design/mechanics/list?t=' + Date.now());
|
||||
if (!resp.ok) throw new Error(resp.status);
|
||||
const data = await resp.json();
|
||||
mechanicsDocs = data.docs || [];
|
||||
mechanicsRenderList();
|
||||
const first = mechanicsDocs.find(d => d.exists) || mechanicsDocs[0];
|
||||
if (first) mechanicsOpen(first.file);
|
||||
} catch (e) {
|
||||
list.innerHTML = '<div class="loading-inline">机制文档加载失败</div>';
|
||||
console.warn('mechanicsLoad failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function mechanicsLoadIfVisible() {
|
||||
const panel = document.getElementById('panel-mechanics');
|
||||
if (panel && panel.classList.contains('active')) {
|
||||
mechanicsLoad();
|
||||
}
|
||||
}
|
||||
|
||||
function mechanicsRenderList() {
|
||||
const list = document.getElementById('mechanics-doc-list');
|
||||
if (!list) return;
|
||||
if (!mechanicsDocs.length) {
|
||||
list.innerHTML = '<div class="loading-inline">暂无机制文档</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = mechanicsDocs.map((doc, index) => {
|
||||
const disabled = doc.exists ? '' : ' disabled';
|
||||
const active = index === 0 ? ' active' : '';
|
||||
const updated = doc.updatedAt ? new Date(doc.updatedAt * 1000).toLocaleDateString() : '';
|
||||
return `<button class="mechanics-doc-btn${active}${disabled}" data-file="${mechanicsEscHtml(doc.file)}" ${doc.exists ? '' : 'disabled'}>
|
||||
<span class="mechanics-doc-name">${mechanicsEscHtml(doc.title)}</span>
|
||||
<span class="mechanics-doc-summary">${mechanicsEscHtml(doc.summary || '')}</span>
|
||||
<span class="mechanics-doc-meta">${mechanicsEscHtml(doc.status || '')}${updated ? ' / ' + mechanicsEscHtml(updated) : ''}</span>
|
||||
</button>`;
|
||||
}).join('');
|
||||
|
||||
list.querySelectorAll('.mechanics-doc-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
list.querySelectorAll('.mechanics-doc-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
mechanicsOpen(btn.dataset.file);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mechanicsOpen(file) {
|
||||
const frame = document.getElementById('mechanics-frame');
|
||||
if (!frame || !file) return;
|
||||
frame.src = '/api/design/mechanics/' + encodeURIComponent(file);
|
||||
}
|
||||
|
||||
window.mechanicsLoad = mechanicsLoad;
|
||||
window.mechanicsOpen = mechanicsOpen;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.sidebar-tab[data-tab="mechanics"]').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
window.setTimeout(mechanicsLoad, 0);
|
||||
});
|
||||
});
|
||||
mechanicsLoadIfVisible();
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', mechanicsLoadIfVisible);
|
||||
327
Tools/Dashboard/js/suggestions.js
Normal file
327
Tools/Dashboard/js/suggestions.js
Normal file
@ -0,0 +1,327 @@
|
||||
/**
|
||||
* TH1 Dashboard - Suggestion Tracker Module
|
||||
*
|
||||
* Data source: DOC/suggestions.json (project-level, version controlled)
|
||||
* Server API:
|
||||
* GET /api/suggestions/list
|
||||
* POST /api/suggestions/create
|
||||
* POST /api/suggestions/update
|
||||
* POST /api/suggestions/delete
|
||||
*/
|
||||
|
||||
let suggestionsList = [];
|
||||
let suggestionsLoaded = false;
|
||||
|
||||
const SUGGESTION_STATUSES = {
|
||||
open: { label: '未处理', color: '#d97706', bg: '#fffbeb' },
|
||||
processed: { label: '已处理', color: '#16a34a', bg: '#f0fdf4' },
|
||||
};
|
||||
|
||||
const SUGGESTION_MODULES = [
|
||||
'战斗系统', '技能系统', 'AI系统', 'UI界面', '地图系统',
|
||||
'科技树', '存档系统', '多语言', '美术资源', '配置数据',
|
||||
'网络/联机', '音频', '性能', '其他'
|
||||
];
|
||||
|
||||
async function suggestionsLoadAll() {
|
||||
if (suggestionsLoaded) return;
|
||||
try {
|
||||
const r = await fetch('/api/suggestions/list?t=' + Date.now());
|
||||
if (!r.ok) throw new Error(r.status);
|
||||
const data = await r.json();
|
||||
suggestionsList = data.suggestions || [];
|
||||
} catch (e) {
|
||||
console.warn('suggestionsLoad fail:', e);
|
||||
suggestionsList = [];
|
||||
}
|
||||
suggestionsLoaded = true;
|
||||
suggestionsRenderList();
|
||||
}
|
||||
|
||||
function suggestionsRenderList() {
|
||||
const container = document.getElementById('suggestions-list');
|
||||
const countEl = document.getElementById('suggestions-count');
|
||||
if (!container) return;
|
||||
|
||||
const searchQ = (document.getElementById('suggestions-search')?.value || '').trim().toLowerCase();
|
||||
const statusF = document.getElementById('suggestions-status-filter')?.value || '';
|
||||
const moduleF = document.getElementById('suggestions-module-filter')?.value || '';
|
||||
|
||||
let filtered = [...suggestionsList];
|
||||
|
||||
if (searchQ) {
|
||||
filtered = filtered.filter(s =>
|
||||
String(s.id).includes(searchQ) ||
|
||||
(s.title || '').toLowerCase().includes(searchQ) ||
|
||||
(s.description || '').toLowerCase().includes(searchQ)
|
||||
);
|
||||
}
|
||||
if (statusF) filtered = filtered.filter(s => s.status === statusF);
|
||||
if (moduleF) filtered = filtered.filter(s => s.module === moduleF);
|
||||
|
||||
filtered.sort((a, b) => b.id - a.id);
|
||||
|
||||
if (countEl) {
|
||||
const total = suggestionsList.length;
|
||||
const openCount = suggestionsList.filter(s => s.status !== 'processed').length;
|
||||
countEl.textContent = `共 ${total} 条建议,${openCount} 条未处理`;
|
||||
}
|
||||
|
||||
if (filtered.length === 0) {
|
||||
container.innerHTML = '<div class="loading-inline">暂无匹配的建议记录</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = filtered.map(s => {
|
||||
const st = SUGGESTION_STATUSES[s.status] || SUGGESTION_STATUSES.open;
|
||||
const dateStr = s.createdAt ? new Date(s.createdAt).toLocaleDateString('zh-CN') : '';
|
||||
const showDoneBtn = s.status !== 'processed';
|
||||
const doneBtn = showDoneBtn ? `<button class="bug-btn bug-btn-sm bug-btn-primary" onclick="suggestionsQuickProcess(${s.id}, event)" title="标记为已处理" style="margin-right:4px">处理</button>` : '';
|
||||
|
||||
return `<div class="bug-card" onclick="suggestionsShowDetail(${s.id})">
|
||||
<div class="bug-card-left">
|
||||
<span class="bug-card-id">#${String(s.id).padStart(3, '0')}</span>
|
||||
<span class="bug-card-title">${suggestionsEsc(s.title)}</span>
|
||||
${s.module ? `<span class="bug-card-module">${suggestionsEsc(s.module)}</span>` : ''}
|
||||
</div>
|
||||
<div class="bug-card-right">
|
||||
${doneBtn}
|
||||
<span class="bug-card-status" style="color:${st.color};background:${st.bg}">${st.label}</span>
|
||||
<span class="bug-card-date">${dateStr}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function suggestionsEsc(s) {
|
||||
if (!s) return '';
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function suggestionsShowDetail(id) {
|
||||
const suggestion = suggestionsList.find(s => s.id === id);
|
||||
if (!suggestion) return;
|
||||
|
||||
const st = SUGGESTION_STATUSES[suggestion.status] || SUGGESTION_STATUSES.open;
|
||||
const dateStr = suggestion.createdAt ? new Date(suggestion.createdAt).toLocaleString('zh-CN') : '';
|
||||
const updStr = suggestion.updatedAt ? new Date(suggestion.updatedAt).toLocaleString('zh-CN') : '';
|
||||
const statusOpts = Object.entries(SUGGESTION_STATUSES).map(([k, v]) =>
|
||||
`<option value="${k}" ${suggestion.status === k ? 'selected' : ''}>${v.label}</option>`
|
||||
).join('');
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'bug-detail-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="bug-detail-modal">
|
||||
<div class="bug-detail-header">
|
||||
<div>
|
||||
<div class="bug-detail-id">#${String(suggestion.id).padStart(3, '0')}</div>
|
||||
<div class="bug-detail-title">${suggestionsEsc(suggestion.title)}</div>
|
||||
<div class="bug-detail-meta">
|
||||
<span class="bug-card-status" style="color:${st.color};background:${st.bg}">${st.label}</span>
|
||||
${suggestion.module ? `<span class="bug-card-module">${suggestionsEsc(suggestion.module)}</span>` : ''}
|
||||
<span>创建: ${dateStr}</span>
|
||||
${updStr ? `<span>更新: ${updStr}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<button class="bug-detail-close" onclick="this.closest('.bug-detail-overlay').remove()">×</button>
|
||||
</div>
|
||||
<div class="bug-detail-body">
|
||||
${suggestion.description ? `<div class="bug-detail-desc">${suggestionsEsc(suggestion.description).replace(/\n/g, '<br>')}</div>` : '<div class="bug-detail-desc" style="color:#94a3b8">无详细描述</div>'}
|
||||
</div>
|
||||
<div class="bug-detail-footer">
|
||||
<div class="bug-detail-status-change">
|
||||
<label>状态</label>
|
||||
<select id="suggestion-status-select" class="filter-select" onchange="suggestionsUpdateStatus(${suggestion.id}, this.value)">${statusOpts}</select>
|
||||
</div>
|
||||
<div>
|
||||
<button class="bug-btn bug-btn-danger" onclick="suggestionsDelete(${suggestion.id})">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const handler = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
overlay.remove();
|
||||
document.removeEventListener('keydown', handler);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handler);
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||||
}
|
||||
|
||||
function suggestionsShowAddModal() {
|
||||
const moduleOpts = SUGGESTION_MODULES.map(m => `<option value="${m}">${m}</option>`).join('');
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'bug-detail-overlay';
|
||||
overlay.id = 'suggestion-add-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="bug-add-modal">
|
||||
<div class="bug-detail-header">
|
||||
<div class="bug-detail-title">新增建议</div>
|
||||
<button class="bug-detail-close" onclick="this.closest('.bug-detail-overlay').remove()">×</button>
|
||||
</div>
|
||||
<div class="bug-add-body">
|
||||
<div class="bug-form-group">
|
||||
<label class="bug-form-label">标题 *</label>
|
||||
<input type="text" id="suggestion-add-title" class="bug-form-input" placeholder="简要描述建议内容">
|
||||
</div>
|
||||
<div class="bug-form-group">
|
||||
<label class="bug-form-label">模块</label>
|
||||
<select id="suggestion-add-module" class="bug-form-select">
|
||||
<option value="">未分类</option>
|
||||
${moduleOpts}
|
||||
</select>
|
||||
</div>
|
||||
<div class="bug-form-group">
|
||||
<label class="bug-form-label">详细描述</label>
|
||||
<textarea id="suggestion-add-desc" class="bug-form-textarea" rows="6" placeholder="记录建议背景、期望调整、玩家反馈来源等..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bug-detail-footer">
|
||||
<button class="bug-btn" onclick="document.getElementById('suggestion-add-overlay').remove()">取消</button>
|
||||
<button class="bug-btn bug-btn-primary" onclick="suggestionsSubmitAdd()">提交建议</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
setTimeout(() => document.getElementById('suggestion-add-title')?.focus(), 100);
|
||||
|
||||
const handler = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
overlay.remove();
|
||||
document.removeEventListener('keydown', handler);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handler);
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||||
}
|
||||
|
||||
async function suggestionsSubmitAdd() {
|
||||
const title = document.getElementById('suggestion-add-title').value.trim();
|
||||
if (!title) { alert('请输入建议标题'); return; }
|
||||
|
||||
const module = document.getElementById('suggestion-add-module').value;
|
||||
const description = document.getElementById('suggestion-add-desc').value.trim();
|
||||
|
||||
const btn = document.querySelector('#suggestion-add-overlay .bug-btn-primary');
|
||||
if (btn) { btn.disabled = true; btn.textContent = '提交中...'; }
|
||||
|
||||
try {
|
||||
const r = await fetch('/api/suggestions/create', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title, module, description })
|
||||
});
|
||||
const result = await r.json();
|
||||
if (result.success) {
|
||||
document.getElementById('suggestion-add-overlay')?.remove();
|
||||
suggestionsLoaded = false;
|
||||
await suggestionsLoadAll();
|
||||
suggestionsShowToast(`建议 #${String(result.id).padStart(3, '0')} 已创建`, 'success');
|
||||
} else {
|
||||
alert('创建失败: ' + (result.error || '未知错误'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('创建失败: ' + e.message);
|
||||
} finally {
|
||||
if (btn) { btn.disabled = false; btn.textContent = '提交建议'; }
|
||||
}
|
||||
}
|
||||
|
||||
async function suggestionsQuickProcess(id, event) {
|
||||
if (event) event.stopPropagation();
|
||||
await suggestionsUpdateStatus(id, 'processed');
|
||||
}
|
||||
|
||||
async function suggestionsUpdateStatus(id, newStatus) {
|
||||
try {
|
||||
const r = await fetch('/api/suggestions/update', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, status: newStatus })
|
||||
});
|
||||
const result = await r.json();
|
||||
if (result.success) {
|
||||
const suggestion = suggestionsList.find(s => s.id === id);
|
||||
if (suggestion) {
|
||||
suggestion.status = newStatus;
|
||||
suggestion.updatedAt = Date.now();
|
||||
}
|
||||
suggestionsRenderList();
|
||||
suggestionsShowToast('状态已更新', 'success');
|
||||
} else {
|
||||
alert('更新失败: ' + (result.error || '未知错误'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('更新失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function suggestionsDelete(id) {
|
||||
if (!confirm(`确定要删除建议 #${String(id).padStart(3, '0')} 吗?`)) return;
|
||||
|
||||
try {
|
||||
const r = await fetch('/api/suggestions/delete', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id })
|
||||
});
|
||||
const result = await r.json();
|
||||
if (result.success) {
|
||||
document.querySelector('.bug-detail-overlay')?.remove();
|
||||
suggestionsLoaded = false;
|
||||
await suggestionsLoadAll();
|
||||
suggestionsShowToast('建议已删除', 'success');
|
||||
} else {
|
||||
alert('删除失败: ' + (result.error || '未知错误'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('删除失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function suggestionsShowToast(msg, type) {
|
||||
const toast = document.getElementById('toast');
|
||||
if (!toast) return;
|
||||
toast.textContent = msg;
|
||||
toast.className = 'toast ' + (type || 'success');
|
||||
toast.style.display = 'block';
|
||||
setTimeout(() => { toast.style.display = 'none'; }, 3000);
|
||||
}
|
||||
|
||||
function suggestionsBindFilters() {
|
||||
const ids = ['suggestions-search', 'suggestions-status-filter', 'suggestions-module-filter'];
|
||||
ids.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.addEventListener(el.tagName === 'SELECT' ? 'change' : 'input', () => suggestionsRenderList());
|
||||
});
|
||||
|
||||
const moduleFilter = document.getElementById('suggestions-module-filter');
|
||||
if (moduleFilter && moduleFilter.options.length <= 1) {
|
||||
SUGGESTION_MODULES.forEach(m => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = m;
|
||||
opt.textContent = m;
|
||||
moduleFilter.appendChild(opt);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(function () {
|
||||
const observer = new MutationObserver(() => {
|
||||
const panel = document.getElementById('panel-suggestions');
|
||||
if (panel && panel.classList.contains('active') && !suggestionsLoaded) {
|
||||
suggestionsLoadAll();
|
||||
suggestionsBindFilters();
|
||||
}
|
||||
});
|
||||
const panel = document.getElementById('panel-suggestions');
|
||||
if (panel) {
|
||||
observer.observe(panel, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
})();
|
||||
@ -5,6 +5,7 @@ TH1 Dashboard Server with data refresh API.
|
||||
Endpoints:
|
||||
GET /* - Static file serving
|
||||
GET /api/bugs/list - Load all bugs from DOC/bugs.json
|
||||
GET /api/suggestions/list - Load all suggestions from DOC/suggestions.json
|
||||
GET /api/marketing/events - Load marketing events from DOC/marketing/events.json
|
||||
GET /api/marketing/notes - Load marketing notes from DOC/marketing/notes.json
|
||||
GET /api/marketing/todos - Load marketing todos from DOC/marketing/todos.json
|
||||
@ -19,6 +20,9 @@ Endpoints:
|
||||
POST /api/bugs/create - Create new bug
|
||||
POST /api/bugs/update - Update bug fields
|
||||
POST /api/bugs/delete - Delete bug by id
|
||||
POST /api/suggestions/create - Create new suggestion
|
||||
POST /api/suggestions/update - Update suggestion fields
|
||||
POST /api/suggestions/delete - Delete suggestion by id
|
||||
POST /api/devplan/update - Update task fields (description, etc.)
|
||||
POST /api/devplan/subtask/create - Create subtask under a task
|
||||
POST /api/devplan/subtask/toggle - Toggle subtask done state
|
||||
@ -40,6 +44,7 @@ import uuid
|
||||
import webbrowser
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
from html.parser import HTMLParser
|
||||
|
||||
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
|
||||
@ -54,10 +59,72 @@ DEVPLAN_FILE = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'DOC', 'dev
|
||||
|
||||
# Project-level data lives in DOC/ (version controlled)
|
||||
BUGS_FILE = os.path.join(SCRIPT_DIR, '..', '..', 'DOC', 'bugs.json')
|
||||
SUGGESTIONS_FILE = os.path.join(SCRIPT_DIR, '..', '..', 'DOC', 'suggestions.json')
|
||||
MKT_TODOS_FILE = os.path.join(SCRIPT_DIR, '..', '..', 'DOC', 'marketing', 'todos.json')
|
||||
OSS_DATA_DIR = os.path.join(SCRIPT_DIR, 'data', 'oss')
|
||||
SNS_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'DOC', 'sns'))
|
||||
QUICK_REPLIES_FILE = os.path.join(SNS_DIR, 'quick_replies.json')
|
||||
DESIGN_MECHANICS_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'Design', 'final', 'mechanics'))
|
||||
DESIGN_NARRATIVE_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'Design', 'final', 'narrative'))
|
||||
DESIGN_SHARED_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..', 'Design', 'shared'))
|
||||
DESIGN_DOCS = [
|
||||
{
|
||||
'id': 'overview',
|
||||
'title': '设计树',
|
||||
'file': 'index.html',
|
||||
'status': '首版',
|
||||
'summary': '设计原则、游戏机制、文案剧情的树形入口。',
|
||||
},
|
||||
{
|
||||
'id': 'design-principles',
|
||||
'title': '设计原则',
|
||||
'file': 'design-principles.html',
|
||||
'status': '草案',
|
||||
'summary': '设计树根节点,后续承载基础原则正文。',
|
||||
},
|
||||
{
|
||||
'id': 'hero-foundation',
|
||||
'title': '基础 / 英雄',
|
||||
'file': 'hero-foundation.html',
|
||||
'status': '草案',
|
||||
'summary': '英雄基础定位与五职阶设计。',
|
||||
},
|
||||
{
|
||||
'id': 'core-loop',
|
||||
'title': '核心循环',
|
||||
'file': 'core-loop.html',
|
||||
'status': '首版',
|
||||
'summary': '回合、行动、表现和结算的设计骨架。',
|
||||
},
|
||||
{
|
||||
'id': 'hero-system',
|
||||
'title': '东方英雄系统',
|
||||
'file': 'hero-system.html',
|
||||
'status': '首版',
|
||||
'summary': '英雄作为战术核心、阵营识别和成长目标的设计说明。',
|
||||
},
|
||||
{
|
||||
'id': 'faction-system',
|
||||
'title': '阵营与文明系统',
|
||||
'file': 'faction-system.html',
|
||||
'status': '首版',
|
||||
'summary': '文明基底与东方势力层的边界。',
|
||||
},
|
||||
{
|
||||
'id': 'unit-skill-system',
|
||||
'title': '单位与技能系统',
|
||||
'file': 'unit-skill-system.html',
|
||||
'status': '首版',
|
||||
'summary': '基础单位、特色单位、英雄单位和技能分类。',
|
||||
},
|
||||
{
|
||||
'id': 'map-city-tech',
|
||||
'title': '地图、城市与科技',
|
||||
'file': 'map-city-tech.html',
|
||||
'status': '首版',
|
||||
'summary': '经营层中地块、资源、城市、科技和文化卡的关系。',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _load_bugs():
|
||||
@ -77,6 +144,23 @@ def _save_bugs(data):
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def _load_suggestions():
|
||||
"""Load suggestions.json from DOC/, return dict with nextId and suggestions list."""
|
||||
path = os.path.normpath(SUGGESTIONS_FILE)
|
||||
if os.path.exists(path):
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
return {'nextId': 1, 'suggestions': []}
|
||||
|
||||
|
||||
def _save_suggestions(data):
|
||||
"""Save suggestions.json to DOC/."""
|
||||
path = os.path.normpath(SUGGESTIONS_FILE)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def _load_devplan():
|
||||
"""Load devplan.json from DOC/."""
|
||||
if os.path.exists(DEVPLAN_FILE):
|
||||
@ -206,10 +290,35 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
|
||||
'.css': 'text/css',
|
||||
}
|
||||
|
||||
def end_headers(self):
|
||||
if self.path.endswith(('.html', '.js', '.css')) or self.path == '/' or self.path.startswith('/api/design/'):
|
||||
self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
self.send_header('Pragma', 'no-cache')
|
||||
self.send_header('Expires', '0')
|
||||
super().end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
if self.path.startswith('/api/design/mechanics/list'):
|
||||
self._send_json(self._get_design_mechanics_docs())
|
||||
return
|
||||
if self.path.startswith('/api/design/mechanics/doc'):
|
||||
self._handle_design_mechanics_doc()
|
||||
return
|
||||
if self.path.startswith('/api/design/mechanics/'):
|
||||
self._handle_design_mechanics_static()
|
||||
return
|
||||
if self.path.startswith('/api/design/narrative/'):
|
||||
self._handle_design_narrative_static()
|
||||
return
|
||||
if self.path.startswith('/api/design/shared/'):
|
||||
self._handle_design_shared_asset()
|
||||
return
|
||||
if self.path.startswith('/api/bugs/list'):
|
||||
self._send_json(_load_bugs())
|
||||
return
|
||||
if self.path.startswith('/api/suggestions/list'):
|
||||
self._send_json(_load_suggestions())
|
||||
return
|
||||
if self.path.startswith('/api/marketing/todos'):
|
||||
self._send_json(_load_mkt_todos())
|
||||
return
|
||||
@ -252,6 +361,91 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
|
||||
return
|
||||
super().do_GET()
|
||||
|
||||
def _get_design_mechanics_docs(self):
|
||||
docs = []
|
||||
for doc in DESIGN_DOCS:
|
||||
path = os.path.join(DESIGN_MECHANICS_DIR, doc['file'])
|
||||
item = dict(doc)
|
||||
item['exists'] = os.path.exists(path)
|
||||
item['updatedAt'] = os.path.getmtime(path) if item['exists'] else None
|
||||
docs.append(item)
|
||||
return {'base': 'Design/final/mechanics', 'docs': docs}
|
||||
|
||||
def _handle_design_mechanics_doc(self):
|
||||
parsed = urlparse(self.path)
|
||||
query = parse_qs(parsed.query)
|
||||
file_name = query.get('file', ['index.html'])[0]
|
||||
if os.path.basename(file_name) != file_name or not file_name.endswith('.html'):
|
||||
self.send_error(400, 'Invalid design document path')
|
||||
return
|
||||
path = os.path.normpath(os.path.join(DESIGN_MECHANICS_DIR, file_name))
|
||||
if not path.startswith(DESIGN_MECHANICS_DIR) or not os.path.exists(path):
|
||||
self.send_error(404, 'Design document not found')
|
||||
return
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def _handle_design_mechanics_static(self):
|
||||
parsed = urlparse(self.path)
|
||||
file_name = parsed.path[len('/api/design/mechanics/'):]
|
||||
if os.path.basename(file_name) != file_name or not file_name.endswith(('.html', '.md')):
|
||||
self.send_error(400, 'Invalid design document path')
|
||||
return
|
||||
path = os.path.normpath(os.path.join(DESIGN_MECHANICS_DIR, file_name))
|
||||
if not path.startswith(DESIGN_MECHANICS_DIR) or not os.path.exists(path):
|
||||
self.send_error(404, 'Design document not found')
|
||||
return
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
content_type = 'text/html; charset=utf-8' if path.endswith('.html') else 'text/plain; charset=utf-8'
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', content_type)
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def _handle_design_narrative_static(self):
|
||||
parsed = urlparse(self.path)
|
||||
file_name = parsed.path[len('/api/design/narrative/'):]
|
||||
if os.path.basename(file_name) != file_name or not file_name.endswith('.html'):
|
||||
self.send_error(400, 'Invalid narrative document path')
|
||||
return
|
||||
path = os.path.normpath(os.path.join(DESIGN_NARRATIVE_DIR, file_name))
|
||||
if not path.startswith(DESIGN_NARRATIVE_DIR) or not os.path.exists(path):
|
||||
self.send_error(404, 'Narrative document not found')
|
||||
return
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def _handle_design_shared_asset(self):
|
||||
parsed = urlparse(self.path)
|
||||
rel_path = parsed.path[len('/api/design/shared/'):]
|
||||
if not rel_path or '..' in rel_path.replace('\\', '/').split('/'):
|
||||
self.send_error(400, 'Invalid shared design asset path')
|
||||
return
|
||||
path = os.path.normpath(os.path.join(DESIGN_SHARED_DIR, rel_path))
|
||||
if not path.startswith(DESIGN_SHARED_DIR) or not os.path.exists(path):
|
||||
self.send_error(404, 'Shared design asset not found')
|
||||
return
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
content_type = 'text/css; charset=utf-8' if path.endswith('.css') else 'application/octet-stream'
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', content_type)
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def do_POST(self):
|
||||
if self.path == '/api/refresh':
|
||||
self.handle_refresh()
|
||||
@ -269,6 +463,12 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.handle_bugs_update()
|
||||
elif self.path == '/api/bugs/delete':
|
||||
self.handle_bugs_delete()
|
||||
elif self.path == '/api/suggestions/create':
|
||||
self.handle_suggestions_create()
|
||||
elif self.path == '/api/suggestions/update':
|
||||
self.handle_suggestions_update()
|
||||
elif self.path == '/api/suggestions/delete':
|
||||
self.handle_suggestions_delete()
|
||||
elif self.path == '/api/marketing/todos/create':
|
||||
self.handle_mkt_todo_create()
|
||||
elif self.path == '/api/marketing/todos/toggle':
|
||||
@ -803,6 +1003,94 @@ class DashboardHandler(http.server.SimpleHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
self._send_json({'success': False, 'error': str(e)}, 500)
|
||||
|
||||
def handle_suggestions_create(self):
|
||||
"""Create a new suggestion record."""
|
||||
try:
|
||||
length = int(self.headers.get('Content-Length', 0))
|
||||
raw = self.rfile.read(length)
|
||||
data = json.loads(raw.decode('utf-8'))
|
||||
|
||||
title = data.get('title', '').strip()
|
||||
if not title:
|
||||
self._send_json({'success': False, 'error': 'Title is required'}, 400)
|
||||
return
|
||||
|
||||
suggestions_data = _load_suggestions()
|
||||
suggestion_id = suggestions_data['nextId']
|
||||
now = int(time.time() * 1000)
|
||||
|
||||
suggestion = {
|
||||
'id': suggestion_id,
|
||||
'title': title,
|
||||
'description': data.get('description', '').strip(),
|
||||
'status': 'open',
|
||||
'module': data.get('module', ''),
|
||||
'createdAt': now,
|
||||
'updatedAt': now,
|
||||
}
|
||||
|
||||
suggestions_data['suggestions'].append(suggestion)
|
||||
suggestions_data['nextId'] = suggestion_id + 1
|
||||
_save_suggestions(suggestions_data)
|
||||
|
||||
self._send_json({'success': True, 'id': suggestion_id})
|
||||
except Exception as e:
|
||||
self._send_json({'success': False, 'error': str(e)}, 500)
|
||||
|
||||
def handle_suggestions_update(self):
|
||||
"""Update a suggestion's fields."""
|
||||
try:
|
||||
length = int(self.headers.get('Content-Length', 0))
|
||||
raw = self.rfile.read(length)
|
||||
data = json.loads(raw.decode('utf-8'))
|
||||
|
||||
suggestion_id = data.get('id')
|
||||
if suggestion_id is None:
|
||||
self._send_json({'success': False, 'error': 'ID is required'}, 400)
|
||||
return
|
||||
|
||||
suggestions_data = _load_suggestions()
|
||||
suggestion = next((s for s in suggestions_data['suggestions'] if s['id'] == suggestion_id), None)
|
||||
if not suggestion:
|
||||
self._send_json({'success': False, 'error': 'Suggestion not found'}, 404)
|
||||
return
|
||||
|
||||
for field in ('status', 'title', 'description', 'module'):
|
||||
if field in data:
|
||||
suggestion[field] = data[field]
|
||||
|
||||
suggestion['updatedAt'] = int(time.time() * 1000)
|
||||
_save_suggestions(suggestions_data)
|
||||
|
||||
self._send_json({'success': True})
|
||||
except Exception as e:
|
||||
self._send_json({'success': False, 'error': str(e)}, 500)
|
||||
|
||||
def handle_suggestions_delete(self):
|
||||
"""Delete a suggestion by id."""
|
||||
try:
|
||||
length = int(self.headers.get('Content-Length', 0))
|
||||
raw = self.rfile.read(length)
|
||||
data = json.loads(raw.decode('utf-8'))
|
||||
|
||||
suggestion_id = data.get('id')
|
||||
if suggestion_id is None:
|
||||
self._send_json({'success': False, 'error': 'ID is required'}, 400)
|
||||
return
|
||||
|
||||
suggestions_data = _load_suggestions()
|
||||
original_len = len(suggestions_data['suggestions'])
|
||||
suggestions_data['suggestions'] = [s for s in suggestions_data['suggestions'] if s['id'] != suggestion_id]
|
||||
|
||||
if len(suggestions_data['suggestions']) == original_len:
|
||||
self._send_json({'success': False, 'error': 'Suggestion not found'}, 404)
|
||||
return
|
||||
|
||||
_save_suggestions(suggestions_data)
|
||||
self._send_json({'success': True})
|
||||
except Exception as e:
|
||||
self._send_json({'success': False, 'error': str(e)}, 500)
|
||||
|
||||
def handle_mkt_todo_create(self):
|
||||
"""Create a new marketing TODO item."""
|
||||
try:
|
||||
@ -1669,7 +1957,7 @@ os.chdir(SCRIPT_DIR)
|
||||
print(f'Checking port {PORT} for stale processes...')
|
||||
kill_stale_servers(PORT)
|
||||
|
||||
with http.server.HTTPServer(('', PORT), DashboardHandler) as httpd:
|
||||
with http.server.ThreadingHTTPServer(('', PORT), DashboardHandler) as httpd:
|
||||
url = f'http://localhost:{PORT}'
|
||||
print(f'TH1 Dashboard serving at {url}')
|
||||
print('Press Ctrl+C to stop.')
|
||||
|
||||
@ -1,5 +1,32 @@
|
||||
@echo off
|
||||
title TH1 Dashboard
|
||||
cd /d "%~dp0"
|
||||
python serve.py
|
||||
|
||||
set "PYTHON_EXE="
|
||||
|
||||
for %%P in (
|
||||
"%LocalAppData%\Programs\Python\Python313\python.exe"
|
||||
"%LocalAppData%\Programs\Python\Python312\python.exe"
|
||||
"%LocalAppData%\Programs\Python\Python311\python.exe"
|
||||
) do (
|
||||
if not defined PYTHON_EXE if exist "%%~P" set "PYTHON_EXE=%%~P"
|
||||
)
|
||||
|
||||
if not defined PYTHON_EXE (
|
||||
for /f "delims=" %%P in ('where python 2^>nul') do (
|
||||
echo %%P | findstr /I "\\WindowsApps\\python.exe" >nul
|
||||
if errorlevel 1 if not defined PYTHON_EXE set "PYTHON_EXE=%%P"
|
||||
)
|
||||
)
|
||||
|
||||
if not defined PYTHON_EXE (
|
||||
echo Could not find a real Python installation.
|
||||
echo The WindowsApps python.exe launcher is not enough to run this dashboard.
|
||||
echo Install Python or add Python to PATH, then run this file again.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Using Python: "%PYTHON_EXE%"
|
||||
"%PYTHON_EXE%" serve.py
|
||||
pause
|
||||
|
||||
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"
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
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/`.
|
||||
Binary file not shown.
Binary file not shown.
@ -32,6 +32,11 @@ MonoBehaviour:
|
||||
OutsideMultiplayIncludeAIPlayerHint: "(\u5305\u542BAI\u73A9\u5BB6)"
|
||||
OutsideMultiplayNoRoomHint: "\u6682\u65E0\u8054\u673A\u623F\u95F4"
|
||||
OutsideMultiplayRoomNameSuffix: "\u7684\u623F\u95F4"
|
||||
OutsideMultiplayTeamTitle: "\u961F\u4F0D"
|
||||
OutsideMultiplayAIPlayerTitle: "AI\u73A9\u5BB6"
|
||||
OutsideMultiplayOpenTitle: "\u5F85\u52A0\u5165"
|
||||
OutsideMultiplayReadyTitle: "\u5DF2\u51C6\u5907"
|
||||
OutsideMultiplayOpenHint: "\u4ECD\u6709\u5F85\u52A0\u5165\u7684\u5E2D\u4F4D\uFF0C\u8BF7\u51CF\u5C11\u5E2D\u4F4D"
|
||||
OutsideHistoryDropListNoLimitP: "\u4E0D\u9650"
|
||||
OutsideHistoryDropList2P: "2\u4EBA"
|
||||
OutsideHistoryDropList3P: "3\u4EBA"
|
||||
|
||||
File diff suppressed because one or more lines are too long
136
Unity/Assets/Resources/Export/NetworkPlayerTipDataAssets.asset
Normal file
136
Unity/Assets/Resources/Export/NetworkPlayerTipDataAssets.asset
Normal file
@ -0,0 +1,136 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 3ebbbbba88fe43409c16e83b934b878c, type: 3}
|
||||
m_Name: NetworkPlayerTipDataAssets
|
||||
m_EditorClassIdentifier:
|
||||
DefaultCooldownSeconds: 3
|
||||
NetworkPlayerTipInfoList:
|
||||
- TipType: 1
|
||||
Title: 20000
|
||||
Message: 20001
|
||||
CooldownSeconds: 5
|
||||
- TipType: 2
|
||||
Title: 20002
|
||||
Message: 20003
|
||||
CooldownSeconds: 5
|
||||
- TipType: 3
|
||||
Title: 20004
|
||||
Message: 20005
|
||||
CooldownSeconds: 10
|
||||
- TipType: 4
|
||||
Title: 20006
|
||||
Message: 20007
|
||||
CooldownSeconds: 5
|
||||
- TipType: 5
|
||||
Title: 20008
|
||||
Message: 20009
|
||||
CooldownSeconds: 3
|
||||
- TipType: 6
|
||||
Title: 20010
|
||||
Message: 20011
|
||||
CooldownSeconds: 3
|
||||
- TipType: 7
|
||||
Title: 20012
|
||||
Message: 20013
|
||||
CooldownSeconds: 2
|
||||
- TipType: 8
|
||||
Title: 20014
|
||||
Message: 20015
|
||||
CooldownSeconds: 2
|
||||
- TipType: 9
|
||||
Title: 20016
|
||||
Message: 20017
|
||||
CooldownSeconds: 3
|
||||
- TipType: 10
|
||||
Title: 20018
|
||||
Message: 20019
|
||||
CooldownSeconds: 3
|
||||
- TipType: 11
|
||||
Title: 20020
|
||||
Message: 20021
|
||||
CooldownSeconds: 5
|
||||
- TipType: 12
|
||||
Title: 20022
|
||||
Message: 20023
|
||||
CooldownSeconds: 3
|
||||
- TipType: 13
|
||||
Title: 20024
|
||||
Message: 20025
|
||||
CooldownSeconds: 5
|
||||
- TipType: 14
|
||||
Title: 20026
|
||||
Message: 20027
|
||||
CooldownSeconds: 3
|
||||
- TipType: 15
|
||||
Title: 20028
|
||||
Message: 20029
|
||||
CooldownSeconds: 3
|
||||
- TipType: 16
|
||||
Title: 20030
|
||||
Message: 20031
|
||||
CooldownSeconds: 5
|
||||
- TipType: 17
|
||||
Title: 20032
|
||||
Message: 20033
|
||||
CooldownSeconds: 3
|
||||
- TipType: 18
|
||||
Title: 20034
|
||||
Message: 20035
|
||||
CooldownSeconds: 5
|
||||
- TipType: 19
|
||||
Title: 20036
|
||||
Message: 20037
|
||||
CooldownSeconds: 5
|
||||
- TipType: 20
|
||||
Title: 20038
|
||||
Message: 20039
|
||||
CooldownSeconds: 5
|
||||
- TipType: 21
|
||||
Title: 20040
|
||||
Message: 20041
|
||||
CooldownSeconds: 3
|
||||
- TipType: 22
|
||||
Title: 20042
|
||||
Message: 20043
|
||||
CooldownSeconds: 5
|
||||
- TipType: 23
|
||||
Title: 20044
|
||||
Message: 20045
|
||||
CooldownSeconds: 5
|
||||
- TipType: 24
|
||||
Title: 20046
|
||||
Message: 20047
|
||||
CooldownSeconds: 5
|
||||
- TipType: 25
|
||||
Title: 20048
|
||||
Message: 20049
|
||||
CooldownSeconds: 5
|
||||
- TipType: 26
|
||||
Title: 20050
|
||||
Message: 20051
|
||||
CooldownSeconds: 3
|
||||
- TipType: 27
|
||||
Title: 20052
|
||||
Message: 20053
|
||||
CooldownSeconds: 3
|
||||
- TipType: 28
|
||||
Title: 20054
|
||||
Message: 20055
|
||||
CooldownSeconds: 2
|
||||
- TipType: 29
|
||||
Title: 20056
|
||||
Message: 20057
|
||||
CooldownSeconds: 3
|
||||
- TipType: 30
|
||||
Title: 20058
|
||||
Message: 20059
|
||||
CooldownSeconds: 5
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8a9f8a49839186478275813e5fe3dc1
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -32,6 +32,11 @@ MonoBehaviour:
|
||||
OutsideMultiplayIncludeAIPlayerHint: 2405
|
||||
OutsideMultiplayNoRoomHint: 17453
|
||||
OutsideMultiplayRoomNameSuffix: 17705
|
||||
OutsideMultiplayTeamTitle: 20066
|
||||
OutsideMultiplayAIPlayerTitle: 20067
|
||||
OutsideMultiplayOpenTitle: 20068
|
||||
OutsideMultiplayReadyTitle: 20069
|
||||
OutsideMultiplayOpenHint: 20077
|
||||
OutsideHistoryDropListNoLimitP: 17352
|
||||
OutsideHistoryDropList2P: 17353
|
||||
OutsideHistoryDropList3P: 17354
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -4312,7 +4312,7 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7849747489070299319, guid: 19402153ee79dbc4e80a78f3222e6b47, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 0
|
||||
value: 830.08167
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7849747489070299319, guid: 19402153ee79dbc4e80a78f3222e6b47, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
@ -4497,7 +4497,7 @@ PrefabInstance:
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7849747489070299319, guid: ed55b8a014ca41543a2d95f370c5750d, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 0
|
||||
value: 556.2688
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7849747489070299319, guid: ed55b8a014ca41543a2d95f370c5750d, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
|
||||
@ -1977,7 +1977,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 475.04083, y: -253.69449}
|
||||
m_SizeDelta: {x: 0, y: 137.3574}
|
||||
m_SizeDelta: {x: 830.08167, y: 137.3574}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1640998311177812195
|
||||
MonoBehaviour:
|
||||
|
||||
@ -197,7 +197,6 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4797701493746855371}
|
||||
- {fileID: 1430550738608142214}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
@ -295,7 +294,7 @@ MonoBehaviour:
|
||||
NetRowContainer: {fileID: 3252362370951117910}
|
||||
NetInfo: {fileID: 7448979660496629192}
|
||||
ScrollViewRect: {fileID: 7039295970119204888}
|
||||
ChatArea: {fileID: 6662476954447050023}
|
||||
ChatAreaRoot: {fileID: 7448979660496629192}
|
||||
--- !u!1 &3830065821644900243
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -779,233 +778,3 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_ShowMaskGraphic: 0
|
||||
--- !u!1001 &3208565566366438738
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 7448979660496629192}
|
||||
m_Modifications:
|
||||
- target: {fileID: 786537916787247843, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_Colors.m_DisabledColor.a
|
||||
value: 0.78431374
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1145808588851263750, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: ChatAreaPanel
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1554949039177054909, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_Colors.m_DisabledColor.a
|
||||
value: 0.78431374
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2045491555638099912, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2045491555638099912, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2045491555638099912, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2045491555638099912, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 261.94098
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2045491555638099912, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -20.005001
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_Pivot.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_Pivot.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: 913.04
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 162.68
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: -408.66
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5312906796478825185, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5312906796478825185, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5312906796478825185, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5806223491937344649, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6734467027181266523, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_Color.a
|
||||
value: 0.78431374
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7907971095448877940, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
propertyPath: m_Color.a
|
||||
value: 0.78431374
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents:
|
||||
- targetCorrespondingSourceObject: {fileID: 5084579344674831282, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 3277486792873264684}
|
||||
- targetCorrespondingSourceObject: {fileID: 8618571515869483388, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: -1501497239500558402}
|
||||
m_SourcePrefab: {fileID: 100100000, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
--- !u!224 &1430550738608142214 stripped
|
||||
RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 4565888280615936724, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
m_PrefabInstance: {fileID: 3208565566366438738}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1 &6565206209314489390 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 8618571515869483388, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
m_PrefabInstance: {fileID: 3208565566366438738}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!114 &-1501497239500558402
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6565206209314489390}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6b27f832d22e4a8d916272b644937774, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Ban: 0
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 18954
|
||||
FontID: 1
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
ApplyFontSize: 0
|
||||
FontSize: 20
|
||||
ApplyCharacterSpacing: 0
|
||||
CharacterSpacing: 0
|
||||
ApplyWordSpacing: 0
|
||||
WordSpacing: 0
|
||||
ApplyLineSpacing: 0
|
||||
LineSpacing: 0
|
||||
ApplyParagraphSpacing: 0
|
||||
ParagraphSpacing: 0
|
||||
--- !u!114 &6662476954447050023 stripped
|
||||
MonoBehaviour:
|
||||
m_CorrespondingSourceObject: {fileID: -1084591119027101579, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
m_PrefabInstance: {fileID: 3208565566366438738}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 078a34fe11854024b88b19ee4dcacdac, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!1 &7644597152579317472 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 5084579344674831282, guid: a3219b8bdc414264e87cb080245b9a45, type: 3}
|
||||
m_PrefabInstance: {fileID: 3208565566366438738}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!114 &3277486792873264684
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7644597152579317472}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6b27f832d22e4a8d916272b644937774, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Ban: 0
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 19739
|
||||
FontID: 0
|
||||
TextCfg: []
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
8
Unity/Assets/Resources/Prefab/UI/Common.meta
Normal file
8
Unity/Assets/Resources/Prefab/UI/Common.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d77441245d1b39240867efb0c993f19c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Unity/Assets/Resources/Prefab/UI/Common/Chat.meta
Normal file
8
Unity/Assets/Resources/Prefab/UI/Common/Chat.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87951fe549cc3324e9570ee8caabcd6d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -137,7 +137,7 @@ MonoBehaviour:
|
||||
DocText: {fileID: 3795164220530813026}
|
||||
MaxChatCount: 2
|
||||
MessageLifetime: 10
|
||||
DocRecentCount: 10
|
||||
DocRecentCount: 50
|
||||
--- !u!1 &2477995237858003733
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -171,7 +171,7 @@ RectTransform:
|
||||
m_Father: {fileID: 9183018199567352959}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: -65.503784, y: -960}
|
||||
m_SizeDelta: {x: 20, y: 20}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@ -914,8 +914,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 261.94098, y: -20.005001}
|
||||
m_SizeDelta: {x: 503.882, y: 20.01}
|
||||
m_AnchoredPosition: {x: 261.94098, y: -35.004997}
|
||||
m_SizeDelta: {x: 503.882, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &4208484589109369530
|
||||
CanvasRenderer:
|
||||
@ -945,7 +945,7 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: asfasfasf
|
||||
m_text: "\u5929\u706B\u4EBA\u96EA\u7CD5\uFF1A\u4E3A\u4EC0\u4E48\u963F\u71D0\u7684\u5C3E\u5DF4\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u8FD9\u4E48\u957F\uFF01\uFF01"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8e119f168f1a6b745be02ef19f51610f, type: 2}
|
||||
m_sharedMaterial: {fileID: -8081454072124122709, guid: 8e119f168f1a6b745be02ef19f51610f, type: 2}
|
||||
@ -988,7 +988,7 @@ MonoBehaviour:
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
@ -1044,7 +1044,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 19739
|
||||
ID: 20076
|
||||
FontID: 0
|
||||
TextCfg: []
|
||||
--- !u!1 &5277186255209122562
|
||||
@ -1478,9 +1478,9 @@ RectTransform:
|
||||
m_Father: {fileID: 9123125548031705264}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -10, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!222 &4276625840450642673
|
||||
CanvasRenderer:
|
||||
@ -1683,7 +1683,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &86432792637060186
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -2420,7 +2420,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0.000002861, y: 0.000028818846}
|
||||
m_SizeDelta: {x: 0.00010681, y: 30.01}
|
||||
m_SizeDelta: {x: 0.00010681, y: 0}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!114 &926841285576668722
|
||||
MonoBehaviour:
|
||||
8
Unity/Assets/Resources/Prefab/UI/Common/Popup.meta
Normal file
8
Unity/Assets/Resources/Prefab/UI/Common/Popup.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7bc59594a797af4e901bc036291c03e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eca335a4eca8c2e499c58e552a7b0f9d
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Unity/Assets/Resources/Prefab/UI/Global.meta
Normal file
8
Unity/Assets/Resources/Prefab/UI/Global.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5218c398c8b8364891b636ee8199fca
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
4930
Unity/Assets/Resources/Prefab/UI/Global/UIGlobalBugReport.prefab
Normal file
4930
Unity/Assets/Resources/Prefab/UI/Global/UIGlobalBugReport.prefab
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 017702e379c4b504f909f601f647475f
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -3404,7 +3404,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 147, y: -23}
|
||||
m_SizeDelta: {x: 120.01, y: 32.0265}
|
||||
m_SizeDelta: {x: 0, y: 32.0265}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &5182836495056545005
|
||||
CanvasRenderer:
|
||||
@ -4348,7 +4348,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 250, y: -550.093}
|
||||
m_AnchoredPosition: {x: 250, y: -480.093}
|
||||
m_SizeDelta: {x: 500, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &4676610239985244751
|
||||
@ -5247,7 +5247,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 250, y: -785.093}
|
||||
m_AnchoredPosition: {x: 250, y: -550.093}
|
||||
m_SizeDelta: {x: 500, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1555931548957767936
|
||||
|
||||
@ -794,6 +794,242 @@ MonoBehaviour:
|
||||
clickSound: {fileID: 0}
|
||||
pressScale: 0.8
|
||||
scaleDuration: 0.1
|
||||
--- !u!1 &2140407895753325769
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7572296490546585051}
|
||||
- component: {fileID: 6457064681279636580}
|
||||
- component: {fileID: 7747318549009700087}
|
||||
- component: {fileID: 5912048873162754301}
|
||||
- component: {fileID: 5535755370362207064}
|
||||
- component: {fileID: 6243691724474789413}
|
||||
m_Layer: 5
|
||||
m_Name: TransReport
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &7572296490546585051
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2140407895753325769}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 5814070196398329509}
|
||||
m_Father: {fileID: 3770347386733582695}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 397.9826, y: -34}
|
||||
m_SizeDelta: {x: 338.1129, y: 53.9677}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6457064681279636580
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2140407895753325769}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &7747318549009700087
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2140407895753325769}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: df39380aaef39404eaf3e0e9183c785c, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1.5
|
||||
--- !u!114 &5912048873162754301
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2140407895753325769}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 0
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 7747318549009700087}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!82 &5535755370362207064
|
||||
AudioSource:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2140407895753325769}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 4
|
||||
OutputAudioMixerGroup: {fileID: 0}
|
||||
m_audioClip: {fileID: 0}
|
||||
m_PlayOnAwake: 1
|
||||
m_Volume: 1
|
||||
m_Pitch: 1
|
||||
Loop: 0
|
||||
Mute: 0
|
||||
Spatialize: 0
|
||||
SpatializePostEffects: 0
|
||||
Priority: 128
|
||||
DopplerLevel: 1
|
||||
MinDistance: 1
|
||||
MaxDistance: 500
|
||||
Pan2D: 0
|
||||
rolloffMode: 0
|
||||
BypassEffects: 0
|
||||
BypassListenerEffects: 0
|
||||
BypassReverbZones: 0
|
||||
rolloffCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
panLevelCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
spreadCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
reverbZoneMixCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
--- !u!114 &6243691724474789413
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2140407895753325769}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 33d75335a9dad784a91baba5578371fb, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
hoverSound: {fileID: 0}
|
||||
targetScale: 1.1
|
||||
clickSound: {fileID: 0}
|
||||
pressScale: 0.8
|
||||
scaleDuration: 0.1
|
||||
--- !u!1 &2717726101838707478
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -1398,6 +1634,7 @@ MonoBehaviour:
|
||||
HistoryButton: {fileID: 7456826708375644621}
|
||||
TutorButton: {fileID: 7185898387603956833}
|
||||
StoryButton: {fileID: 3611518138806132449}
|
||||
TransReportButton: {fileID: 5912048873162754301}
|
||||
ButtonList:
|
||||
- {fileID: 5519043339116172500}
|
||||
- {fileID: 7179142844686828325}
|
||||
@ -1587,7 +1824,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 1401
|
||||
ID: 19977
|
||||
FontID: 1
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
@ -3005,6 +3242,7 @@ RectTransform:
|
||||
- {fileID: 2811878508391649866}
|
||||
- {fileID: 4025582528238821115}
|
||||
- {fileID: 8350919062383745882}
|
||||
- {fileID: 7572296490546585051}
|
||||
m_Father: {fileID: 4735088385716738539}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
@ -3200,6 +3438,160 @@ MonoBehaviour:
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &6691324969803418791
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 5814070196398329509}
|
||||
- component: {fileID: 7963249260858927386}
|
||||
- component: {fileID: 1157791559205469021}
|
||||
- component: {fileID: -7562340078456195498}
|
||||
m_Layer: 5
|
||||
m_Name: Text
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &5814070196398329509
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6691324969803418791}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 7572296490546585051}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: -4.215, y: 0.000032425}
|
||||
m_SizeDelta: {x: 308.4651, y: 53.968}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7963249260858927386
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6691324969803418791}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &1157791559205469021
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6691324969803418791}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: "\u7FFB\u8BD1\u9519\u8BEF\\bug\u53CD\u9988"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8e119f168f1a6b745be02ef19f51610f, type: 2}
|
||||
m_sharedMaterial: {fileID: -8081454072124122709, guid: 8e119f168f1a6b745be02ef19f51610f, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4285542705
|
||||
m_fontColor: {r: 0.19215688, g: 0.19215688, b: 0.43921572, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 24
|
||||
m_fontSizeBase: 24
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 24
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!114 &-7562340078456195498
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6691324969803418791}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6b27f832d22e4a8d916272b644937774, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Ban: 0
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 20074
|
||||
FontID: 0
|
||||
TextCfg: []
|
||||
--- !u!1 &6763301404642353131
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -86,9 +86,8 @@ GameObject:
|
||||
- component: {fileID: 2438225421395076648}
|
||||
- component: {fileID: 591212338599555894}
|
||||
- component: {fileID: 7030574526118729750}
|
||||
- component: {fileID: 959187344526487202}
|
||||
m_Layer: 5
|
||||
m_Name: UIOutsideMultiplayRoomMemberButtonRow
|
||||
m_Name: UIOutsideMultiplayRoomMemberLineRow
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
@ -152,23 +151,3 @@ MonoBehaviour:
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1.25
|
||||
--- !u!114 &959187344526487202
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7377647467463133788}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: b944c6e03ad742440a76463c2ecf000a, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
AvatarImage: {fileID: 0}
|
||||
LeaderImage: {fileID: 0}
|
||||
NameText: {fileID: 0}
|
||||
StatusText: {fileID: 0}
|
||||
ForcesText: {fileID: 0}
|
||||
ForcesButton: {fileID: 0}
|
||||
RefreshButton: {fileID: 0}
|
||||
QuitButton: {fileID: 0}
|
||||
@ -1935,7 +1935,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 1635
|
||||
ID: 19995
|
||||
FontID: 2
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
@ -2505,7 +2505,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 2225
|
||||
ID: 2231
|
||||
FontID: 1
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
@ -3010,6 +3010,160 @@ MonoBehaviour:
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &7335209913350123456
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4667728109123456789}
|
||||
- component: {fileID: 8120436579012345678}
|
||||
- component: {fileID: 6024178395501234567}
|
||||
- component: {fileID: 4008369207175740087}
|
||||
m_Layer: 5
|
||||
m_Name: OpenHintText
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4667728109123456789
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7335209913350123456}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2438225421395076648}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0.5}
|
||||
m_AnchorMax: {x: 0, y: 0.5}
|
||||
m_AnchoredPosition: {x: 860.5266, y: 0.70847}
|
||||
m_SizeDelta: {x: 271.2567, y: 57.991}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &8120436579012345678
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7335209913350123456}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &6024178395501234567
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7335209913350123456}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: "\u5F85\u52A0\u5165"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: ce4904f8ddac15944907907115531ad5, type: 2}
|
||||
m_sharedMaterial: {fileID: 1214840240034325189, guid: ce4904f8ddac15944907907115531ad5, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4287072135
|
||||
m_fontColor: {r: 0.5283019, g: 0.5283019, b: 0.5283019, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 28
|
||||
m_fontSizeBase: 28
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 12
|
||||
m_fontSizeMax: 28
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 1
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!114 &4008369207175740087
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7335209913350123456}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6b27f832d22e4a8d916272b644937774, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Ban: 0
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 20068
|
||||
FontID: 0
|
||||
TextCfg: []
|
||||
--- !u!1 &7372916197570849001
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -3123,6 +3277,7 @@ RectTransform:
|
||||
- {fileID: 8168637493792220255}
|
||||
- {fileID: 2439652715768046550}
|
||||
- {fileID: 8207125967839836190}
|
||||
- {fileID: 4667728109123456789}
|
||||
- {fileID: 6303313510194795360}
|
||||
- {fileID: 3110038563642209199}
|
||||
- {fileID: 6579359848737393927}
|
||||
@ -3184,11 +3339,19 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: b944c6e03ad742440a76463c2ecf000a, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
AvatarRoot: {fileID: 6592483684148402150}
|
||||
AvatarImage: {fileID: 7762177666811050190}
|
||||
LeaderRoot: {fileID: 3382124894360767356}
|
||||
LeaderImage: {fileID: 2028509407625721253}
|
||||
NameText: {fileID: 6503687505537430979}
|
||||
OpenHintText: {fileID: 6024178395501234567}
|
||||
StatusText: {fileID: 9094648081951770890}
|
||||
ForcesText: {fileID: 2875130741396939258}
|
||||
ForcesButton: {fileID: 8612292243586636816}
|
||||
RefreshButton: {fileID: 8612292243586636816}
|
||||
QuitButton: {fileID: 8717023295481533829}
|
||||
ForceLeftButton: {fileID: 8612292243586636816}
|
||||
ForceRightButton: {fileID: 6556385597021353403}
|
||||
TeamText: {fileID: 3332880645268972119}
|
||||
TeamLeftButton: {fileID: 8962252367205184452}
|
||||
TeamRightButton: {fileID: 2906850645216239320}
|
||||
|
||||
@ -2200,9 +2200,9 @@ RectTransform:
|
||||
- {fileID: 7354981697241446154}
|
||||
m_Father: {fileID: 5566567905778636362}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 36.7275, y: -39.524}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 73.455, y: 79.048}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6757100784094017993
|
||||
@ -3167,9 +3167,9 @@ RectTransform:
|
||||
- {fileID: 3962389234318188601}
|
||||
m_Father: {fileID: 3698126482628459019}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 377.4315, y: -59.52385}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 714.863, y: 79.0477}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &2164550660865405208
|
||||
@ -3529,9 +3529,9 @@ RectTransform:
|
||||
- {fileID: 7578212672192949060}
|
||||
m_Father: {fileID: 5566567905778636362}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 416.73907, y: -24.0022}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 686.5681, y: 48.0044}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6522695967421094694
|
||||
|
||||
@ -302,7 +302,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 121.7672, y: 35.2382}
|
||||
m_SizeDelta: {x: 208.1909, y: 35.238}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &9186104963804813620
|
||||
CanvasRenderer:
|
||||
@ -4735,6 +4735,171 @@ CanvasRenderer:
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3645069085365621322}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!1 &3732511013873620431
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1535537025373028270}
|
||||
- component: {fileID: 3394967736349762794}
|
||||
- component: {fileID: 5925858155960801726}
|
||||
- component: {fileID: 7130562122336279992}
|
||||
m_Layer: 5
|
||||
m_Name: Text
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1535537025373028270
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3732511013873620431}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 8441048275297643431}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: -1.9145, y: 0.000014783}
|
||||
m_SizeDelta: {x: 192.3311, y: 35.238}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &3394967736349762794
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3732511013873620431}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &5925858155960801726
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3732511013873620431}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: "\u7FFB\u8BD1\u9519\u8BEF\\bug\u53CD\u9988"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8e119f168f1a6b745be02ef19f51610f, type: 2}
|
||||
m_sharedMaterial: {fileID: -8081454072124122709, guid: 8e119f168f1a6b745be02ef19f51610f, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 18
|
||||
m_fontSizeBase: 32
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 4
|
||||
m_fontSizeMax: 18
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!114 &7130562122336279992
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3732511013873620431}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6b27f832d22e4a8d916272b644937774, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Ban: 0
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 20074
|
||||
FontID: 1
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
ApplyFontSize: 0
|
||||
FontSize: 14
|
||||
ApplyCharacterSpacing: 0
|
||||
CharacterSpacing: 0
|
||||
ApplyWordSpacing: 0
|
||||
WordSpacing: 0
|
||||
ApplyLineSpacing: 0
|
||||
LineSpacing: 0
|
||||
ApplyParagraphSpacing: 0
|
||||
ParagraphSpacing: 0
|
||||
--- !u!1 &3873969227329466608
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -5948,6 +6113,7 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4837278989566805878}
|
||||
- {fileID: 8441048275297643431}
|
||||
m_Father: {fileID: 1451539500624979695}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
@ -5968,12 +6134,12 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Padding:
|
||||
m_Left: 0
|
||||
m_Left: 20
|
||||
m_Right: 0
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
m_ChildAlignment: 0
|
||||
m_Spacing: 0
|
||||
m_Spacing: 20
|
||||
m_ChildForceExpandWidth: 0
|
||||
m_ChildForceExpandHeight: 0
|
||||
m_ChildControlWidth: 0
|
||||
@ -8965,6 +9131,7 @@ MonoBehaviour:
|
||||
CloseButton: {fileID: 2931647333088810527}
|
||||
BlockButton: {fileID: 5282322453820411557}
|
||||
RepairRendererButton: {fileID: 1941810936467228881}
|
||||
BugReportButton: {fileID: 1972885062573234700}
|
||||
LanguageOptionGroup: {fileID: 3444656944565885498}
|
||||
KeyMomentToggle: {fileID: 4395786735994074065}
|
||||
BgmContinuousToggle: {fileID: 5173756232815940152}
|
||||
@ -9094,8 +9261,8 @@ MonoBehaviour:
|
||||
m_TargetGraphic: {fileID: 7771937312228267370}
|
||||
m_HandleRect: {fileID: 6801890359025387631}
|
||||
m_Direction: 2
|
||||
m_Value: 1
|
||||
m_Size: 0.8625885
|
||||
m_Value: 1.0000005
|
||||
m_Size: 0.86258847
|
||||
m_NumberOfSteps: 0
|
||||
m_OnValueChanged:
|
||||
m_PersistentCalls:
|
||||
@ -9626,6 +9793,242 @@ RectTransform:
|
||||
m_AnchoredPosition: {x: 0.00012207031, y: 0}
|
||||
m_SizeDelta: {x: -20, y: -20}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!1 &8249099813133539951
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8441048275297643431}
|
||||
- component: {fileID: 3050059261919365178}
|
||||
- component: {fileID: 8280418899881803767}
|
||||
- component: {fileID: 1972885062573234700}
|
||||
- component: {fileID: 4910684173926155080}
|
||||
- component: {fileID: 7833713733495233730}
|
||||
m_Layer: 5
|
||||
m_Name: BugReport
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &8441048275297643431
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8249099813133539951}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1535537025373028270}
|
||||
m_Father: {fileID: 1573632891455071784}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 230.2221, y: 35.238}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &3050059261919365178
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8249099813133539951}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &8280418899881803767
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8249099813133539951}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 1e43ee4b2c787014db05c4f94b37be0e, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 1
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 2.5
|
||||
--- !u!114 &1972885062573234700
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8249099813133539951}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 0
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 8280418899881803767}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!114 &4910684173926155080
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8249099813133539951}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 33d75335a9dad784a91baba5578371fb, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
hoverSound: {fileID: 8300000, guid: b70699fb70c0d3b44a22f2bdf87da7d2, type: 3}
|
||||
targetScale: 1.1
|
||||
clickSound: {fileID: 0}
|
||||
pressScale: 0.95
|
||||
scaleDuration: 0.1
|
||||
--- !u!82 &7833713733495233730
|
||||
AudioSource:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8249099813133539951}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 4
|
||||
OutputAudioMixerGroup: {fileID: 0}
|
||||
m_audioClip: {fileID: 0}
|
||||
m_PlayOnAwake: 1
|
||||
m_Volume: 1
|
||||
m_Pitch: 1
|
||||
Loop: 0
|
||||
Mute: 0
|
||||
Spatialize: 0
|
||||
SpatializePostEffects: 0
|
||||
Priority: 128
|
||||
DopplerLevel: 1
|
||||
MinDistance: 1
|
||||
MaxDistance: 500
|
||||
Pan2D: 0
|
||||
rolloffMode: 0
|
||||
BypassEffects: 0
|
||||
BypassListenerEffects: 0
|
||||
BypassReverbZones: 0
|
||||
rolloffCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
panLevelCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
spreadCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
reverbZoneMixCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
--- !u!1 &8424698946813465439
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -10794,12 +11197,12 @@ MonoBehaviour:
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 14
|
||||
m_fontSize: 18
|
||||
m_fontSizeBase: 32
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 1
|
||||
m_fontSizeMax: 14
|
||||
m_fontSizeMin: 4
|
||||
m_fontSizeMax: 18
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
|
||||
@ -874,7 +874,7 @@ GameObject:
|
||||
- component: {fileID: 39487247}
|
||||
- component: {fileID: 39487248}
|
||||
m_Layer: 5
|
||||
m_Name: OutsideTop
|
||||
m_Name: GlobalTop
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
|
||||
@ -84,9 +84,11 @@ namespace TH1_Core.Events
|
||||
EventManager.Subscribe<HideUIOutsideStory>(HandleHideUIOutsideStory);
|
||||
EventManager.Subscribe<ShowUIOutsideMod>(HandleShowUIOutsideMod);
|
||||
EventManager.Subscribe<HideUIOutsideMod>(HandleHideUIOutsideMod);
|
||||
|
||||
|
||||
|
||||
EventManager.Subscribe<ShowUIGlobalBugReport>(HandleShowUIGlobalBugReport);
|
||||
EventManager.Subscribe<HideUIGlobalBugReport>(HandleHideUIGlobalBugReport);
|
||||
|
||||
|
||||
|
||||
EventManager.Subscribe<ShowUIBottomBottomBar>(HandleShowUIBottomBottomBar);
|
||||
EventManager.Subscribe<HideUIBottomBottomBar>(HandleHideUIBottomBottomBar);
|
||||
EventManager.Subscribe<ShowUIBottomBottomBarNextTurn>(HandleShowUIBottomBottomBarNextTurn);
|
||||
@ -175,8 +177,10 @@ namespace TH1_Core.Events
|
||||
|
||||
EventManager.Unsubscribe<ShowUIOutsideMod>(HandleShowUIOutsideMod);
|
||||
EventManager.Unsubscribe<HideUIOutsideMod>(HandleHideUIOutsideMod);
|
||||
EventManager.Unsubscribe<ShowUIGlobalBugReport>(HandleShowUIGlobalBugReport);
|
||||
EventManager.Unsubscribe<HideUIGlobalBugReport>(HandleHideUIGlobalBugReport);
|
||||
|
||||
|
||||
|
||||
EventManager.Unsubscribe<ShowUIBottomBottomBar>(HandleShowUIBottomBottomBar);
|
||||
EventManager.Unsubscribe<HideUIBottomBottomBar>(HandleHideUIBottomBottomBar);
|
||||
EventManager.Unsubscribe<ShowUIBottomBottomBarNextTurn>(HandleShowUIBottomBottomBarNextTurn);
|
||||
@ -457,7 +461,17 @@ namespace TH1_Core.Events
|
||||
UIManager.Instance.UIOutsideManager.HideCurTask();
|
||||
}
|
||||
|
||||
|
||||
private void HandleShowUIGlobalBugReport(ShowUIGlobalBugReport evt)
|
||||
{
|
||||
ViewControllerManager.UIGlobalBugReportController?.OpenWithParam(evt);
|
||||
}
|
||||
|
||||
private void HandleHideUIGlobalBugReport(HideUIGlobalBugReport evt)
|
||||
{
|
||||
ViewControllerManager.UIGlobalBugReportController?.Close();
|
||||
}
|
||||
|
||||
|
||||
private void HandleShowUIOutsideInvited(ShowUIOutsideInvited evt)
|
||||
{
|
||||
UIManager.Instance.UIOutsideManager.OpenInvited(evt);
|
||||
|
||||
@ -196,7 +196,11 @@ namespace TH1_Core.Events
|
||||
|
||||
public struct ShowUIOutsideInvited { public ulong LobbyId; }
|
||||
public struct HideUIOutsideInvited { }
|
||||
|
||||
|
||||
//----------------------------------------- UIGlobal 相关的事件 ----------------------------------
|
||||
public struct ShowUIGlobalBugReport { }
|
||||
public struct HideUIGlobalBugReport { }
|
||||
|
||||
|
||||
// ------------------------------------------------ UIBottom 相关的事件 ------------------------------------
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ namespace TH1_Core.Managers
|
||||
OutsideUI,//所有外部UI
|
||||
BottomUI,//包含 bottombar ranking 等
|
||||
TopUI,//包含topbar
|
||||
OutsideTopUI//主要是全局通知这种
|
||||
GlobalTopUI//主要是全局通知这种
|
||||
};
|
||||
|
||||
public class UIManager
|
||||
@ -92,7 +92,7 @@ namespace TH1_Core.Managers
|
||||
UIResourceController.Instance.SetUIRoot(UIRootType.BottomUI,ROUIManager.transform.Find("Inside/Bottom"));
|
||||
UIResourceController.Instance.SetUIRoot(UIRootType.TopUI,ROUIManager.transform.Find("Inside/Top"));
|
||||
UIResourceController.Instance.SetUIRoot(UIRootType.OutsideUI,ROUIManager.transform.Find("Outside"));
|
||||
UIResourceController.Instance.SetUIRoot(UIRootType.OutsideTopUI,ROUIManager.transform.Find("OutsideTop"));
|
||||
UIResourceController.Instance.SetUIRoot(UIRootType.GlobalTopUI,ROUIManager.transform.Find("GlobalTop"));
|
||||
|
||||
|
||||
UIInfoManager = new UIInfoManager();
|
||||
|
||||
@ -38,6 +38,9 @@ public class TextDataAssets : ScriptableObject
|
||||
[MultilingualField]public string OutsideMultiplayRoomNameSuffix;
|
||||
[MultilingualField]public string OutsideMultiplayTeamTitle;
|
||||
[MultilingualField]public string OutsideMultiplayAIPlayerTitle;
|
||||
[MultilingualField]public string OutsideMultiplayOpenTitle;
|
||||
[MultilingualField]public string OutsideMultiplayReadyTitle;
|
||||
[MultilingualField]public string OutsideMultiplayOpenHint;
|
||||
|
||||
[MultilingualField]public string OutsideHistoryDropListNoLimitP;
|
||||
[MultilingualField]public string OutsideHistoryDropList2P;
|
||||
|
||||
@ -33,16 +33,19 @@ namespace TH1_Logic.Chat
|
||||
|
||||
public void SendChatItem(string message)
|
||||
{
|
||||
var lobby = LobbyManager.Instance.Lobby;
|
||||
if (lobby == null || !lobby.IsInLobby()) return;
|
||||
|
||||
var item = new ChatItem();
|
||||
item.Hash = System.Guid.NewGuid().GetHashCode();
|
||||
item.SenderId = LobbyManager.Instance.Lobby.GetSelfMemberId();
|
||||
item.SenderId = lobby.GetSelfMemberId();
|
||||
item.Message = BannedWordFilter.Filter(message);
|
||||
item.Time = Time.time;
|
||||
|
||||
ChatItems.Add(item);
|
||||
_chatItems[item.Hash] = item;
|
||||
|
||||
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) GameNetSender.Instance.BroadcastChatMessage(item);
|
||||
if (lobby.IsLobbyOwner()) GameNetSender.Instance.BroadcastChatMessage(item);
|
||||
else GameNetSender.Instance.SendChatMessage(item);
|
||||
|
||||
TrimIfNeeded();
|
||||
|
||||
@ -22,6 +22,7 @@ using TH1_Logic.Net;
|
||||
using TH1_UI.Controller.Info;
|
||||
using TH1_UI.HintUI;
|
||||
using TH1_UI.View.Bottom;
|
||||
using TH1_UI.View.Common;
|
||||
using UI;
|
||||
using UnityEngine.UI;
|
||||
|
||||
@ -87,15 +88,15 @@ namespace Logic
|
||||
public void Update()
|
||||
{
|
||||
// 聊天输入框激活时,Enter发送/关闭,ESC/右键关闭,其余快捷键全部屏蔽
|
||||
if (UIBottomNetChatAreaMono.IsInputFieldFocused)
|
||||
if (UIChatAreaMono.IsInputFieldFocused)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))
|
||||
{
|
||||
UIBottomNetChatAreaMono.SendOrClose();
|
||||
UIChatAreaMono.SendOrClose();
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Escape) || Input.GetMouseButtonDown(1))
|
||||
{
|
||||
UIBottomNetChatAreaMono.HideInputArea();
|
||||
UIChatAreaMono.HideInputArea();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -339,17 +340,17 @@ namespace Logic
|
||||
// Enter键 - 联机聊天快捷键
|
||||
if ((Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))
|
||||
&& Main.MapData?.Net != null && Main.MapData.Net.Mode == NetMode.Multi
|
||||
&& UIBottomNetChatAreaMono.IsActive)
|
||||
&& UIChatAreaMono.IsActive)
|
||||
{
|
||||
if (!UIBottomNetChatAreaMono.IsInputAreaVisible)
|
||||
if (!UIChatAreaMono.IsInputAreaVisible)
|
||||
{
|
||||
// 输入区未打开 → 打开并聚焦
|
||||
UIBottomNetChatAreaMono.ShowInputArea();
|
||||
UIChatAreaMono.ShowInputArea();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 输入区已打开但未聚焦 → 聚焦
|
||||
UIBottomNetChatAreaMono.FocusInputField();
|
||||
UIChatAreaMono.FocusInputField();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ namespace TH1_Logic.Net
|
||||
public void KickMember(ulong memberId);
|
||||
|
||||
// 创建房间
|
||||
public void CreateLobby(int maxMembers = 4,bool isPublic = true, string password = "");
|
||||
public void CreateLobby(int maxMembers = 4,bool isPublic = true, string password = "", string roomName = "");
|
||||
|
||||
// 加入房间
|
||||
public void JoinLobbyById(ulong lobbyId, string password = "");
|
||||
@ -196,9 +196,9 @@ namespace TH1_Logic.Net
|
||||
return null;
|
||||
}
|
||||
|
||||
public void CreateLobby(int maxMembers = 4,bool isPublic = true, string password = "")
|
||||
public void CreateLobby(int maxMembers = 4,bool isPublic = true, string password = "", string roomName = "")
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 获取成员数量
|
||||
|
||||
@ -34,5 +34,10 @@ namespace Logic.Skill
|
||||
self.AddSkill_Legacy(SkillType.KAGUYAFRENCHFOREVERBUFF, mapData, false, 1, true, 5, false, SpecialAddSkillType.Normal, self.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ReservedOnTransform(UnitData self, UnitFullType fullType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +105,6 @@ namespace TH1_Logic.Steam
|
||||
ForceCleanupPort(TargetPort);
|
||||
LogSystem.LogInfo($"尝试虚拟端口 {TargetPort}...");
|
||||
_listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(TargetPort, 0, null);
|
||||
|
||||
if (_listenSocket != HSteamListenSocket.Invalid)
|
||||
{
|
||||
LogSystem.LogInfo($"成功在虚拟端口 {TargetPort} 创建监听套接字: {_listenSocket}");
|
||||
|
||||
@ -13,6 +13,7 @@ using RuntimeData;
|
||||
using Steamworks;
|
||||
using TH1_Core.Events;
|
||||
using TH1_Core.Managers;
|
||||
using TH1_Logic.Chat;
|
||||
using TH1_Logic.Config;
|
||||
using TH1_Logic.Core;
|
||||
using TH1_Logic.Net;
|
||||
@ -93,6 +94,7 @@ namespace TH1_Logic.Steam
|
||||
private const int LobbyReportRenameThreshold = 5;
|
||||
private const string ReportedLobbyDefaultRoomName = "Default";
|
||||
private string _pendingLobbyPassword = "";
|
||||
private string _pendingLobbyRoomName = "";
|
||||
private bool _pendingLobbyIsPublic = true;
|
||||
private CSteamID _pendingJoinLobby = CSteamID.Nil;
|
||||
private string _pendingJoinPassword = "";
|
||||
@ -631,7 +633,7 @@ namespace TH1_Logic.Steam
|
||||
}
|
||||
|
||||
// 建房
|
||||
public void CreateLobby(int maxMembers = 4, bool isPublic = true, string password = "")
|
||||
public void CreateLobby(int maxMembers = 4, bool isPublic = true, string password = "", string roomName = "")
|
||||
{
|
||||
if (!EnsureSteamReadyForLobbyAction(nameof(CreateLobby))) return;
|
||||
if (CurrentState != LobbyState.None)
|
||||
@ -641,6 +643,7 @@ namespace TH1_Logic.Steam
|
||||
}
|
||||
|
||||
_pendingLobbyPassword = password ?? "";
|
||||
_pendingLobbyRoomName = FilterRoomName(string.IsNullOrWhiteSpace(roomName) ? GetDefaultRoomName(SelfName) : roomName.Trim());
|
||||
_pendingLobbyIsPublic = isPublic;
|
||||
CurrentState = LobbyState.Creating;
|
||||
LogSystem.LogInfo($"Creating {(isPublic ? "public" : "friends-only")} lobby with max members: {maxMembers}, hasPassword: {!string.IsNullOrEmpty(_pendingLobbyPassword)}");
|
||||
@ -1172,6 +1175,7 @@ namespace TH1_Logic.Steam
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void RefreshLobbyListInfo(LobbyListInfo lobbyInfo, CSteamID lobbyId)
|
||||
{
|
||||
if (lobbyInfo == null) return;
|
||||
@ -1203,6 +1207,11 @@ namespace TH1_Logic.Steam
|
||||
{
|
||||
var gameStateData = SteamMatchmaking.GetLobbyData(lobbyId, LobbyGameStateKey);
|
||||
return int.TryParse(gameStateData, out var gameState) ? gameState : 0;
|
||||
|
||||
public static string GetDefaultRoomName(string selfName)
|
||||
{
|
||||
return selfName + MultilingualManager.Instance.GetMultilingualText(Table.Instance.TextDataAssets.OutsideMultiplayRoomNameSuffix);
|
||||
|
||||
}
|
||||
|
||||
// 过滤房间名称,只保留中文、英文字母、数字和常用符号
|
||||
@ -1231,8 +1240,9 @@ namespace TH1_Logic.Steam
|
||||
// 达到最大长度时停止
|
||||
if (result.Length >= maxLength) break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(result.ToString())) return "Default";
|
||||
return result.ToString();
|
||||
var filteredRoomName = result.ToString();
|
||||
if (string.IsNullOrEmpty(filteredRoomName)) return "Default";
|
||||
return BannedWordFilter.Filter(filteredRoomName);
|
||||
}
|
||||
|
||||
public bool IsInitialized()
|
||||
@ -1324,6 +1334,39 @@ namespace TH1_Logic.Steam
|
||||
return SteamMatchmaking.GetLobbyData(CurrentLobby, key);
|
||||
}
|
||||
|
||||
public string GetRoomName()
|
||||
{
|
||||
var roomName = GetLobbyData("RoomName");
|
||||
return FilterRoomName(string.IsNullOrWhiteSpace(roomName) ? GetDefaultRoomName(SelfName) : roomName);
|
||||
}
|
||||
|
||||
public bool SetRoomName(string roomName)
|
||||
{
|
||||
if (!IsLobbyOwner())
|
||||
{
|
||||
LogSystem.LogError("Only lobby owner can set room name");
|
||||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CurrentLobby.IsValid()) return false;
|
||||
|
||||
roomName = FilterRoomName(roomName);
|
||||
bool success = SteamMatchmaking.SetLobbyData(CurrentLobby, "RoomName", roomName);
|
||||
if (success)
|
||||
{
|
||||
EventManager.Publish(new UpdateUIOutsideMultiplayLobbyList());
|
||||
EventManager.Publish(new UpdateUIOutsideMultiplayRoomSetting());
|
||||
}
|
||||
else
|
||||
{
|
||||
LogSystem.LogError($"Failed to set room name: {roomName}");
|
||||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// 房间创建回调
|
||||
private void OnLobbyCreatedCallback(LobbyCreated_t data)
|
||||
{
|
||||
@ -1331,6 +1374,7 @@ namespace TH1_Logic.Steam
|
||||
{
|
||||
CurrentState = LobbyState.None;
|
||||
_pendingLobbyPassword = "";
|
||||
_pendingLobbyRoomName = "";
|
||||
_pendingLobbyIsPublic = true;
|
||||
LogSystem.LogError($"Failed to create lobby: {data.m_eResult}");
|
||||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyCreateFailed);
|
||||
@ -1354,6 +1398,7 @@ namespace TH1_Logic.Steam
|
||||
SteamMatchmaking.SetLobbyData(CurrentLobby, "Version", ConfigManager.Instance.VersionCfg.CurVersionInfo.Version);
|
||||
//SteamMatchmaking.SetLobbyData(CurrentLobby, "RoomName", FilterRoomName(SelfName + MultilingualManager.Instance.GetMultilingualText(Table.Instance.TextDataAssets.OutsideMultiplayRoomNameSuffix)));
|
||||
SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyRoomNameKey, SelfName + MultilingualManager.Instance.GetMultilingualText(Table.Instance.TextDataAssets.OutsideMultiplayRoomNameSuffix));
|
||||
SteamMatchmaking.SetLobbyData(CurrentLobby, "RoomName", _pendingLobbyRoomName);
|
||||
SteamMatchmaking.SetLobbyData(CurrentLobby, "RoomCode", GenerateRoomCode());
|
||||
SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyGameStateKey, "0");
|
||||
SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyReportCountKey, "0");
|
||||
@ -1361,6 +1406,7 @@ namespace TH1_Logic.Steam
|
||||
SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyPasswordKey, _pendingLobbyPassword);
|
||||
SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyIsPublicKey, _pendingLobbyIsPublic ? "true" : "false");
|
||||
_pendingLobbyPassword = "";
|
||||
_pendingLobbyRoomName = "";
|
||||
_pendingLobbyIsPublic = true;
|
||||
|
||||
// 设置Rich Presence
|
||||
@ -1400,6 +1446,12 @@ namespace TH1_Logic.Steam
|
||||
private void OnLobbyDataUpdateCallback(LobbyDataUpdate_t data)
|
||||
{
|
||||
var lobbyId = new CSteamID(data.m_ulSteamIDLobby);
|
||||
if (data.m_bSuccess != 0 && CurrentLobby.IsValid() && lobbyId == CurrentLobby)
|
||||
{
|
||||
EventManager.Publish(new UpdateUIOutsideMultiplayRoomSetting());
|
||||
EventManager.Publish(new UpdateUIOutsideMultiplayLobbyList());
|
||||
}
|
||||
|
||||
if (!_pendingJoinLobby.IsValid() || lobbyId != _pendingJoinLobby) return;
|
||||
|
||||
var password = _pendingJoinPassword;
|
||||
@ -1592,6 +1644,7 @@ namespace TH1_Logic.Steam
|
||||
_steamSessionFailCount = 0;
|
||||
_refreshSteamStatus = 0f;
|
||||
_pendingLobbyPassword = "";
|
||||
_pendingLobbyRoomName = "";
|
||||
_pendingLobbyIsPublic = true;
|
||||
ClearPendingJoinLobby();
|
||||
_lobbyReporters.Clear();
|
||||
|
||||
8
Unity/Assets/Scripts/TH1_UI/Controller/Global.meta
Normal file
8
Unity/Assets/Scripts/TH1_UI/Controller/Global.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da5416ff5e2fd5d448abc57411ce9d52
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,49 @@
|
||||
using TH1_Core.Events;
|
||||
using TH1_UI.Controller.Base;
|
||||
using TH1_UI.View.Global;
|
||||
|
||||
namespace TH1_UI.Controller.Global
|
||||
{
|
||||
public class UIGlobalBugReportController : ViewController<UIGlobalBugReportView>
|
||||
{
|
||||
public UIGlobalBugReportController() { }
|
||||
|
||||
protected override void RegisterEventCallback()
|
||||
{
|
||||
base.RegisterEventCallback();
|
||||
if (WindowScript != null)
|
||||
{
|
||||
WindowScript.OnBtnCloseClick += _OnBtnCloseClick;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UnregisterEventCallback()
|
||||
{
|
||||
if (WindowScript != null)
|
||||
{
|
||||
WindowScript.OnBtnCloseClick = null;
|
||||
}
|
||||
base.UnregisterEventCallback();
|
||||
}
|
||||
|
||||
protected override void OnOpen()
|
||||
{
|
||||
base.OnOpen();
|
||||
if (_openParameter is ShowUIGlobalBugReport evt)
|
||||
{
|
||||
WindowScript?.SetContent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Close()
|
||||
{
|
||||
WindowScript?.CloseView();
|
||||
return base.Close();
|
||||
}
|
||||
|
||||
void _OnBtnCloseClick()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user