Files
zeling_v2/Docs_Dev/superpowers/specs/2026-07-10-enemy-sprite-driven-box-colliders-design.md
T
2026-07-21 10:22:43 +08:00

6.9 KiB
Raw Blame History

敌人碰撞体:Sprite 驱动 + 统一 Box + 全员接触伤害区 —— 设计文档

  • 日期:2026-07-10
  • 范围:只改脚手架 SceneObjectPlacerTool 与向导 CharacterWizardWindow;不动现有 prefab(用户后续手动重生成替换)。

目标

脚手架生成的每个敌人(含 Boss)满足:

  1. 主体碰撞体、HurtBox、ContactDamageZone 三者统一为 BoxCollider2D,尺寸完全一致、底部对齐 y=0(敌人原点在碰撞体底部,参考 E001;供 PathBerserker2d NavAgent 检测原点落在导航线上)。
  2. 三者尺寸由向导指定的默认 Sprite 的包围盒sprite.bounds.size)推导;Sprite 留空则回退到各敌人现有的硬编码尺寸(不破坏无 sprite 的旧行为)。
  3. 每个敌人 + Boss 都有 ContactDamageZone(接触伤害区);不需要接触伤害的敌人由策划把该节点置为非激活。

背景(现状)

  • SceneObjectPlacerToolCreateBodyCollider(go, type, size) 已支持 Box/Capsule/Circle + AlignColliderBottomToPivot(底部对齐)。主体默认 Box。
  • HurtBox 各敌人现为 CapsuleCollider2DE001 已是 Box);ContactDamageZone 参差:E001=Box、E003/E006=Circle、E002/E004/E005/ChaoFeng 缺失
  • 尺寸为每敌人硬编码(如 E001 body 0.6×0.8E002 0.5×0.7…)。
  • EnsureCollidersAreTriggers(go) 助手已存在(把节点全部 Collider2D 设 trigger)。
  • CharacterWizardWindow 小怪/ Boss Tab 已有"主体碰撞器类型"EnumField,调 PlaceSpecificEnemy(id, colliderType) / PlaceChaoFeng

设计

A. 向导:默认 Sprite 字段(可选)

  • 小怪 Tab、Boss Tab 各加一个 UnityEditor.UIElements.ObjectFieldobjectType = typeof(Sprite)),标签"默认外观 Sprite(碰撞体尺寸依据,可留空)",绑定 Sprite _enemyDefaultSpriteBoss 用同一字段或独立 _bossDefaultSprite,实现上用一个 _defaultSprite 即可,按当前 Tab 复用)。
  • 放置按钮把选中 Sprite 传入:PlaceSpecificEnemy(id, colliderType, _defaultSprite)PlaceChaoFeng(colliderType, _defaultSprite)

B. 脚手架:Sprite 驱动 + 统一 Box + 全员接触伤害区

新增两个助手:

// Sprite 有则用其世界包围盒尺寸,否则回退。
private static Vector2 SpriteSizeOr(Sprite s, Vector2 fallback)
    => s != null ? (Vector2)s.bounds.size : fallback;

// 建 HurtBox + ContactDamageZone 两个子节点,均为 Box、同尺寸、底部对齐、isTrigger。
// contactEnabledBodyContactDamage 初始启用状态(不需要接触伤害的敌人可置 false 或后续置节点非激活)。
// 返回创建的 (hurtBox, bodyContact, contactHitBox) 供上层按需接线。
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 hurtBox2D = GetOrAddComponent<BoxCollider2D>(hurtT.gameObject);
    hurtBox2D.size = size; hurtBox2D.isTrigger = true;
    AlignColliderBottomToPivot(hurtBox2D);
    var hurtBox = GetOrAddComponent<HurtBox>(hurtT.gameObject);
    EnsureCollidersAreTriggers(hurtT.gameObject);

    // ContactDamageZone 子节点
    var contactT = GetOrCreateChild(root.transform, "ContactDamageZone");
    SetLayer(contactT.gameObject, "EnemyHitBox", report);
    var contactBox = GetOrAddComponent<BoxCollider2D>(contactT.gameObject);
    contactBox.size = size; contactBox.isTrigger = true;
    AlignColliderBottomToPivot(contactBox);
    var contactHitBox = GetOrAddComponent<HitBox>(contactT.gameObject);
    var bodyContact = GetOrAddComponent<BodyContactDamage>(contactT.gameObject);
    bodyContact.enabled = contactEnabled;
    EnsureCollidersAreTriggers(contactT.gameObject);
    return (hurtBox, bodyContact, contactHitBox);
}

