@
docs(scaffold): 敌人碰撞体 Sprite 驱动+统一 Box+全员接触伤害区 实施计划 Task1 助手(SpriteSizeOr/SetupHurtAndContactBoxes)/Task2 E001 样板/Task3 E002-E006/ Task4 ChaoFeng+通用/Task5 向导 sprite 字段/Task6 端到端验证。只改脚手架+向导。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
# 敌人碰撞体 Sprite 驱动 + 统一 Box + 全员接触伤害区 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 让脚手架 `SceneObjectPlacerTool` 生成的每个敌人(含 Boss)的主体/HurtBox/ContactDamageZone 三者统一为 BoxCollider2D、同尺寸、底部对齐 y=0,尺寸由向导默认 Sprite 的包围盒推导(留空回退硬编码),且每个敌人都有 ContactDamageZone。
|
||||
|
||||
**Architecture:** 在 `SceneObjectPlacerTool` 新增两个静态助手(`SpriteSizeOr`、`SetupHurtAndContactBoxes`),把各 `PlaceExxx` 里参差的 Capsule/Circle HurtBox/接触伤害创建统一替换为"主体 Box(size) + 助手建 HurtBox/ContactDamageZone(Box, size, trigger, 底部对齐)";`CharacterWizardWindow` 加一个可选默认 Sprite 字段并透传。只改这两个编辑器脚本,不动任何 prefab。
|
||||
|
||||
**Tech Stack:** Unity 2022.3 (C# 9)、UnityEditor、UI Toolkit(向导)、Unity MCP(端口 7890)做编译门与放置验证。
|
||||
|
||||
**设计文档:** `Docs/superpowers/specs/2026-07-10-enemy-sprite-driven-box-colliders-design.md`
|
||||
|
||||
**全局约定:**
|
||||
- **编译门**:`unity_execute_code`(port 7890) 跑 `AssetDatabase.Refresh(); CompilationPipeline.RequestScriptCompilation(); return "ok";` → Bash `sleep 9` → `unity_get_compilation_errors` severity=error 期望 count=0。桥接超时则 `unity_editor_ping` 等恢复再重试;长会话保持 Unity 窗口前台(失焦会卡死队列)。
|
||||
- 每个 Task 结束提交一次(中文 + 类型前缀)。分支已在 `fix/e001-chase-facing-and-cd-patrol`(或按需另开)。
|
||||
- **不修改任何 prefab**(用户后续手动重生成替换)。
|
||||
- **放置验证**:`PlaceExxx` 会在活动场景创建一个敌人 GameObject。验证时先确认活动场景是 TestRoomA(或任意可写场景),放置后读该对象的碰撞体,验证完 `DestroyImmediate` 清理,避免污染场景。
|
||||
|
||||
---
|
||||
|
||||
## 关键事实(实现前必读,均已核实)
|
||||
|
||||
- `EnemyBodyColliderType { Box, Capsule, Circle }`(SceneObjectPlacerTool.cs:44);`CreateBodyCollider(GameObject, EnemyBodyColliderType, Vector2 size)` 建主体碰撞体并 `AlignColliderBottomToPivot`。
|
||||
- `AlignColliderBottomToPivot(Collider2D)`:Box/Capsule 设 `offset.y = size.y*0.5`,Circle 设 `offset.y = radius` —— 底部对齐 y=0。
|
||||
- `EnsureCollidersAreTriggers(GameObject)` 已存在:把节点全部 Collider2D 设 `isTrigger=true`。
|
||||
- `GetOrCreateChild(Transform, string)`、`GetOrAddComponent<T>(GameObject)`、`SetLayer(GameObject, string, List<string>)`、`AssignReference(Object, string, Object, List<string>)`、`FindFirstAsset(params string[])` 均已存在。
|
||||
- `SetupSpriteRenderer(GameObject) → SpriteRenderer`:若 sr.sprite 为空则填一个默认 Square;返回 sr。
|
||||
- 各敌人现有 fallback 主体尺寸(Vector2):E001=0.6×0.8、E002=0.5×0.7、E003=0.5×0.6、E004=0.8×1.2、E005=0.9×1.0、E006=0.7×1.0、ChaoFeng=1.2×2.0、PlaceEnemy(通用)=0.7×0.9、PlaceBossEnemy(通用)=1.5×2.5。
|
||||
- E001 接触伤害现有接线:`BodyContactDamage.enabled=false`(伪装期不伤,追击能力开);`ContactChaseAbility._contactDamage` 绑该 BodyContactDamage;ContactDamageZone 的 `HitBox._defaultSource` 绑 `FindFirstAsset("CMB_DS_EnemyBody","DS_EnemyBody")`。
|
||||
- 命名空间:`BaseGames.Combat`(HurtBox/HitBox)、`BaseGames.Enemies`(BodyContactDamage)。SceneObjectPlacerTool 顶部已 `using` 这些。
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 新增两个助手 `SpriteSizeOr` / `SetupHurtAndContactBoxes`
|
||||
|
||||
**Files:**
|
||||
- Modify: `Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs`(紧邻现有 `EnsureCollidersAreTriggers` 助手之后)
|
||||
|
||||
- [ ] **Step 1: 加两个助手**
|
||||
|
||||
在 `EnsureCollidersAreTriggers` 方法之后插入:
|
||||
```csharp
|
||||
/// <summary>Sprite 有则用其世界包围盒尺寸(sprite.bounds.size),否则回退。</summary>
|
||||
private static Vector2 SpriteSizeOr(Sprite s, Vector2 fallback)
|
||||
=> s != null ? (Vector2)s.bounds.size : fallback;
|
||||
|
||||
/// <summary>
|
||||
/// 建/更新 HurtBox 与 ContactDamageZone 两个子节点:均为 BoxCollider2D、同尺寸 size、
|
||||
/// 底部对齐 y=0、isTrigger=true。ContactDamageZone 带 HitBox + BodyContactDamage。
|
||||
/// contactEnabled:BodyContactDamage 初始启用状态(不需要接触伤害的敌人可传 false 或后续置节点非激活)。
|
||||
/// 返回 (hurtBox, bodyContact, contactHitBox) 供上层按需接线(如伤害源/能力引用)。
|
||||
/// </summary>
|
||||
private static (HurtBox hurt, BodyContactDamage contact, HitBox contactHitBox)
|
||||
SetupHurtAndContactBoxes(GameObject root, Vector2 size, bool contactEnabled, List<string> report)
|
||||
{
|
||||
// HurtBox 子节点
|
||||
var hurtT = GetOrCreateChild(root.transform, "HurtBox");
|
||||
SetLayer(hurtT.gameObject, "EnemyHurtBox", report);
|
||||
var hurtCol = GetOrAddComponent<BoxCollider2D>(hurtT.gameObject);
|
||||
hurtCol.size = size;
|
||||
hurtCol.isTrigger = true;
|
||||
AlignColliderBottomToPivot(hurtCol);
|
||||
var hurtBox = GetOrAddComponent<HurtBox>(hurtT.gameObject);
|
||||
EnsureCollidersAreTriggers(hurtT.gameObject);
|
||||
|
||||
// ContactDamageZone 子节点
|
||||
var contactT = GetOrCreateChild(root.transform, "ContactDamageZone");
|
||||
SetLayer(contactT.gameObject, "EnemyHitBox", report);
|
||||
var contactCol = GetOrAddComponent<BoxCollider2D>(contactT.gameObject);
|
||||
contactCol.size = size;
|
||||
contactCol.isTrigger = true;
|
||||
AlignColliderBottomToPivot(contactCol);
|
||||
var contactHitBox = GetOrAddComponent<HitBox>(contactT.gameObject);
|
||||
var bodyContact = GetOrAddComponent<BodyContactDamage>(contactT.gameObject);
|
||||
bodyContact.enabled = contactEnabled;
|
||||
EnsureCollidersAreTriggers(contactT.gameObject);
|
||||
|
||||
return (hurtBox, bodyContact, contactHitBox);
|
||||
}
|
||||
```
|
||||
|
||||
注意:`HurtBox`/`HitBox` 在 `BaseGames.Combat`,`BodyContactDamage` 在 `BaseGames.Enemies`,文件顶部已 using。`GetOrAddComponent<BoxCollider2D>` 若节点已有别的 Collider2D 不会移除——但本任务不改现有 prefab,全新放置的节点只会有这一个 Box,无残留。
|
||||
|
||||
- [ ] **Step 2: 编译门** → count=0(此时助手未被调用,只验证语法/引用)。
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
```bash
|
||||
git add Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs
|
||||
git commit -m "feat(scaffold): 新增 SpriteSizeOr / SetupHurtAndContactBoxes 助手(Box 统一 HurtBox/接触伤害区)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: E001 改用助手 + sprite 参数(参考样板)
|
||||
|
||||
**Files:**
|
||||
- Modify: `Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs`(`PlaceE001_CaoZhi`)
|
||||
|
||||
- [ ] **Step 1: 改签名加 sprite 参数**
|
||||
|
||||
把重载:
|
||||
```csharp
|
||||
public static void PlaceE001_CaoZhi() => PlaceE001_CaoZhi(EnemyBodyColliderType.Box);
|
||||
public static void PlaceE001_CaoZhi(EnemyBodyColliderType bodyCollider)
|
||||
```
|
||||
改为:
|
||||
```csharp
|
||||
public static void PlaceE001_CaoZhi() => PlaceE001_CaoZhi(EnemyBodyColliderType.Box, null);
|
||||
public static void PlaceE001_CaoZhi(EnemyBodyColliderType bodyCollider, Sprite defaultSprite = null)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 用 sprite 推导 size、赋 SpriteRenderer、走助手**
|
||||
|
||||
E001 现有片段(约 457-489 行)大致为:
|
||||
```csharp
|
||||
Collider2D body = CreateBodyCollider(go, bodyCollider, new Vector2(0.6f, 0.8f));
|
||||
...
|
||||
SpriteRenderer sr1 = SetupSpriteRenderer(visual.gameObject);
|
||||
...
|
||||
Transform hurtBoxT = GetOrCreateChild(go.transform, "HurtBox");
|
||||
SetLayer(hurtBoxT.gameObject, "EnemyHurtBox", report);
|
||||
CapsuleCollider2D hurtCap = GetOrAddComponent<CapsuleCollider2D>(hurtBoxT.gameObject);
|
||||
hurtCap.isTrigger = true;
|
||||
hurtCap.size = new Vector2(0.55f, 0.75f);
|
||||
AlignColliderBottomToPivot(hurtCap);
|
||||
HurtBox hurtBox = GetOrAddComponent<HurtBox>(hurtBoxT.gameObject);
|
||||
EnsureCollidersAreTriggers(hurtBoxT.gameObject);
|
||||
|
||||
Transform contactT = GetOrCreateChild(go.transform, "ContactDamageZone");
|
||||
SetLayer(contactT.gameObject, "EnemyHitBox", report);
|
||||
CircleCollider2D contactCol = GetOrAddComponent<CircleCollider2D>(contactT.gameObject);
|
||||
contactCol.isTrigger = true;
|
||||
contactCol.radius = 0.4f;
|
||||
AlignColliderBottomToPivot(contactCol);
|
||||
HitBox contactHitBox = GetOrAddComponent<HitBox>(contactT.gameObject);
|
||||
BodyContactDamage bodyContact = GetOrAddComponent<BodyContactDamage>(contactT.gameObject);
|
||||
bodyContact.enabled = false;
|
||||
EnsureCollidersAreTriggers(contactT.gameObject);
|
||||
```
|
||||
|
||||
改为(size 用 sprite,主体 Box(size),HurtBox/ContactDamageZone 走助手;E001 接触伤害仍禁用+由能力控制):
|
||||
```csharp
|
||||
Vector2 size = SpriteSizeOr(defaultSprite, new Vector2(0.6f, 0.8f));
|
||||
Collider2D body = CreateBodyCollider(go, bodyCollider, size);
|
||||
...
|
||||
SpriteRenderer sr1 = SetupSpriteRenderer(visual.gameObject);
|
||||
if (defaultSprite != null) sr1.sprite = defaultSprite;
|
||||
...
|
||||
var (hurtBox, bodyContact, contactHitBox) =
|
||||
SetupHurtAndContactBoxes(go, size, contactEnabled: false, report); // E001 伪装期不伤,追击能力开
|
||||
```
|
||||
保留后续对 `bodyContact` / `contactHitBox` / `hurtBox` 的接线不变,即:
|
||||
- `AssignReference(enemyBase, "_hurtBox", hurtBox, report)` 若原有(用返回的 hurtBox)。
|
||||
- `AssignReference(chaseAbility, "_contactDamage", bodyContact, report)` 保留。
|
||||
- 伤害源:`Object dmgSrc = FindFirstAsset("CMB_DS_EnemyBody", "DS_EnemyBody"); if (dmgSrc != null) AssignReference(contactHitBox, "_defaultSource", dmgSrc, report);` 保留。
|
||||
|
||||
> 实现前 Read 当前 `PlaceE001_CaoZhi` 全文,把上面被替换段之外的接线原样保留(只换碰撞体创建方式与 size 来源)。`sr1` 变量名沿用现有。
|
||||
|
||||
- [ ] **Step 3: 编译门** → count=0。
|
||||
|
||||
- [ ] **Step 4: 放置验证(MCP)**
|
||||
|
||||
`unity_execute_code`(port 7890):反射调用 `PlaceE001_CaoZhi(EnemyBodyColliderType.Box, null)`(无 sprite→回退 0.6×0.8),然后校验并清理:
|
||||
```csharp
|
||||
var t = System.AppDomain.CurrentDomain.GetAssemblies().SelectMany(a=>{try{return a.GetTypes();}catch{return new System.Type[0];}}).FirstOrDefault(x=>x.Name=="SceneObjectPlacerTool");
|
||||
var m = t.GetMethod("PlaceE001_CaoZhi", new[]{ t.GetNestedType("EnemyBodyColliderType") ?? typeof(int), typeof(UnityEngine.Sprite) });
|
||||
// 若上面拿不到,改用带默认参数的 2-参重载:
|
||||
m = t.GetMethods().First(x=>x.Name=="PlaceE001_CaoZhi" && x.GetParameters().Length==2);
|
||||
var enumType = m.GetParameters()[0].ParameterType;
|
||||
m.Invoke(null, new object[]{ System.Enum.ToObject(enumType, 0), null });
|
||||
var go = UnityEngine.GameObject.Find("ENM_CaoZhi");
|
||||
var sb=new System.Text.StringBuilder();
|
||||
foreach(var name in new[]{"","HurtBox","ContactDamageZone"}){
|
||||
var node = name==""? go.transform : go.transform.Find(name);
|
||||
var col = node.GetComponent<UnityEngine.Collider2D>();
|
||||
string label = name==""? "BODY" : name;
|
||||
if(col is UnityEngine.BoxCollider2D b)
|
||||
sb.AppendLine(label+": Box size="+b.size.ToString("F2")+" isTrigger="+b.isTrigger+" offset.y="+b.offset.y.ToString("F3")+" (期望 offset.y=size.y/2="+(b.size.y*0.5f).ToString("F3")+")");
|
||||
else sb.AppendLine(label+": "+(col!=null?col.GetType().Name:"NO COLLIDER"));
|
||||
}
|
||||
sb.AppendLine("ContactDamageZone.BodyContactDamage.enabled="+go.transform.Find("ContactDamageZone").GetComponent<BaseGames.Enemies.BodyContactDamage>().enabled+" (E001 期望 false)");
|
||||
UnityEngine.Object.DestroyImmediate(go); // 清理
|
||||
return sb.ToString();
|
||||
```
|
||||
期望:BODY/HurtBox/ContactDamageZone 都是 Box、size=(0.60,0.80)、HurtBox/Contact isTrigger=True、BODY isTrigger=False、三者 offset.y=size.y/2、ContactDamage.enabled=False。
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
```bash
|
||||
git add Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs
|
||||
git commit -m "refactor(scaffold): E001 用统一助手建 Box HurtBox/接触伤害区 + sprite 尺寸参数"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: E002-E006 改用助手 + sprite 参数
|
||||
|
||||
**Files:**
|
||||
- Modify: `Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs`(`PlaceE002_HuangZhi`、`PlaceE003_YouZhi_Enemy`、`PlaceE004_ZhiMu_Enemy`、`PlaceE005_FeiZhi_Enemy`、`PlaceE006_Huan`)
|
||||
|
||||
对每个敌人应用**相同变换**(下方 fallback size 逐敌人不同):
|
||||
|
||||
- [ ] **Step 1: 每个敌人改签名加 sprite 参数**
|
||||
|
||||
例(E002):
|
||||
```csharp
|
||||
public static void PlaceE002_HuangZhi() => PlaceE002_HuangZhi(EnemyBodyColliderType.Box, null);
|
||||
public static void PlaceE002_HuangZhi(EnemyBodyColliderType bodyCollider, Sprite defaultSprite = null)
|
||||
```
|
||||
E003/E004/E005/E006 同理(保持各自方法名)。
|
||||
|
||||
- [ ] **Step 2: 每个敌人替换碰撞体创建**
|
||||
|
||||
对每个 `PlaceExxx`,Read 其当前实现,然后:
|
||||
1. 主体:`Vector2 size = SpriteSizeOr(defaultSprite, <fallback>);` 再 `CreateBodyCollider(go, bodyCollider, size);`(替换原 `new Vector2(...)` 硬编码)。
|
||||
2. SpriteRenderer:找到该敌人的 `SetupSpriteRenderer(...)` 调用(变量如 sr/sr3/sr5),其后加 `if (defaultSprite != null) <sr>.sprite = defaultSprite;`。
|
||||
3. HurtBox + ContactDamageZone:删除该敌人现有的 HurtBox(Capsule) 与(若有)hit/ContactDamageZone(Circle) 创建代码,改为:
|
||||
`var (hurtBox, bodyContact, contactHitBox) = SetupHurtAndContactBoxes(go, size, contactEnabled: true, report);`
|
||||
(E002/E004/E005 原本**没有** ContactDamageZone——现在统一补上,contactEnabled=true。)
|
||||
4. 保留该敌人原有对 `hurtBox` 的接线(如 `AssignReference(enemyBase, "_hurtBox", hurtBox, report)`;用返回的 hurtBox 替换原局部变量名)。若该敌人的某个攻击能力原本引用了旧 HurtBox 变量,改用返回的 `hurtBox`。
|
||||
5. 伤害源接线(统一加,若该敌人原本没有):`Object dmgSrc = FindFirstAsset("CMB_DS_EnemyBody", "DS_EnemyBody"); if (dmgSrc != null) AssignReference(contactHitBox, "_defaultSource", dmgSrc, report);`
|
||||
|
||||
**逐敌人 fallback size:**
|
||||
- E002 `PlaceE002_HuangZhi`:`new Vector2(0.5f, 0.7f)`
|
||||
- E003 `PlaceE003_YouZhi_Enemy`:`new Vector2(0.5f, 0.6f)`
|
||||
- E004 `PlaceE004_ZhiMu_Enemy`:`new Vector2(0.8f, 1.2f)`
|
||||
- E005 `PlaceE005_FeiZhi_Enemy`:`new Vector2(0.9f, 1.0f)`
|
||||
- E006 `PlaceE006_Huan`:`new Vector2(0.7f, 1.0f)`
|
||||
|
||||
> 注意:某些敌人(如 E002)的攻击能力有独立的 `_attackHitBox` / `_hurtBox` 引用——这些是**能力自己的 HitBox**,与本任务的 HurtBox/ContactDamageZone 无关,勿动;只替换"主体受击 HurtBox"与"接触伤害 ContactDamageZone"两处。实现前务必 Read 每个 PlaceExxx 全文分清。
|
||||
|
||||
- [ ] **Step 3: 编译门** → count=0。
|
||||
|
||||
- [ ] **Step 4: 放置验证(MCP)**
|
||||
|
||||
对 E002-E006 各放置一次并校验(同 Task 2 的验证代码,替换方法名与期望 GameObject 名 ENM_HuangZhi/ENM_YouZhi/ENM_ZhiMu/ENM_FeiZhi/ENM_Huan,期望 size=各 fallback、三者 Box+底部对齐、HurtBox/Contact isTrigger=True、ContactDamage.enabled=True)。每次验证后 `DestroyImmediate` 清理。
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
```bash
|
||||
git add Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs
|
||||
git commit -m "refactor(scaffold): E002-E006 统一 Box HurtBox/接触伤害区 + sprite 尺寸(补全缺失的接触伤害区)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: ChaoFeng(Boss) + 通用 PlaceEnemy/PlaceBossEnemy 改用助手 + sprite
|
||||
|
||||
**Files:**
|
||||
- Modify: `Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs`(`PlaceChaoFeng`、`PlaceEnemy`、`PlaceBossEnemy`)
|
||||
|
||||
- [ ] **Step 1: PlaceChaoFeng 加 sprite 参数 + 助手**
|
||||
|
||||
```csharp
|
||||
public static void PlaceChaoFeng() => PlaceChaoFeng(EnemyBodyColliderType.Box, null);
|
||||
public static void PlaceChaoFeng(EnemyBodyColliderType bodyCollider, Sprite defaultSprite = null)
|
||||
```
|
||||
体内:`Vector2 size = SpriteSizeOr(defaultSprite, new Vector2(1.2f, 2.0f));` → `CreateBodyCollider(go, bodyCollider, size);`;SpriteRenderer 后加 sprite 赋值;HurtBox 段(Capsule)替换为 `var (hurtBox, bodyContact, contactHitBox) = SetupHurtAndContactBoxes(go, size, contactEnabled: true, report);`(Boss 原**无** ContactDamageZone,现补上,默认启用,策划按需置非激活);保留 Boss 原有 hurtBox 接线;加伤害源接线(同上)。
|
||||
|
||||
- [ ] **Step 2: 通用 PlaceEnemy / PlaceBossEnemy 同样处理**
|
||||
|
||||
`PlaceEnemy(EnemyBodyColliderType, Sprite defaultSprite=null)` fallback `new Vector2(0.7f, 0.9f)`;`PlaceBossEnemy(...)` fallback `new Vector2(1.5f, 2.5f)`。各自:size 用 sprite、CreateBodyCollider(size)、sprite 赋 Sr、`SetupHurtAndContactBoxes(go, size, contactEnabled: true, report)`(若原本没有 HurtBox/ContactDamageZone 则新增;有则替换)。加无参重载 `=> PlaceXxx(EnemyBodyColliderType.Box, null)` 若需要。
|
||||
|
||||
- [ ] **Step 3: 编译门** → count=0。
|
||||
|
||||
- [ ] **Step 4: 放置验证(MCP)**:放置 ChaoFeng(ENM_ChaoFeng),校验三者 Box、size=(1.20,2.00)、HurtBox/Contact isTrigger=True、底部对齐、ContactDamageZone 存在。`DestroyImmediate` 清理。
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
```bash
|
||||
git add Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs
|
||||
git commit -m "refactor(scaffold): ChaoFeng + 通用 Place 统一 Box HurtBox/接触伤害区 + sprite 尺寸"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 向导 CharacterWizardWindow 加默认 Sprite 字段并透传
|
||||
|
||||
**Files:**
|
||||
- Modify: `Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs`
|
||||
|
||||
- [ ] **Step 1: 加字段**
|
||||
|
||||
在字段区(约 `_enemyBodyCollider` 声明附近)加:
|
||||
```csharp
|
||||
private Sprite _defaultSprite; // 敌人/Boss 默认外观 Sprite:碰撞体尺寸依据(可留空)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 小怪 Tab 加 ObjectField**
|
||||
|
||||
在 `RefreshEnemyTabContent` 里的 `colliderField`(EnumField "主体碰撞器类型")之后加:
|
||||
```csharp
|
||||
var spriteField = new UnityEditor.UIElements.ObjectField("默认外观 Sprite(碰撞体尺寸依据,可留空)")
|
||||
{ objectType = typeof(Sprite), allowSceneObjects = false, value = _defaultSprite };
|
||||
spriteField.RegisterValueChangedCallback(evt => _defaultSprite = evt.newValue as Sprite);
|
||||
container.Add(spriteField);
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 小怪放置调用透传 sprite**
|
||||
|
||||
`PlaceSpecificEnemy(string id, EnemyBodyColliderType bodyCollider)` 改签名加 `Sprite defaultSprite`:
|
||||
```csharp
|
||||
private static void PlaceSpecificEnemy(string id, SceneObjectPlacerTool.EnemyBodyColliderType bodyCollider, Sprite defaultSprite)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case "E001": SceneObjectPlacerTool.PlaceE001_CaoZhi(bodyCollider, defaultSprite); break;
|
||||
case "E002": SceneObjectPlacerTool.PlaceE002_HuangZhi(bodyCollider, defaultSprite); break;
|
||||
case "E003": SceneObjectPlacerTool.PlaceE003_YouZhi_Enemy(bodyCollider, defaultSprite); break;
|
||||
case "E004": SceneObjectPlacerTool.PlaceE004_ZhiMu_Enemy(bodyCollider, defaultSprite); break;
|
||||
case "E005": SceneObjectPlacerTool.PlaceE005_FeiZhi_Enemy(bodyCollider, defaultSprite); break;
|
||||
case "E006": SceneObjectPlacerTool.PlaceE006_Huan(bodyCollider, defaultSprite); break;
|
||||
default:
|
||||
Debug.LogError($"[CharacterWizardWindow] 未注册的敌人 id '{id}'。");
|
||||
SceneObjectPlacerTool.PlaceEnemy(bodyCollider, defaultSprite);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
并把调用处 `PlaceSpecificEnemy(id, _enemyBodyCollider)` 改为 `PlaceSpecificEnemy(id, _enemyBodyCollider, _defaultSprite)`(`RefreshEnemyTabContent` 里的 `MakeSceneButton(sceneLabel, () => PlaceSpecificEnemy(id, _enemyBodyCollider))`)。
|
||||
|
||||
- [ ] **Step 4: Boss Tab 加 ObjectField + 透传**
|
||||
|
||||
在 `BuildBossTab` 的 "放置嘲风到场景并绑定 SO" 按钮之前加同款 spriteField(绑 `_defaultSprite`);把该按钮改为 `MakeSceneButton("放置嘲风到场景并绑定 SO", () => SceneObjectPlacerTool.PlaceChaoFeng(SceneObjectPlacerTool.EnemyBodyColliderType.Box, _defaultSprite))`。
|
||||
|
||||
- [ ] **Step 5: 编译门** → count=0。
|
||||
|
||||
- [ ] **Step 6: 提交**
|
||||
```bash
|
||||
git add Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs
|
||||
git commit -m "feat(wizard): 敌人/Boss 加默认外观 Sprite 字段并透传给脚手架(碰撞体尺寸依据)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: 端到端 Sprite 驱动验证 + 收尾
|
||||
|
||||
**Files:** 无(仅 MCP 验证)
|
||||
|
||||
- [ ] **Step 1: 用一张真实 Sprite 走一次放置**,验证碰撞体尺寸 = sprite 包围盒
|
||||
|
||||
`unity_execute_code`(port 7890):
|
||||
```csharp
|
||||
// 取任意一张 sprite 资产
|
||||
var spriteGuid = UnityEditor.AssetDatabase.FindAssets("t:Sprite").FirstOrDefault();
|
||||
var sprite = spriteGuid!=null ? UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Sprite>(UnityEditor.AssetDatabase.GUIDToAssetPath(spriteGuid)) : null;
|
||||
if(sprite==null) return "no sprite asset found";
|
||||
var t = System.AppDomain.CurrentDomain.GetAssemblies().SelectMany(a=>{try{return a.GetTypes();}catch{return new System.Type[0];}}).FirstOrDefault(x=>x.Name=="SceneObjectPlacerTool");
|
||||
var m = t.GetMethods().First(x=>x.Name=="PlaceE002_HuangZhi" && x.GetParameters().Length==2);
|
||||
var enumType = m.GetParameters()[0].ParameterType;
|
||||
m.Invoke(null, new object[]{ System.Enum.ToObject(enumType, 0), sprite });
|
||||
var go = UnityEngine.GameObject.Find("ENM_HuangZhi");
|
||||
var body = go.GetComponent<UnityEngine.BoxCollider2D>();
|
||||
var sb=new System.Text.StringBuilder();
|
||||
sb.AppendLine("sprite.bounds.size="+((UnityEngine.Vector2)sprite.bounds.size).ToString("F3"));
|
||||
sb.AppendLine("body Box size="+body.size.ToString("F3")+" (期望=sprite.bounds.size)");
|
||||
sb.AppendLine("HurtBox size="+go.transform.Find("HurtBox").GetComponent<UnityEngine.BoxCollider2D>().size.ToString("F3"));
|
||||
sb.AppendLine("Contact size="+go.transform.Find("ContactDamageZone").GetComponent<UnityEngine.BoxCollider2D>().size.ToString("F3"));
|
||||
sb.AppendLine("SpriteRenderer.sprite="+(go.GetComponentInChildren<UnityEngine.SpriteRenderer>()?.sprite?.name ?? "null"));
|
||||
UnityEngine.Object.DestroyImmediate(go);
|
||||
return sb.ToString();
|
||||
```
|
||||
期望:body/HurtBox/Contact size 三者一致且 = `sprite.bounds.size`;SpriteRenderer.sprite = 该 sprite 名。
|
||||
|
||||
- [ ] **Step 2: 自检**:`BaseGames/Tools/Maintenance/Physics2D Layer Matrix/Check` 期望无新增不符(层未变)。
|
||||
|
||||
- [ ] **Step 3: grep 确认无残留 Capsule/Circle HurtBox/接触伤害创建**
|
||||
```
|
||||
rg -n "CapsuleCollider2D hurt|CircleCollider2D contact|CircleCollider2D hit" Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs
|
||||
```
|
||||
期望:0 命中(HurtBox/接触伤害区的 Capsule/Circle 创建都已换成助手;主体的 CreateBodyCollider 里 Capsule/Circle 分支保留属正常)。
|
||||
|
||||
- [ ] **Step 4: 更新记忆** `editor_scaffold_tools.md` / `scaffold_tools_required.md`:记录敌人碰撞体现由 sprite 驱动、三者统一 Box+底部对齐、全员有 ContactDamageZone。
|
||||
|
||||
---
|
||||
|
||||
## Self-Review(作者已核对)
|
||||
|
||||
- **Spec 覆盖**:§A 向导 sprite 字段→Task 5;§B 助手→Task 1、各敌人改造→Task 2/3/4(含补全 4 个缺失 ContactDamageZone、E001 contactEnabled=false 特例、伤害源接线);§C 不动 prefab→全程无 prefab 操作;§D 验证→各 Task Step 4 + Task 6。主体默认 Box→CreateBodyCollider 默认分支;底部对齐→助手内 AlignColliderBottomToPivot。
|
||||
- **占位符**:无 TBD;助手给完整代码;per-enemy 改造给"读现有→套模式+具体 fallback size+接线说明",因每个 PlaceExxx 的能力接线各异,显式要求实现前 Read 全文(属机械变换)。
|
||||
- **类型一致**:`SpriteSizeOr(Sprite,Vector2)→Vector2`、`SetupHurtAndContactBoxes(GameObject,Vector2,bool,List<string>)→(HurtBox,BodyContactDamage,HitBox)` 全程一致;`EnemyBodyColliderType` 参数名/默认值一致;`PlaceExxx(bodyCollider, defaultSprite=null)` 签名贯穿 Task 2-5。
|
||||
- **已知风险**:各 PlaceExxx 内的攻击能力自带 HitBox(如 E002 `_attackHitBox`)与本任务的主体 HurtBox/ContactDamageZone 不同,Task 3 已显式提示勿混淆——实现前必须 Read 每个方法全文。
|
||||
Reference in New Issue
Block a user