feat: Add HurtBoxOwnerGuard to prevent multiple damage registrations from the same HitBox activation
- Implemented HurtBoxOwnerGuard to ensure that multiple HurtBoxes on the same character do not register damage multiple times during a single HitBox activation. - Added custom editor for HitBox to facilitate the creation of shape colliders with HitBoxColliderProxy. - Introduced PhysicsPerceptionSystem for enemy perception, supporting multiple detection modes including RangeCircle, BatchLOS, FanCast, and BoxCast. - Created EnemyPatrolZone to define patrol and chase areas for enemies, allowing for shared zones among multiple enemies. - Added BD_IsOutsideZone conditional task for Behavior Designer to check if an enemy or player is outside a defined patrol zone.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Animancer;
|
||||
using BaseGames.Boss;
|
||||
@@ -36,6 +36,9 @@ namespace BaseGames.Editor
|
||||
/// </summary>
|
||||
public static class SceneObjectPlacerTool
|
||||
{
|
||||
// ── 碰撞器类型 ────────────────────────────────────────────────────────
|
||||
public enum EnemyBodyColliderType { Box, Capsule, Circle }
|
||||
|
||||
// ══ 菜单入口 ══════════════════════════════════════════════════════════
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Player", priority = 100)]
|
||||
@@ -246,7 +249,9 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Enemy (Basic)", priority = 110)]
|
||||
public static void PlaceEnemy()
|
||||
public static void PlaceEnemy() => PlaceEnemy(EnemyBodyColliderType.Box);
|
||||
|
||||
public static void PlaceEnemy(EnemyBodyColliderType bodyCollider)
|
||||
{
|
||||
var report = new List<string>();
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
@@ -264,10 +269,12 @@ namespace BaseGames.Editor
|
||||
rb.constraints = RigidbodyConstraints2D.FreezeRotation;
|
||||
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
|
||||
|
||||
GetOrAddComponent<CapsuleCollider2D>(go);
|
||||
GetOrAddComponent<Animator>(go);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(go);
|
||||
SpriteRenderer sr = SetupSpriteRenderer(go);
|
||||
Collider2D body = CreateBodyCollider(go, bodyCollider, new Vector2(0.7f, 0.9f));
|
||||
Transform visual = GetOrCreateChild(go.transform, "Visual");
|
||||
visual.localPosition = (Vector3)(Vector2)body.offset;
|
||||
GetOrAddComponent<Animator>(visual.gameObject);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer sr = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
EnemyBase enemyBase = GetOrAddComponent<EnemyBase>(go);
|
||||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(go);
|
||||
@@ -275,7 +282,7 @@ namespace BaseGames.Editor
|
||||
GetOrAddComponent<EnemyNavAgent>(go);
|
||||
GetOrAddComponent<NavAgent>(go);
|
||||
GetOrAddComponent<TransformBasedMovement>(go); // required by EnemyNavAgent [RequireComponent]
|
||||
GetOrAddComponent<EnemySensorHub>(go);
|
||||
PhysicsPerceptionSystem sensorHub = GetOrAddComponent<PhysicsPerceptionSystem>(go);
|
||||
|
||||
// HurtBox child
|
||||
Transform hurtBoxT = GetOrCreateChild(go.transform, "HurtBox");
|
||||
@@ -306,6 +313,7 @@ namespace BaseGames.Editor
|
||||
AssignAsset(hurtBox, "_onHitConfirmed", report, false, "EVT_HitConfirmed");
|
||||
|
||||
// Wire EnemyMovement
|
||||
AssignReference(movement, "_visualRoot", visual, report);
|
||||
AssignReference(movement, "_animancer", animancer, report);
|
||||
AssignReference(movement, "_spriteRenderer", sr, report);
|
||||
AssignLayerMask(movement, "_groundMask",
|
||||
@@ -319,6 +327,7 @@ namespace BaseGames.Editor
|
||||
else
|
||||
report.Add("未找到 DamageSourceSO,HitBox_Body._defaultSource 未绑定。请创建 CMB_DS_EnemyBody.asset。");
|
||||
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "los" }, report);
|
||||
report.Add("★ 指定 EnemyBase._statsSO、_animConfig 资产(按所创建的敌人类型命名)。");
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定对应 .asset。");
|
||||
|
||||
@@ -329,7 +338,9 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Boss Enemy", priority = 115)]
|
||||
public static void PlaceBossEnemy()
|
||||
public static void PlaceBossEnemy() => PlaceBossEnemy(EnemyBodyColliderType.Box);
|
||||
|
||||
public static void PlaceBossEnemy(EnemyBodyColliderType bodyCollider)
|
||||
{
|
||||
var report = new List<string>();
|
||||
|
||||
@@ -345,12 +356,13 @@ namespace BaseGames.Editor
|
||||
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
|
||||
rb.interpolation = RigidbodyInterpolation2D.Interpolate;
|
||||
|
||||
GetOrAddComponent<CapsuleCollider2D>(go);
|
||||
CreateBodyCollider(go, bodyCollider, new Vector2(1.5f, 2.5f));
|
||||
GetOrAddComponent<Animator>(go);
|
||||
SetupSpriteRenderer(go);
|
||||
|
||||
BossBase bossBase = GetOrAddComponent<BossBase>(go);
|
||||
EnemyStats bossStats = GetOrAddComponent<EnemyStats>(go);
|
||||
PhysicsPerceptionSystem sensorHub = GetOrAddComponent<PhysicsPerceptionSystem>(go);
|
||||
|
||||
// HurtBox child
|
||||
Transform hurtBoxT = GetOrCreateChild(go.transform, "HurtBox");
|
||||
@@ -388,6 +400,7 @@ namespace BaseGames.Editor
|
||||
AssignAsset(hurtBox, "_onDamageDealt", report, false, "EVT_DamageDealt");
|
||||
AssignAsset(hurtBox, "_onHitConfirmed", report, false, "EVT_HitConfirmed");
|
||||
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "attack_melee", "attack_range", "los" }, report);
|
||||
report.Add("填写 _bossId。");
|
||||
report.Add("挂载 BossSkillSequencer 组件并指定技能序列 SO;行为树、NavAgent 需手工添加。");
|
||||
report.Add("多阶段 Boss 可在此 GameObject 上继续 AddComponent 阶段切换控制器。");
|
||||
@@ -399,7 +412,9 @@ namespace BaseGames.Editor
|
||||
// ══ 具体敌人快速放置 ════════════════════════════════════════════════════
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Enemy E001 (草蛭)", priority = 111)]
|
||||
public static void PlaceE001_CaoZhi()
|
||||
public static void PlaceE001_CaoZhi() => PlaceE001_CaoZhi(EnemyBodyColliderType.Box);
|
||||
|
||||
public static void PlaceE001_CaoZhi(EnemyBodyColliderType bodyCollider)
|
||||
{
|
||||
var report = new List<string>();
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
@@ -416,12 +431,12 @@ namespace BaseGames.Editor
|
||||
rb.constraints = RigidbodyConstraints2D.FreezeRotation;
|
||||
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
|
||||
|
||||
CapsuleCollider2D body = GetOrAddComponent<CapsuleCollider2D>(go);
|
||||
body.size = new Vector2(0.6f, 0.8f);
|
||||
|
||||
GetOrAddComponent<Animator>(go);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(go);
|
||||
SpriteRenderer sr1 = SetupSpriteRenderer(go);
|
||||
Collider2D body = CreateBodyCollider(go, bodyCollider, new Vector2(0.6f, 0.8f));
|
||||
Transform visual = GetOrCreateChild(go.transform, "Visual");
|
||||
visual.localPosition = (Vector3)(Vector2)body.offset;
|
||||
GetOrAddComponent<Animator>(visual.gameObject);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer sr1 = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
EnemyBase enemyBase = GetOrAddComponent<EnemyBase>(go);
|
||||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(go);
|
||||
@@ -429,7 +444,7 @@ namespace BaseGames.Editor
|
||||
GetOrAddComponent<EnemyNavAgent>(go);
|
||||
GetOrAddComponent<NavAgent>(go);
|
||||
GetOrAddComponent<TransformBasedMovement>(go); // required by EnemyNavAgent [RequireComponent]
|
||||
EnemySensorHub sensorHub = GetOrAddComponent<EnemySensorHub>(go);
|
||||
PhysicsPerceptionSystem sensorHub = GetOrAddComponent<PhysicsPerceptionSystem>(go);
|
||||
|
||||
Transform hurtBoxT = GetOrCreateChild(go.transform, "HurtBox");
|
||||
SetLayer(hurtBoxT.gameObject, "EnemyHurtBox", report);
|
||||
@@ -469,6 +484,7 @@ namespace BaseGames.Editor
|
||||
|
||||
AssignAsset(movement, "_config", report, false, "ENM_E001_Stats");
|
||||
AssignAsset(movement, "_animConfig", report, false, "ENM_E001_AnimConfig");
|
||||
AssignReference(movement, "_visualRoot", visual, report);
|
||||
AssignReference(movement, "_animancer", animancer, report);
|
||||
AssignReference(movement, "_spriteRenderer", sr1, report);
|
||||
AssignLayerMask(movement, "_groundMask",
|
||||
@@ -478,13 +494,11 @@ namespace BaseGames.Editor
|
||||
AssignAsset(alertAbility, "_config", report, false, "ABL_E001_Alert");
|
||||
AssignAsset(chaseAbility, "_config", report, false, "ABL_E001_Chase");
|
||||
AssignReference(chaseAbility, "_contactDamage", bodyContact, report);
|
||||
AssignReference(chaseAbility, "_sensorHub", sensorHub, report);
|
||||
|
||||
Object dmgSrc = FindFirstAsset("CMB_DS_EnemyBody");
|
||||
if (dmgSrc != null) AssignReference(contactHitBox, "_defaultSource", dmgSrc, report);
|
||||
|
||||
SetupSensorHubSlotNames(sensorHub, new[] { "aggro", "wall_ahead", "ledge" }, report);
|
||||
report.Add("★ 在 EnemySensorHub Inspector 中绑定 Sensor 子节点(aggro/wall_ahead/ledge)。");
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "los" }, report);
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定 E001_CaoZhi.asset。");
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
@@ -494,7 +508,9 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Enemy E002 (簧蛭)", priority = 112)]
|
||||
public static void PlaceE002_HuangZhi()
|
||||
public static void PlaceE002_HuangZhi() => PlaceE002_HuangZhi(EnemyBodyColliderType.Box);
|
||||
|
||||
public static void PlaceE002_HuangZhi(EnemyBodyColliderType bodyCollider)
|
||||
{
|
||||
var report = new List<string>();
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
@@ -509,18 +525,24 @@ namespace BaseGames.Editor
|
||||
rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
rb.gravityScale = 0f;
|
||||
|
||||
CapsuleCollider2D body = GetOrAddComponent<CapsuleCollider2D>(go);
|
||||
body.size = new Vector2(0.5f, 0.7f);
|
||||
Collider2D body = CreateBodyCollider(go, bodyCollider, new Vector2(0.5f, 0.7f));
|
||||
|
||||
GetOrAddComponent<Animator>(go);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(go);
|
||||
SetupSpriteRenderer(go);
|
||||
// Visual 子节点:挂载精灵 / 动画(EnemyMovement 翻转时操作此节点)
|
||||
Transform visual = GetOrCreateChild(go.transform, "Visual");
|
||||
visual.localPosition = (Vector3)(Vector2)body.offset;
|
||||
GetOrAddComponent<Animator>(visual.gameObject);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer sr = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
EnemyBase enemyBase = GetOrAddComponent<EnemyBase>(go);
|
||||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(go);
|
||||
EnemySensorHub sensorHub = GetOrAddComponent<EnemySensorHub>(go);
|
||||
EnemyMovement movement = GetOrAddComponent<EnemyMovement>(go);
|
||||
GetOrAddComponent<EnemyNavAgent>(go);
|
||||
GetOrAddComponent<NavAgent>(go);
|
||||
GetOrAddComponent<TransformBasedMovement>(go); // required by EnemyNavAgent [RequireComponent]
|
||||
PhysicsPerceptionSystem sensorHub = GetOrAddComponent<PhysicsPerceptionSystem>(go);
|
||||
|
||||
// HurtBox(初始禁用,附着天花板时不受伤)
|
||||
// HurtBox(初始禁用,悬挂阶段无法被攻击)
|
||||
Transform hurtBoxT = GetOrCreateChild(go.transform, "HurtBox");
|
||||
SetLayer(hurtBoxT.gameObject, "EnemyHurtBox", report);
|
||||
CapsuleCollider2D hurtCap = GetOrAddComponent<CapsuleCollider2D>(hurtBoxT.gameObject);
|
||||
@@ -529,24 +551,35 @@ namespace BaseGames.Editor
|
||||
HurtBox hurtBox = GetOrAddComponent<HurtBox>(hurtBoxT.gameObject);
|
||||
hurtBoxT.gameObject.SetActive(false);
|
||||
|
||||
// AttackHitBox(下坠发动时由能力启用)
|
||||
Transform atkT = GetOrCreateChild(go.transform, "AttackHitBox");
|
||||
SetLayer(atkT.gameObject, "EnemyHitBox", report);
|
||||
BoxCollider2D atkCol = GetOrAddComponent<BoxCollider2D>(atkT.gameObject);
|
||||
atkCol.isTrigger = true;
|
||||
atkCol.size = new Vector2(0.5f, 0.5f);
|
||||
HitBox atkHitBox = GetOrAddComponent<HitBox>(atkT.gameObject);
|
||||
atkT.gameObject.SetActive(false);
|
||||
// LandingHitBox(落地瞬间 AoE,由 CeilingDropAbility 激活)
|
||||
Transform landingHitBoxT = GetOrCreateChild(go.transform, "LandingHitBox");
|
||||
SetLayer(landingHitBoxT.gameObject, "EnemyHitBox", report);
|
||||
BoxCollider2D landingCol = GetOrAddComponent<BoxCollider2D>(landingHitBoxT.gameObject);
|
||||
landingCol.isTrigger = true;
|
||||
landingCol.size = new Vector2(0.8f, 0.3f);
|
||||
HitBox landingHitBox = GetOrAddComponent<HitBox>(landingHitBoxT.gameObject);
|
||||
landingHitBoxT.gameObject.SetActive(false);
|
||||
|
||||
// ContactDamageZone(地面巡逻时造成接触伤害,落地后由行为树启用)
|
||||
Transform contactT = GetOrCreateChild(go.transform, "ContactDamageZone");
|
||||
SetLayer(contactT.gameObject, "EnemyHitBox", report);
|
||||
CircleCollider2D contactCol = GetOrAddComponent<CircleCollider2D>(contactT.gameObject);
|
||||
contactCol.isTrigger = true;
|
||||
contactCol.radius = 0.35f;
|
||||
HitBox contactHitBox = GetOrAddComponent<HitBox>(contactT.gameObject);
|
||||
GetOrAddComponent<BodyContactDamage>(contactT.gameObject);
|
||||
contactT.gameObject.SetActive(false);
|
||||
|
||||
Transform abilitiesT = GetOrCreateChild(go.transform, "Abilities");
|
||||
Transform strikeT = GetOrCreateChild(abilitiesT, "CeilingHangStrikeAbility");
|
||||
CeilingHangStrikeAbility strikeAbility = GetOrAddComponent<CeilingHangStrikeAbility>(strikeT.gameObject);
|
||||
Transform dropT = GetOrCreateChild(abilitiesT, "CeilingDropAbility");
|
||||
CeilingDropAbility dropAbility = GetOrAddComponent<CeilingDropAbility>(dropT.gameObject);
|
||||
|
||||
// SOs — assign first so OnValidate doesn't warn during wiring
|
||||
AssignAsset(enemyBase, "_statsSO", report, false, "ENM_E002_Stats");
|
||||
AssignAsset(enemyBase, "_animConfig", report, false, "ENM_E002_AnimConfig");
|
||||
AssignAsset(enemyBase, "_statsSO", report, false, "ENM_E002_Stats");
|
||||
AssignAsset(enemyBase, "_animConfig", report, false, "ENM_E002_AnimConfig");
|
||||
|
||||
AssignReference(enemyBase, "_stats", enemyStats, report);
|
||||
AssignReference(enemyBase, "_movement", movement, report);
|
||||
AssignReference(enemyBase, "_animancer", animancer, report);
|
||||
AssignReference(enemyBase, "_hurtBox", hurtBox, report);
|
||||
AssignAsset(enemyBase, "_onEnemyDied", report, false, "EVT_EnemyDied");
|
||||
@@ -555,16 +588,32 @@ namespace BaseGames.Editor
|
||||
AssignAsset(hurtBox, "_onDamageDealt", report, false, "EVT_DamageDealt");
|
||||
AssignAsset(hurtBox, "_onHitConfirmed", report, false, "EVT_HitConfirmed");
|
||||
|
||||
AssignAsset(strikeAbility, "_config", report, false, "ABL_E002_Strike");
|
||||
AssignReference(strikeAbility, "_attackHitBox", atkHitBox, report);
|
||||
AssignReference(strikeAbility, "_hurtBox", hurtBox, report);
|
||||
AssignAsset(movement, "_config", report, false, "ENM_E002_Stats");
|
||||
AssignAsset(movement, "_animConfig", report, false, "ENM_E002_AnimConfig");
|
||||
AssignReference(movement, "_visualRoot", visual, report);
|
||||
AssignReference(movement, "_animancer", animancer, report);
|
||||
AssignReference(movement, "_spriteRenderer", sr, report);
|
||||
AssignLayerMask(movement, "_groundMask",
|
||||
new[] { "Platform", "OneWayPlatform", "MovingOneWayPlatform", "MidHeightOneWayPlatform" },
|
||||
report);
|
||||
|
||||
AssignReference(dropAbility, "_landingHitBox", landingHitBox, report);
|
||||
AssignLayerMask(dropAbility, "_groundMask",
|
||||
new[] { "Platform", "OneWayPlatform", "MovingOneWayPlatform", "MidHeightOneWayPlatform" },
|
||||
report);
|
||||
|
||||
Object dmgSrc = FindFirstAsset("CMB_DS_EnemyBody");
|
||||
if (dmgSrc != null) AssignReference(atkHitBox, "_defaultSource", dmgSrc, report);
|
||||
if (dmgSrc != null)
|
||||
{
|
||||
AssignReference(landingHitBox, "_defaultSource", dmgSrc, report);
|
||||
AssignReference(contactHitBox, "_defaultSource", dmgSrc, report);
|
||||
}
|
||||
|
||||
SetupSensorHubSlotNames(sensorHub, new[] { "attack_range" }, report);
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "attack_range" }, report);
|
||||
report.Add("★ 将此对象放置于天花板,调整位置使 CapsuleCollider 正好贴合天花板底面。");
|
||||
report.Add("★ HurtBox / ContactDamageZone 初始禁用;落地后由行为树激活。");
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定 E002_HuangZhi.asset。");
|
||||
report.Add("★ BD 树逻辑建议:Idle(悬挂)→ IsSensorDetecting(aggro) → UseAbility(CeilingDrop) → IsGrounded → Patrol(Pace)。");
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
Selection.activeGameObject = go;
|
||||
@@ -573,7 +622,9 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Enemy E003 (幼蛭)", priority = 113)]
|
||||
public static void PlaceE003_YouZhi_Enemy()
|
||||
public static void PlaceE003_YouZhi_Enemy() => PlaceE003_YouZhi_Enemy(EnemyBodyColliderType.Box);
|
||||
|
||||
public static void PlaceE003_YouZhi_Enemy(EnemyBodyColliderType bodyCollider)
|
||||
{
|
||||
var report = new List<string>();
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
@@ -588,12 +639,12 @@ namespace BaseGames.Editor
|
||||
rb.bodyType = RigidbodyType2D.Kinematic;
|
||||
rb.gravityScale = 0f;
|
||||
|
||||
CapsuleCollider2D body = GetOrAddComponent<CapsuleCollider2D>(go);
|
||||
body.size = new Vector2(0.5f, 0.6f);
|
||||
|
||||
GetOrAddComponent<Animator>(go);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(go);
|
||||
SpriteRenderer sr3 = SetupSpriteRenderer(go);
|
||||
Collider2D body = CreateBodyCollider(go, bodyCollider, new Vector2(0.5f, 0.6f));
|
||||
Transform visual = GetOrCreateChild(go.transform, "Visual");
|
||||
visual.localPosition = (Vector3)(Vector2)body.offset;
|
||||
GetOrAddComponent<Animator>(visual.gameObject);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer sr3 = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
E003_YouZhi enemyBase = GetOrAddComponent<E003_YouZhi>(go);
|
||||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(go);
|
||||
@@ -601,7 +652,7 @@ namespace BaseGames.Editor
|
||||
GetOrAddComponent<EnemyNavAgent>(go);
|
||||
GetOrAddComponent<NavAgent>(go);
|
||||
GetOrAddComponent<TransformBasedMovement>(go); // required by EnemyNavAgent [RequireComponent]
|
||||
EnemySensorHub sensorHub = GetOrAddComponent<EnemySensorHub>(go);
|
||||
PhysicsPerceptionSystem sensorHub = GetOrAddComponent<PhysicsPerceptionSystem>(go);
|
||||
|
||||
Transform hurtBoxT = GetOrCreateChild(go.transform, "HurtBox");
|
||||
SetLayer(hurtBoxT.gameObject, "EnemyHurtBox", report);
|
||||
@@ -638,6 +689,7 @@ namespace BaseGames.Editor
|
||||
|
||||
AssignAsset(movement, "_config", report, false, "ENM_E003_Stats");
|
||||
AssignAsset(movement, "_animConfig", report, false, "ENM_E003_AnimConfig");
|
||||
AssignReference(movement, "_visualRoot", visual, report);
|
||||
AssignReference(movement, "_animancer", animancer, report);
|
||||
AssignReference(movement, "_spriteRenderer", sr3, report);
|
||||
AssignLayerMask(movement, "_groundMask",
|
||||
@@ -650,7 +702,7 @@ namespace BaseGames.Editor
|
||||
Object dmgSrc = FindFirstAsset("CMB_DS_EnemyBody");
|
||||
if (dmgSrc != null) AssignReference(contactHitBox, "_defaultSource", dmgSrc, report);
|
||||
|
||||
SetupSensorHubSlotNames(sensorHub, new[] { "aggro" }, report);
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "los" }, report);
|
||||
report.Add("★ 将此对象放置于天花板下方,E003_YouZhi 会在 OnSpawn/ActivateFromCeiling 时执行下坠。");
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定 E003_YouZhi.asset。");
|
||||
|
||||
@@ -661,7 +713,9 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Enemy E004 (蛭母)", priority = 114)]
|
||||
public static void PlaceE004_ZhiMu_Enemy()
|
||||
public static void PlaceE004_ZhiMu_Enemy() => PlaceE004_ZhiMu_Enemy(EnemyBodyColliderType.Box);
|
||||
|
||||
public static void PlaceE004_ZhiMu_Enemy(EnemyBodyColliderType bodyCollider)
|
||||
{
|
||||
var report = new List<string>();
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
@@ -678,12 +732,12 @@ namespace BaseGames.Editor
|
||||
rb.constraints = RigidbodyConstraints2D.FreezeRotation;
|
||||
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
|
||||
|
||||
CapsuleCollider2D body = GetOrAddComponent<CapsuleCollider2D>(go);
|
||||
body.size = new Vector2(0.8f, 1.2f);
|
||||
|
||||
GetOrAddComponent<Animator>(go);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(go);
|
||||
SpriteRenderer sr4 = SetupSpriteRenderer(go);
|
||||
Collider2D body = CreateBodyCollider(go, bodyCollider, new Vector2(0.8f, 1.2f));
|
||||
Transform visual = GetOrCreateChild(go.transform, "Visual");
|
||||
visual.localPosition = (Vector3)(Vector2)body.offset;
|
||||
GetOrAddComponent<Animator>(visual.gameObject);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer sr4 = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
E004_ZhiMu enemyBase = GetOrAddComponent<E004_ZhiMu>(go);
|
||||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(go);
|
||||
@@ -692,7 +746,7 @@ namespace BaseGames.Editor
|
||||
GetOrAddComponent<EnemyNavAgent>(go);
|
||||
GetOrAddComponent<NavAgent>(go);
|
||||
GetOrAddComponent<TransformBasedMovement>(go); // required by EnemyNavAgent [RequireComponent]
|
||||
EnemySensorHub sensorHub = GetOrAddComponent<EnemySensorHub>(go);
|
||||
PhysicsPerceptionSystem sensorHub = GetOrAddComponent<PhysicsPerceptionSystem>(go);
|
||||
|
||||
Transform hurtBoxT = GetOrCreateChild(go.transform, "HurtBox");
|
||||
SetLayer(hurtBoxT.gameObject, "EnemyHurtBox", report);
|
||||
@@ -756,6 +810,7 @@ namespace BaseGames.Editor
|
||||
|
||||
AssignAsset(movement, "_config", report, false, "ENM_E004_Stats");
|
||||
AssignAsset(movement, "_animConfig", report, false, "ENM_E004_AnimConfig");
|
||||
AssignReference(movement, "_visualRoot", visual, report);
|
||||
AssignReference(movement, "_animancer", animancer, report);
|
||||
AssignReference(movement, "_spriteRenderer", sr4, report);
|
||||
AssignLayerMask(movement, "_groundMask",
|
||||
@@ -772,7 +827,6 @@ namespace BaseGames.Editor
|
||||
AssignReference(slamAbl, "_hitBox", slamHitBox, report);
|
||||
AssignReference(acidAbl, "_muzzle", acidMuzzleT, report);
|
||||
AssignReference(chargeAbl, "_chargeHitBox", chargeHitBox, report);
|
||||
AssignReference(chaseAbl, "_sensorHub", sensorHub, report);
|
||||
|
||||
Object dmgSrc = FindFirstAsset("CMB_DS_EnemyBody");
|
||||
if (dmgSrc != null)
|
||||
@@ -782,7 +836,7 @@ namespace BaseGames.Editor
|
||||
AssignReference(chargeHitBox, "_defaultSource", dmgSrc, report);
|
||||
}
|
||||
|
||||
SetupSensorHubSlotNames(sensorHub, new[] { "aggro", "los" }, report);
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "attack_melee", "attack_range", "los" }, report);
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定 E004_ZhiMu.asset。");
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
@@ -792,7 +846,9 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Enemy E005 (肥蛭)", priority = 115)]
|
||||
public static void PlaceE005_FeiZhi_Enemy()
|
||||
public static void PlaceE005_FeiZhi_Enemy() => PlaceE005_FeiZhi_Enemy(EnemyBodyColliderType.Box);
|
||||
|
||||
public static void PlaceE005_FeiZhi_Enemy(EnemyBodyColliderType bodyCollider)
|
||||
{
|
||||
var report = new List<string>();
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
@@ -809,12 +865,12 @@ namespace BaseGames.Editor
|
||||
rb.constraints = RigidbodyConstraints2D.FreezeRotation;
|
||||
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
|
||||
|
||||
CapsuleCollider2D body = GetOrAddComponent<CapsuleCollider2D>(go);
|
||||
body.size = new Vector2(0.9f, 1.0f);
|
||||
|
||||
GetOrAddComponent<Animator>(go);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(go);
|
||||
SpriteRenderer sr5 = SetupSpriteRenderer(go);
|
||||
Collider2D body = CreateBodyCollider(go, bodyCollider, new Vector2(0.9f, 1.0f));
|
||||
Transform visual = GetOrCreateChild(go.transform, "Visual");
|
||||
visual.localPosition = (Vector3)(Vector2)body.offset;
|
||||
GetOrAddComponent<Animator>(visual.gameObject);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer sr5 = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
E005_FeiZhi enemyBase = GetOrAddComponent<E005_FeiZhi>(go);
|
||||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(go);
|
||||
@@ -822,7 +878,7 @@ namespace BaseGames.Editor
|
||||
GetOrAddComponent<EnemyNavAgent>(go);
|
||||
GetOrAddComponent<NavAgent>(go);
|
||||
GetOrAddComponent<TransformBasedMovement>(go); // required by EnemyNavAgent [RequireComponent]
|
||||
EnemySensorHub sensorHub = GetOrAddComponent<EnemySensorHub>(go);
|
||||
PhysicsPerceptionSystem sensorHub = GetOrAddComponent<PhysicsPerceptionSystem>(go);
|
||||
|
||||
Transform hurtBoxT = GetOrCreateChild(go.transform, "HurtBox");
|
||||
SetLayer(hurtBoxT.gameObject, "EnemyHurtBox", report);
|
||||
@@ -863,6 +919,7 @@ namespace BaseGames.Editor
|
||||
|
||||
AssignAsset(movement, "_config", report, false, "ENM_E005_Stats");
|
||||
AssignAsset(movement, "_animConfig", report, false, "ENM_E005_AnimConfig");
|
||||
AssignReference(movement, "_visualRoot", visual, report);
|
||||
AssignReference(movement, "_animancer", animancer, report);
|
||||
AssignReference(movement, "_spriteRenderer", sr5, report);
|
||||
AssignLayerMask(movement, "_groundMask",
|
||||
@@ -878,7 +935,7 @@ namespace BaseGames.Editor
|
||||
Object dmgSrc = FindFirstAsset("CMB_DS_EnemyBody");
|
||||
if (dmgSrc != null) AssignReference(biteHitBox, "_defaultSource", dmgSrc, report);
|
||||
|
||||
SetupSensorHubSlotNames(sensorHub, new[] { "aggro" }, report);
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "attack_melee", "attack_range", "los" }, report);
|
||||
report.Add("★ 在 E005_FeiZhi._deathPreClip 上添加 AnimationEvent 调用 SpawnProjectile(\"spawn_e003\")。");
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定 E005_FeiZhi.asset。");
|
||||
|
||||
@@ -889,7 +946,9 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Enemy E006 (讙)", priority = 116)]
|
||||
public static void PlaceE006_Huan()
|
||||
public static void PlaceE006_Huan() => PlaceE006_Huan(EnemyBodyColliderType.Box);
|
||||
|
||||
public static void PlaceE006_Huan(EnemyBodyColliderType bodyCollider)
|
||||
{
|
||||
var report = new List<string>();
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
@@ -906,12 +965,12 @@ namespace BaseGames.Editor
|
||||
rb.constraints = RigidbodyConstraints2D.FreezeRotation;
|
||||
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
|
||||
|
||||
CapsuleCollider2D body = GetOrAddComponent<CapsuleCollider2D>(go);
|
||||
body.size = new Vector2(0.7f, 1.0f);
|
||||
|
||||
GetOrAddComponent<Animator>(go);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(go);
|
||||
SpriteRenderer sr6 = SetupSpriteRenderer(go);
|
||||
Collider2D body = CreateBodyCollider(go, bodyCollider, new Vector2(0.7f, 1.0f));
|
||||
Transform visual = GetOrCreateChild(go.transform, "Visual");
|
||||
visual.localPosition = (Vector3)(Vector2)body.offset;
|
||||
GetOrAddComponent<Animator>(visual.gameObject);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer sr6 = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
EnemyBase enemyBase = GetOrAddComponent<EnemyBase>(go);
|
||||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(go);
|
||||
@@ -919,7 +978,7 @@ namespace BaseGames.Editor
|
||||
GetOrAddComponent<EnemyNavAgent>(go);
|
||||
GetOrAddComponent<NavAgent>(go);
|
||||
GetOrAddComponent<TransformBasedMovement>(go); // required by EnemyNavAgent [RequireComponent]
|
||||
EnemySensorHub sensorHub = GetOrAddComponent<EnemySensorHub>(go);
|
||||
PhysicsPerceptionSystem sensorHub = GetOrAddComponent<PhysicsPerceptionSystem>(go);
|
||||
|
||||
Transform hurtBoxT = GetOrCreateChild(go.transform, "HurtBox");
|
||||
SetLayer(hurtBoxT.gameObject, "EnemyHurtBox", report);
|
||||
@@ -966,6 +1025,7 @@ namespace BaseGames.Editor
|
||||
|
||||
AssignAsset(movement, "_config", report, false, "ENM_E006_Stats");
|
||||
AssignAsset(movement, "_animConfig", report, false, "ENM_E006_AnimConfig");
|
||||
AssignReference(movement, "_visualRoot", visual, report);
|
||||
AssignReference(movement, "_animancer", animancer, report);
|
||||
AssignReference(movement, "_spriteRenderer", sr6, report);
|
||||
AssignLayerMask(movement, "_groundMask",
|
||||
@@ -977,7 +1037,6 @@ namespace BaseGames.Editor
|
||||
|
||||
AssignReference(leapAbl, "_landingHitBox", landHitBox, report);
|
||||
AssignReference(chaseAbl, "_contactDamage", bodyContact, report);
|
||||
AssignReference(chaseAbl, "_sensorHub", sensorHub, report);
|
||||
|
||||
Object dmgSrc = FindFirstAsset("CMB_DS_EnemyBody");
|
||||
if (dmgSrc != null)
|
||||
@@ -986,7 +1045,7 @@ namespace BaseGames.Editor
|
||||
AssignReference(landHitBox, "_defaultSource", dmgSrc, report);
|
||||
}
|
||||
|
||||
SetupSensorHubSlotNames(sensorHub, new[] { "aggro", "wall_ahead", "ledge" }, report);
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "los" }, report);
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定 E006_Huan.asset。");
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
@@ -996,7 +1055,9 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
[MenuItem("BaseGames/Scene/Place/Boss 嘲风 (ChaoFeng)", priority = 117)]
|
||||
public static void PlaceChaoFeng()
|
||||
public static void PlaceChaoFeng() => PlaceChaoFeng(EnemyBodyColliderType.Box);
|
||||
|
||||
public static void PlaceChaoFeng(EnemyBodyColliderType bodyCollider)
|
||||
{
|
||||
var report = new List<string>();
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
@@ -1014,12 +1075,12 @@ namespace BaseGames.Editor
|
||||
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
|
||||
rb.interpolation = RigidbodyInterpolation2D.Interpolate;
|
||||
|
||||
CapsuleCollider2D body = GetOrAddComponent<CapsuleCollider2D>(go);
|
||||
body.size = new Vector2(1.2f, 2.0f);
|
||||
|
||||
GetOrAddComponent<Animator>(go);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(go);
|
||||
SpriteRenderer srBoss = SetupSpriteRenderer(go);
|
||||
Collider2D body = CreateBodyCollider(go, bodyCollider, new Vector2(1.2f, 2.0f));
|
||||
Transform visual = GetOrCreateChild(go.transform, "Visual");
|
||||
visual.localPosition = (Vector3)(Vector2)body.offset;
|
||||
GetOrAddComponent<Animator>(visual.gameObject);
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer srBoss = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
ChaoFengBoss bossBase = GetOrAddComponent<ChaoFengBoss>(go);
|
||||
EnemyStats bossStats = GetOrAddComponent<EnemyStats>(go);
|
||||
@@ -1031,7 +1092,7 @@ namespace BaseGames.Editor
|
||||
BossSkillExecutor skillExec = GetOrAddComponent<BossSkillExecutor>(go);
|
||||
ChaoFengFloatController floatCtrl = GetOrAddComponent<ChaoFengFloatController>(go);
|
||||
ChaoFengKnockdownCounter knockdown = GetOrAddComponent<ChaoFengKnockdownCounter>(go);
|
||||
EnemySensorHub sensorHub = GetOrAddComponent<EnemySensorHub>(go);
|
||||
PhysicsPerceptionSystem sensorHub = GetOrAddComponent<PhysicsPerceptionSystem>(go);
|
||||
|
||||
// HurtBox
|
||||
Transform hurtBoxT = GetOrCreateChild(go.transform, "HurtBox");
|
||||
@@ -1078,6 +1139,7 @@ namespace BaseGames.Editor
|
||||
|
||||
AssignAsset(movement, "_config", report, false, "ENM_ChaoFeng_Stats");
|
||||
AssignAsset(movement, "_animConfig", report, false, "ENM_ChaoFeng_AnimConfig");
|
||||
AssignReference(movement, "_visualRoot", visual, report);
|
||||
AssignReference(movement, "_animancer", animancer, report);
|
||||
AssignReference(movement, "_spriteRenderer", srBoss, report);
|
||||
AssignLayerMask(movement, "_groundMask",
|
||||
@@ -1103,7 +1165,7 @@ namespace BaseGames.Editor
|
||||
if (hb != null) AssignReference(hb, "_defaultSource", dmgSrc, report);
|
||||
}
|
||||
|
||||
SetupSensorHubSlotNames(sensorHub, new[] { "aggro", "attack_melee", "attack_range", "los" }, report);
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "attack_melee", "attack_range", "los" }, report);
|
||||
|
||||
report.Add("★ 设置 BossSkillExecutor._bossId = \"ChaoFeng\"。");
|
||||
report.Add("★ 将各 Phase1 HitBox 引用拖入 BossSkillExecutor._hitBoxes 数组。");
|
||||
@@ -1145,20 +1207,56 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置 EnemySensorHub._slots 的 slotName 字段(Sensor 引用需在 Inspector 中手工绑定)。
|
||||
/// 在 <see cref="PhysicsPerceptionSystem"/> 上预填充 <c>_slots</c> 数组,
|
||||
/// 根据 slotName 自动选择类型、半径、检测层及 GizmoColor。
|
||||
/// </summary>
|
||||
private static void SetupSensorHubSlotNames(EnemySensorHub hub, string[] slotNames, List<string> report)
|
||||
private static void SetupPerceptionSystemSlots(PhysicsPerceptionSystem system, string[] slotNames, List<string> report)
|
||||
{
|
||||
var so = new SerializedObject(hub);
|
||||
var so = new SerializedObject(system);
|
||||
var slots = so.FindProperty("_slots");
|
||||
if (slots == null || !slots.isArray)
|
||||
{
|
||||
report?.Add("EnemySensorHub._slots 字段未找到,传感器槽位需手工配置。");
|
||||
report?.Add("PhysicsPerceptionSystem._slots 字段未找到,请检查脚本序列化。");
|
||||
return;
|
||||
}
|
||||
|
||||
int playerLayer = LayerMask.GetMask("Player");
|
||||
|
||||
slots.arraySize = slotNames.Length;
|
||||
for (int i = 0; i < slotNames.Length; i++)
|
||||
slots.GetArrayElementAtIndex(i).FindPropertyRelative("slotName").stringValue = slotNames[i];
|
||||
{
|
||||
var elem = slots.GetArrayElementAtIndex(i);
|
||||
string name = slotNames[i];
|
||||
|
||||
elem.FindPropertyRelative("slotName").stringValue = name;
|
||||
|
||||
int enumIdx = 0; // RangeCircle
|
||||
float radius = 3f;
|
||||
int layer = playerLayer;
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case "aggro": enumIdx = 0; radius = 5f; layer = playerLayer; break;
|
||||
case "los": enumIdx = 1; radius = 0f; layer = 0; break;
|
||||
case "attack_melee":enumIdx = 0; radius = 1.5f; layer = playerLayer; break;
|
||||
case "attack_range":enumIdx = 0; radius = 8f; layer = playerLayer; break;
|
||||
}
|
||||
|
||||
elem.FindPropertyRelative("type").enumValueIndex = enumIdx;
|
||||
elem.FindPropertyRelative("radius").floatValue = radius;
|
||||
elem.FindPropertyRelative("detectLayer").intValue = layer;
|
||||
|
||||
// 各 slot 分配语义化默认颜色,可在 Inspector 中按需覆盖
|
||||
Color defaultColor = name switch
|
||||
{
|
||||
"aggro" => new Color(1.00f, 0.60f, 0.10f, 1f), // 橙
|
||||
"los" => new Color(0.00f, 0.80f, 1.00f, 1f), // 青
|
||||
"attack_melee" => new Color(1.00f, 0.20f, 0.20f, 1f), // 红
|
||||
"attack_range" => new Color(1.00f, 0.40f, 0.60f, 1f), // 粉红
|
||||
_ => Color.clear, // 未知 slot 回退为紫色
|
||||
};
|
||||
elem.FindPropertyRelative("gizmoColor").colorValue = defaultColor;
|
||||
}
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
|
||||
@@ -1713,6 +1811,25 @@ namespace BaseGames.Editor
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
private static Collider2D CreateBodyCollider(GameObject go, EnemyBodyColliderType type, Vector2 size)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EnemyBodyColliderType.Capsule:
|
||||
var cap = GetOrAddComponent<CapsuleCollider2D>(go);
|
||||
cap.size = size;
|
||||
return cap;
|
||||
case EnemyBodyColliderType.Circle:
|
||||
var cir = GetOrAddComponent<CircleCollider2D>(go);
|
||||
cir.radius = Mathf.Min(size.x, size.y) * 0.5f;
|
||||
return cir;
|
||||
default: // Box
|
||||
var box = GetOrAddComponent<BoxCollider2D>(go);
|
||||
box.size = size;
|
||||
return box;
|
||||
}
|
||||
}
|
||||
|
||||
private static T GetOrAddComponent<T>(GameObject go) where T : Component
|
||||
{
|
||||
T comp = go.GetComponent<T>();
|
||||
|
||||
Reference in New Issue
Block a user