每个 PlaceExxx 签名加可选 sprite;用它推导 size、赋给 SpriteRenderer、走统一助手:

public static void PlaceE0xx(EnemyBodyColliderType bodyCollider = EnemyBodyColliderType.Box,
                            Sprite defaultSprite = null)
{
    ...
    Vector2 size = SpriteSizeOr(defaultSprite, /*该敌人原硬编码 fallback*/);
    var sr = SetupSpriteRenderer(visual.gameObject);
    if (defaultSprite != null) sr.sprite = defaultSprite;  // 视觉与碰撞体一致
    CreateBodyCollider(go, bodyCollider, size);            // 主体 Box(size)、底部对齐
    var (hurtBox, bodyContact, contactHitBox) =
        SetupHurtAndContactBoxes(go, size, contactEnabled: true, report);
    // 该敌人特有接线(如 E001contactEnabled=false + 把 bodyContact 绑给 ContactChaseAbility._contactDamage
    //   contactHitBox 绑 CMB_DS_EnemyBody 伤害源)。
    ...
}
  • 主体默认 BoxEnemyBodyColliderType.Box);Capsule/Circle 选项保留但默认 Box。三者尺寸都 = size
  • 接触伤害默认启用(碰到即伤):contactEnabled: trueE001 例外 contactEnabled: false(其 ContactChaseAbility 在冲刺期开启接触伤害),并把 bodyContact 绑到 ContactChaseAbility._contactDamage
  • ContactDamageZone 的 HitBox 绑默认伤害源 CMB_DS_EnemyBody(沿用 E001 现有做法)。
  • Boss PlaceChaoFeng 同样通过助手补 ContactDamageZone(默认启用,策划按需置非激活)。
  • 全部走 AlignColliderBottomToPivot(底部对齐,中心在底部)。

C. 不动现有 prefab

现有 7 个 prefab 不由本次修改,用户用更新后的脚手架手动重生成替换。

影响文件

  • Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs:新增 SpriteSizeOr / SetupHurtAndContactBoxes;改 7 个 PlaceExxx(E001-E006) + PlaceChaoFeng 用统一助手 + sprite 参数;替换原 Capsule/Circle 的 HurtBox/ContactDamageZone 创建;给缺失的 4 个敌人补 ContactDamageZone。
  • Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs:加 _defaultSprite 字段 + ObjectField;放置调用传 sprite。

验证

  • 编译 0 错误。
  • 反射/直接调用各 PlaceExxx 生成到临时场景对象,校验:
    • 主体/HurtBox/ContactDamageZone 都是 BoxCollider2D
    • HurtBox/ContactDamageZone isTrigger=true,主体 isTrigger=false
    • 三者 size 一致(= sprite 包围盒 或 fallback)。
    • 三者底部对齐(offset.y == size.y * 0.5)。
    • 每个敌人 + Boss 均存在 ContactDamageZoneBodyContactDamage 组件在)。
    • E001BodyContactDamage 初始 disabled 且已绑到 ContactChaseAbility。
  • 用一张测试 Sprite 走一次向导放置,确认碰撞体尺寸 = sprite 包围盒、SpriteRenderer 用了该 sprite。

非目标

  • 不改现有 prefab(用户手动重生成)。
  • 不改敌人 AI / 移动 / 伤害逻辑。
  • 不引入 sprite 紧密网格/物理形状(用完整包围盒即可)。