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:
@@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using BaseGames.Boss;
|
||||
@@ -15,6 +15,8 @@ using BaseGames.Parry;
|
||||
using BaseGames.Player;
|
||||
using BaseGames.Player.States;
|
||||
using BaseGames.Skills;
|
||||
using BaseGames.Editor.Combat;
|
||||
using BaseGames.Editor.Skills;
|
||||
|
||||
namespace BaseGames.Editor
|
||||
{
|
||||
@@ -59,6 +61,7 @@ namespace BaseGames.Editor
|
||||
|
||||
// 小怪类型选择 — 具体敌人类型
|
||||
private int _enemyTypeIndex = 0;
|
||||
private SceneObjectPlacerTool.EnemyBodyColliderType _enemyBodyCollider = SceneObjectPlacerTool.EnemyBodyColliderType.Box;
|
||||
private static readonly (string id, string name)[] EnemyTypes =
|
||||
{
|
||||
("E001", "草蛭"),
|
||||
@@ -195,6 +198,8 @@ namespace BaseGames.Editor
|
||||
|
||||
var jumpGroup = MakeActionGroup();
|
||||
jumpGroup.Add(MakeJumpButton("Data Hub(武器/技能/形态)", DataHubWindow.Open));
|
||||
jumpGroup.Add(MakeJumpButton("武器 HitBox 向导", WeaponHitBoxWizard.Open));
|
||||
jumpGroup.Add(MakeJumpButton("技能 HitBox 向导", SkillHitBoxWizard.Open));
|
||||
jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu));
|
||||
root.Add(jumpGroup);
|
||||
|
||||
@@ -248,8 +253,9 @@ namespace BaseGames.Editor
|
||||
root.Add(MakeSectionHeader("▶ 专项编辑器"));
|
||||
|
||||
var jumpGroup = MakeActionGroup();
|
||||
jumpGroup.Add(MakeJumpButton("Data Hub(敌人数据)", DataHubWindow.Open));
|
||||
jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu));
|
||||
jumpGroup.Add(MakeJumpButton("Data Hub(敌人数据)", DataHubWindow.Open));
|
||||
jumpGroup.Add(MakeJumpButton("武器 HitBox 向导", WeaponHitBoxWizard.Open));
|
||||
jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu));
|
||||
root.Add(jumpGroup);
|
||||
|
||||
return root;
|
||||
@@ -286,11 +292,16 @@ namespace BaseGames.Editor
|
||||
|
||||
container.Add(MakeSeparator());
|
||||
container.Add(MakeSectionHeader("▶ 场景搭建"));
|
||||
container.Add(MakeHelpBox("在当前活动场景中放置完整组件树并自动绑定已有 SO。"));
|
||||
container.Add(MakeHelpBox("在当前活动场景中放置完整组件树(含 Visual 子节点对齐 Collider)并自动绑定已有 SO。"));
|
||||
|
||||
var colliderField = new UnityEngine.UIElements.EnumField("主体碰撞器类型", _enemyBodyCollider);
|
||||
colliderField.RegisterValueChangedCallback(evt => _enemyBodyCollider = (SceneObjectPlacerTool.EnemyBodyColliderType)evt.newValue);
|
||||
container.Add(colliderField);
|
||||
|
||||
var sceneGroup = MakeActionGroup();
|
||||
string sceneLabel = $"放置 {id} {name} 到场景";
|
||||
sceneGroup.Add(MakeSceneButton(sceneLabel, () => PlaceSpecificEnemy(id)));
|
||||
sceneGroup.Add(MakeSceneButton(sceneLabel, () => PlaceSpecificEnemy(id, _enemyBodyCollider)));
|
||||
sceneGroup.Add(MakeSceneButton("迁移选中对象 Visual 节点", MigrateSelectedEnemyVisualNode));
|
||||
container.Add(sceneGroup);
|
||||
}
|
||||
|
||||
@@ -330,15 +341,17 @@ namespace BaseGames.Editor
|
||||
|
||||
var sceneGroup = MakeActionGroup();
|
||||
sceneGroup.Add(MakeSceneButton("放置嘲风到场景并绑定 SO", SceneObjectPlacerTool.PlaceChaoFeng));
|
||||
sceneGroup.Add(MakeSceneButton("迁移选中对象 Visual 节点", MigrateSelectedEnemyVisualNode));
|
||||
root.Add(sceneGroup);
|
||||
|
||||
root.Add(MakeSeparator());
|
||||
root.Add(MakeSectionHeader("▶ 专项编辑器"));
|
||||
|
||||
var jumpGroup = MakeActionGroup();
|
||||
jumpGroup.Add(MakeJumpButton("Boss 技能序列查看器", BossSkillSequenceWindow.OpenWindow));
|
||||
jumpGroup.Add(MakeJumpButton("Data Hub(Boss技能)", DataHubWindow.Open));
|
||||
jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu));
|
||||
jumpGroup.Add(MakeJumpButton("Boss 技能序列查看器", BossSkillSequenceWindow.OpenWindow));
|
||||
jumpGroup.Add(MakeJumpButton("武器 HitBox 向导", WeaponHitBoxWizard.Open));
|
||||
jumpGroup.Add(MakeJumpButton("Data Hub(Boss技能)", DataHubWindow.Open));
|
||||
jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu));
|
||||
root.Add(jumpGroup);
|
||||
|
||||
return root;
|
||||
@@ -603,23 +616,45 @@ namespace BaseGames.Editor
|
||||
$"全部 {id} SO 已创建(已存在的跳过)。\n请放置到场景后检查组件绑定。", "确定");
|
||||
}
|
||||
|
||||
private static void PlaceSpecificEnemy(string id)
|
||||
private static void PlaceSpecificEnemy(string id, SceneObjectPlacerTool.EnemyBodyColliderType bodyCollider)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case "E001": SceneObjectPlacerTool.PlaceE001_CaoZhi(); break;
|
||||
case "E002": SceneObjectPlacerTool.PlaceE002_HuangZhi(); break;
|
||||
case "E003": SceneObjectPlacerTool.PlaceE003_YouZhi_Enemy(); break;
|
||||
case "E004": SceneObjectPlacerTool.PlaceE004_ZhiMu_Enemy(); break;
|
||||
case "E005": SceneObjectPlacerTool.PlaceE005_FeiZhi_Enemy(); break;
|
||||
case "E006": SceneObjectPlacerTool.PlaceE006_Huan(); break;
|
||||
case "E001": SceneObjectPlacerTool.PlaceE001_CaoZhi(bodyCollider); break;
|
||||
case "E002": SceneObjectPlacerTool.PlaceE002_HuangZhi(bodyCollider); break;
|
||||
case "E003": SceneObjectPlacerTool.PlaceE003_YouZhi_Enemy(bodyCollider); break;
|
||||
case "E004": SceneObjectPlacerTool.PlaceE004_ZhiMu_Enemy(bodyCollider); break;
|
||||
case "E005": SceneObjectPlacerTool.PlaceE005_FeiZhi_Enemy(bodyCollider); break;
|
||||
case "E006": SceneObjectPlacerTool.PlaceE006_Huan(bodyCollider); break;
|
||||
default:
|
||||
Debug.LogError($"[CharacterWizardWindow] 未注册的敌人 id '{id}',请在 SceneObjectPlacerTool 中实现对应 PlaceE...() 方法并注册。");
|
||||
SceneObjectPlacerTool.PlaceEnemy();
|
||||
SceneObjectPlacerTool.PlaceEnemy(bodyCollider);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MigrateSelectedEnemyVisualNode()
|
||||
{
|
||||
var targets = Selection.gameObjects;
|
||||
if (targets == null || targets.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("迁移 Visual 节点",
|
||||
"请先在 Hierarchy 中选中一个或多个 Enemy 对象。", "确定");
|
||||
return;
|
||||
}
|
||||
int count = 0;
|
||||
foreach (var go in targets)
|
||||
{
|
||||
var movement = go.GetComponent<EnemyMovement>();
|
||||
if (movement != null) { movement.SetupVisualNode(); count++; }
|
||||
}
|
||||
if (count == 0)
|
||||
EditorUtility.DisplayDialog("迁移 Visual 节点",
|
||||
"选中的对象中没有找到 EnemyMovement 组件。", "确定");
|
||||
else
|
||||
Debug.Log($"[CharacterWizardWindow] 已对 {count} 个对象完成 Visual 节点迁移。");
|
||||
}
|
||||
|
||||
// ── SO 资产工厂:嘲风 Boss ─────────────────────────────────────────────
|
||||
|
||||
private static void CreateChaoFengStatsSO()
|
||||
|
||||
115
Assets/_Game/Scripts/Editor/Combat/HitBoxEditor.cs
Normal file
115
Assets/_Game/Scripts/Editor/Combat/HitBoxEditor.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using BaseGames.Combat;
|
||||
|
||||
namespace BaseGames.Editor.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// HitBox 自定义 Inspector。
|
||||
/// 在"多形状碰撞体"面板中提供快捷按钮,一键创建带 HitBoxColliderProxy 的子形状节点。
|
||||
///
|
||||
/// 生成子节点结构:
|
||||
/// [HitBoxParent] ← HitBox 组件(可无 Collider2D)
|
||||
/// ├── [Shape_Box] ← BoxCollider2D(isTrigger) + HitBoxColliderProxy
|
||||
/// ├── [Shape_Circle] ← CircleCollider2D(isTrigger) + HitBoxColliderProxy
|
||||
/// ├── [Shape_Capsule] ← CapsuleCollider2D(isTrigger) + HitBoxColliderProxy
|
||||
/// └── [Shape_Polygon] ← PolygonCollider2D(isTrigger) + HitBoxColliderProxy
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(HitBox))]
|
||||
public class HitBoxEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
EditorGUILayout.Space(6);
|
||||
EditorGUILayout.LabelField("── 多形状碰撞体 ──", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"在子节点添加 HitBoxColliderProxy + Collider2D 以组合多形状判定盒。\n" +
|
||||
"子节点 Layer 自动继承本节点;Collider2D.IsTrigger 自动设为 true。",
|
||||
MessageType.Info);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("+ Box"))
|
||||
AddShapeChild((HitBox)target, ShapeKind.Box);
|
||||
if (GUILayout.Button("+ Circle"))
|
||||
AddShapeChild((HitBox)target, ShapeKind.Circle);
|
||||
if (GUILayout.Button("+ Capsule"))
|
||||
AddShapeChild((HitBox)target, ShapeKind.Capsule);
|
||||
if (GUILayout.Button("+ Polygon"))
|
||||
AddShapeChild((HitBox)target, ShapeKind.Polygon);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.Space(2);
|
||||
int proxyCount = ((HitBox)target).GetComponentsInChildren<HitBoxColliderProxy>(true).Length;
|
||||
EditorGUILayout.LabelField($"子代 HitBoxColliderProxy 数:{proxyCount}", EditorStyles.miniLabel);
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 内部工具 ──────────────────────────────────────────────────────────
|
||||
|
||||
private enum ShapeKind { Box, Circle, Capsule, Polygon }
|
||||
|
||||
private static void AddShapeChild(HitBox hitBox, ShapeKind kind)
|
||||
{
|
||||
var child = new GameObject($"[Shape_{kind}]");
|
||||
Undo.RegisterCreatedObjectUndo(child, $"Add HitBox {kind} Shape");
|
||||
child.transform.SetParent(hitBox.transform, false);
|
||||
child.layer = hitBox.gameObject.layer;
|
||||
|
||||
Collider2D col = kind switch
|
||||
{
|
||||
ShapeKind.Box => CreateBox(child),
|
||||
ShapeKind.Circle => CreateCircle(child),
|
||||
ShapeKind.Capsule => CreateCapsule(child),
|
||||
ShapeKind.Polygon => CreatePolygon(child),
|
||||
_ => CreateBox(child),
|
||||
};
|
||||
col.isTrigger = true;
|
||||
|
||||
Undo.AddComponent<HitBoxColliderProxy>(child);
|
||||
|
||||
Selection.activeGameObject = child;
|
||||
EditorGUIUtility.PingObject(child);
|
||||
}
|
||||
|
||||
private static BoxCollider2D CreateBox(GameObject go)
|
||||
{
|
||||
var c = Undo.AddComponent<BoxCollider2D>(go);
|
||||
c.size = new Vector2(1f, 0.5f);
|
||||
return c;
|
||||
}
|
||||
|
||||
private static CircleCollider2D CreateCircle(GameObject go)
|
||||
{
|
||||
var c = Undo.AddComponent<CircleCollider2D>(go);
|
||||
c.radius = 0.4f;
|
||||
return c;
|
||||
}
|
||||
|
||||
private static CapsuleCollider2D CreateCapsule(GameObject go)
|
||||
{
|
||||
var c = Undo.AddComponent<CapsuleCollider2D>(go);
|
||||
c.size = new Vector2(0.5f, 1f);
|
||||
return c;
|
||||
}
|
||||
|
||||
private static PolygonCollider2D CreatePolygon(GameObject go)
|
||||
{
|
||||
var c = Undo.AddComponent<PolygonCollider2D>(go);
|
||||
c.SetPath(0, new Vector2[]
|
||||
{
|
||||
new( 0f, 0.3f),
|
||||
new( 0.5f, 0f ),
|
||||
new( 0f, -0.3f),
|
||||
new(-0.5f, 0f ),
|
||||
});
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/_Game/Scripts/Editor/Combat/HitBoxEditor.cs.meta
Normal file
11
Assets/_Game/Scripts/Editor/Combat/HitBoxEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 414d2528016107148929f4d65d5507e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -6,9 +6,14 @@ using BaseGames.Combat;
|
||||
namespace BaseGames.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// HurtBox 运行时注入状态可视化面板。
|
||||
/// 通过 HurtBox 上的 Editor* 属性读取注入状态,以颜色区分是否注入成功。
|
||||
/// 绿色 = 注入完成;橙色 = 未注入(该能力静默不生效);灰色 = 非 PlayMode。
|
||||
/// HurtBox 自定义 Inspector。
|
||||
/// ① 提供"多形状受击区域"快捷按钮:一键创建带 HurtBox 的子形状节点(共享同一 HP 池)。
|
||||
/// ② 运行时注入状态可视化面板:绿色 = 注入完成;橙色 = 未注入;灰色 = 非 PlayMode。
|
||||
///
|
||||
/// 生成子节点结构:
|
||||
/// [HurtBoxParent] ← HurtBox + 任意 Collider2D(主受击区)
|
||||
/// ├── [HurtShape_Box] ← BoxCollider2D(isTrigger) + HurtBox(共享 HP)
|
||||
/// └── [HurtShape_Circle] ← CircleCollider2D(isTrigger) + HurtBox(共享 HP)
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(HurtBox))]
|
||||
public class HurtBoxEditor : UnityEditor.Editor
|
||||
@@ -27,6 +32,24 @@ namespace BaseGames.Editor
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
// ── 多形状受击区域 ──────────────────────────────────────────────
|
||||
EditorGUILayout.Space(6);
|
||||
EditorGUILayout.LabelField("── 多形状受击区域 ──", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox(
|
||||
"在子节点添加 HurtBox + Collider2D 以组合多形状受击区域,各子节点共享同一 HP 池。\n" +
|
||||
"子节点 Layer 自动继承本节点;HurtBoxOwnerGuard 防止同一次攻击重复扣血。",
|
||||
MessageType.Info);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("+ Box"))
|
||||
AddShapeChild((HurtBox)target, ShapeKind.Box);
|
||||
if (GUILayout.Button("+ Circle"))
|
||||
AddShapeChild((HurtBox)target, ShapeKind.Circle);
|
||||
if (GUILayout.Button("+ Capsule"))
|
||||
AddShapeChild((HurtBox)target, ShapeKind.Capsule);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// ── 运行时注入状态 ──────────────────────────────────────────────
|
||||
EditorGUILayout.Space(4);
|
||||
EditorGUILayout.LabelField("── 运行时注入状态 ──", EditorStyles.boldLabel);
|
||||
|
||||
@@ -59,6 +82,34 @@ namespace BaseGames.Editor
|
||||
// 持续刷新(避免只显示初始状态)
|
||||
if (Application.isPlaying) Repaint();
|
||||
}
|
||||
|
||||
// ── 子形状创建工具 ─────────────────────────────────────────────────────
|
||||
|
||||
private enum ShapeKind { Box, Circle, Capsule }
|
||||
|
||||
private static void AddShapeChild(HurtBox hurtBox, ShapeKind kind)
|
||||
{
|
||||
var child = new GameObject($"[HurtShape_{kind}]");
|
||||
Undo.RegisterCreatedObjectUndo(child, $"Add HurtBox {kind} Shape");
|
||||
child.transform.SetParent(hurtBox.transform, false);
|
||||
child.layer = hurtBox.gameObject.layer;
|
||||
|
||||
// 先加 Collider2D 以满足 HurtBox 的 [RequireComponent(typeof(Collider2D))],
|
||||
// 再 AddComponent<HurtBox>() 则不会重复创建 Collider2D。
|
||||
Collider2D col = kind switch
|
||||
{
|
||||
ShapeKind.Box => Undo.AddComponent<BoxCollider2D>(child),
|
||||
ShapeKind.Circle => Undo.AddComponent<CircleCollider2D>(child),
|
||||
ShapeKind.Capsule => Undo.AddComponent<CapsuleCollider2D>(child),
|
||||
_ => Undo.AddComponent<BoxCollider2D>(child),
|
||||
};
|
||||
col.isTrigger = true;
|
||||
|
||||
Undo.AddComponent<HurtBox>(child);
|
||||
|
||||
Selection.activeGameObject = child;
|
||||
EditorGUIUtility.PingObject(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using BaseGames.Enemies.Perception;
|
||||
|
||||
namespace BaseGames.Editor
|
||||
{
|
||||
[CustomEditor(typeof(PhysicsPerceptionSystem))]
|
||||
public sealed class PhysicsPerceptionSystemEditor : UnityEditor.Editor
|
||||
{
|
||||
// ── Scene 视图 Gizmo ──────────────────────────────────────────────────
|
||||
|
||||
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected | GizmoType.InSelectionHierarchy)]
|
||||
static void DrawGizmos(PhysicsPerceptionSystem system, GizmoType gizmoType)
|
||||
{
|
||||
var slots = system.EditorSlots;
|
||||
if (slots == null) return;
|
||||
|
||||
bool isSelected = (gizmoType & (GizmoType.Selected | GizmoType.InSelectionHierarchy)) != 0;
|
||||
Vector3 rootPos = system.transform.position;
|
||||
float facingSign = system.transform.localScale.x < 0f ? -1f : 1f;
|
||||
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
if (string.IsNullOrEmpty(slot.slotName)) continue;
|
||||
|
||||
Color baseColor = ResolveGizmoColor(slot.gizmoColor);
|
||||
Color fill = baseColor; fill.a = isSelected ? 0.12f : 0.04f;
|
||||
Color outline = baseColor; outline.a = isSelected ? 0.90f : 0.40f;
|
||||
|
||||
// 每个 Slot 独立检测原点(X 随朝向翻转)
|
||||
Vector3 slotCenter = rootPos + new Vector3(slot.offset.x * facingSign, slot.offset.y, 0f);
|
||||
|
||||
switch (slot.type)
|
||||
{
|
||||
case PhysicsPerceptionSystem.SlotType.RangeCircle:
|
||||
if (slot.radius <= 0f) break;
|
||||
Handles.color = fill;
|
||||
Handles.DrawSolidDisc(slotCenter, Vector3.forward, slot.radius);
|
||||
Handles.color = outline;
|
||||
Handles.DrawWireDisc(slotCenter, Vector3.forward, slot.radius);
|
||||
break;
|
||||
|
||||
case PhysicsPerceptionSystem.SlotType.FanCast:
|
||||
if (slot.radius > 0f && slot.fanAngle > 0f)
|
||||
{
|
||||
DrawFanGizmo(slotCenter, facingSign, slot, fill, outline);
|
||||
if (isSelected) DrawOriginDot(slotCenter, outline);
|
||||
}
|
||||
break;
|
||||
|
||||
case PhysicsPerceptionSystem.SlotType.BoxCast:
|
||||
if (slot.boxSize.x > 0f && slot.boxSize.y > 0f)
|
||||
{
|
||||
DrawBoxGizmo(slotCenter, facingSign, slot, fill, outline);
|
||||
if (isSelected) DrawOriginDot(slotCenter, outline);
|
||||
}
|
||||
break;
|
||||
|
||||
// BatchLOS: eye-position marker + optional range disc + runtime ray
|
||||
case PhysicsPerceptionSystem.SlotType.BatchLOS:
|
||||
DrawBatchLOSGizmo(system, slotCenter, slot, outline, isSelected);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Handles.color = Color.white;
|
||||
}
|
||||
|
||||
/// <summary>在 Slot 原点处画一个小十字点,帮助可视化 offset 配置。</summary>
|
||||
static void DrawOriginDot(Vector3 pos, Color color)
|
||||
{
|
||||
Handles.color = color;
|
||||
Handles.DrawSolidDisc(pos, Vector3.forward, 0.04f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅当颜色字段为 Unity 默认值 Color(0,0,0,0)(全零 = 未配置)时,
|
||||
/// 返回易辨识的紫色回退(alpha=1);用户明确设置的任何颜色(包括近黑色)均原样保留。
|
||||
/// </summary>
|
||||
static Color ResolveGizmoColor(Color c)
|
||||
{
|
||||
bool isDefault = c.r + c.g + c.b + c.a < 0.01f;
|
||||
return isDefault ? new Color(0.85f, 0.3f, 1.0f, 1.0f) : c;
|
||||
}
|
||||
|
||||
static void DrawBatchLOSGizmo(PhysicsPerceptionSystem system, Vector3 slotCenter,
|
||||
PhysicsPerceptionSystem.PerceptionSlot slot, Color slotColor, bool isSelected)
|
||||
{
|
||||
// slotColor 已经过 ResolveGizmoColor 处理,alpha 已由调用方设为 outline alpha。
|
||||
|
||||
// 所有 gizmo 元素统一画在 slotCenter(由 slot.offset 控制),
|
||||
// 与 LOSOrigin(实际射线起点)解耦——gizmo 跟 offset 走。
|
||||
float facingSign = system.transform.localScale.x < 0f ? -1f : 1f;
|
||||
|
||||
// ── 最大检测范围圆(slot.radius > 0 时)──
|
||||
if (slot.radius > 0f)
|
||||
{
|
||||
Color fill = slotColor; fill.a = isSelected ? 0.08f : 0.03f;
|
||||
Handles.color = fill;
|
||||
Handles.DrawSolidDisc(slotCenter, Vector3.forward, slot.radius);
|
||||
Color rim = slotColor; rim.a = isSelected ? 0.70f : 0.30f;
|
||||
Handles.color = rim;
|
||||
Handles.DrawWireDisc(slotCenter, Vector3.forward, slot.radius);
|
||||
}
|
||||
|
||||
// ── 眼睛中心圆点 ──
|
||||
Handles.color = slotColor;
|
||||
Handles.DrawSolidDisc(slotCenter, Vector3.forward, 0.08f);
|
||||
|
||||
// ── 朝向指示箭头(沿 facingSign 方向,明确"方向性"视线感知)──
|
||||
float arrowLen = slot.radius > 0f ? Mathf.Min(slot.radius * 0.6f, 0.6f) : 0.4f;
|
||||
Vector3 fwdArrow = new Vector3(facingSign * arrowLen, 0f, 0f);
|
||||
Handles.DrawLine(slotCenter, slotCenter + fwdArrow);
|
||||
|
||||
Vector3 arrowTip = slotCenter + fwdArrow;
|
||||
float headLen = 0.08f;
|
||||
Handles.DrawLine(arrowTip, arrowTip + new Vector3(-facingSign * headLen, headLen, 0f));
|
||||
Handles.DrawLine(arrowTip, arrowTip + new Vector3(-facingSign * headLen, -headLen, 0f));
|
||||
|
||||
// ── 放射线(仅选中时显示,避免杂乱)──
|
||||
if (isSelected)
|
||||
{
|
||||
float innerR = 0.12f;
|
||||
float outerR = slot.radius > 0f ? Mathf.Min(slot.radius, 0.30f) : 0.26f;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
float deg = i * 45f;
|
||||
float rad = deg * Mathf.Deg2Rad;
|
||||
Vector3 dir = new Vector3(Mathf.Cos(rad), Mathf.Sin(rad), 0f);
|
||||
Handles.DrawLine(slotCenter + dir * innerR, slotCenter + dir * outerR);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 运行时:视线连线;绿 = 可见 / 红 = 遮挡 ──
|
||||
if (!Application.isPlaying) return;
|
||||
var owner = system.EditorOwner ?? system.GetComponentInParent<BaseGames.Enemies.EnemyBase>();
|
||||
if (owner == null || owner.PlayerTransform == null) return;
|
||||
|
||||
bool visible = owner.IsPlayerVisible();
|
||||
Color rayCol = visible
|
||||
? new Color(0.2f, 1.0f, 0.3f, 0.9f)
|
||||
: new Color(1.0f, 0.3f, 0.3f, 0.45f);
|
||||
|
||||
Handles.color = rayCol;
|
||||
Handles.DrawDottedLine(slotCenter, owner.PlayerTransform.position, 3f);
|
||||
|
||||
if (visible)
|
||||
Handles.DrawSolidDisc(owner.PlayerTransform.position, Vector3.forward, 0.09f);
|
||||
}
|
||||
|
||||
static void DrawFanGizmo(Vector3 center, float facingSign,
|
||||
PhysicsPerceptionSystem.PerceptionSlot slot, Color fill, Color outline)
|
||||
{
|
||||
float halfAngle = slot.fanAngle * 0.5f;
|
||||
Vector3 fromDir = RotateVec3(new Vector3(facingSign, 0f, 0f), -halfAngle);
|
||||
|
||||
Handles.color = fill;
|
||||
Handles.DrawSolidArc(center, Vector3.forward, fromDir, slot.fanAngle, slot.radius);
|
||||
Handles.color = outline;
|
||||
Handles.DrawWireArc(center, Vector3.forward, fromDir, slot.fanAngle, slot.radius);
|
||||
|
||||
// Edge rays for clarity
|
||||
Vector3 edgeL = RotateVec3(new Vector3(facingSign, 0f, 0f), -halfAngle) * slot.radius;
|
||||
Vector3 edgeR = RotateVec3(new Vector3(facingSign, 0f, 0f), halfAngle) * slot.radius;
|
||||
Handles.DrawLine(center, center + edgeL);
|
||||
Handles.DrawLine(center, center + edgeR);
|
||||
}
|
||||
|
||||
static void DrawBoxGizmo(Vector3 center, float facingSign,
|
||||
PhysicsPerceptionSystem.PerceptionSlot slot, Color fill, Color outline)
|
||||
{
|
||||
Vector3 boxCenter = center + new Vector3(slot.boxOffset.x * facingSign, slot.boxOffset.y, 0f);
|
||||
float hw = slot.boxSize.x * 0.5f;
|
||||
float hh = slot.boxSize.y * 0.5f;
|
||||
|
||||
Vector3[] corners =
|
||||
{
|
||||
boxCenter + new Vector3(-hw, -hh, 0f),
|
||||
boxCenter + new Vector3( hw, -hh, 0f),
|
||||
boxCenter + new Vector3( hw, hh, 0f),
|
||||
boxCenter + new Vector3(-hw, hh, 0f),
|
||||
};
|
||||
|
||||
Handles.DrawSolidRectangleWithOutline(corners, fill, outline);
|
||||
}
|
||||
|
||||
static Vector3 RotateVec3(Vector3 v, float angleDeg)
|
||||
{
|
||||
float rad = angleDeg * Mathf.Deg2Rad;
|
||||
float cos = Mathf.Cos(rad);
|
||||
float sin = Mathf.Sin(rad);
|
||||
return new Vector3(cos * v.x - sin * v.y, sin * v.x + cos * v.y, 0f);
|
||||
}
|
||||
|
||||
// ── Inspector ─────────────────────────────────────────────────────────
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
if (!Application.isPlaying) return;
|
||||
|
||||
var system = (PhysicsPerceptionSystem)target;
|
||||
var detected = system.EditorDetected;
|
||||
if (detected == null || detected.Count == 0) return;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("── 实时检测结果 ──", EditorStyles.boldLabel);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kvp in detected)
|
||||
{
|
||||
sb.Clear();
|
||||
if (kvp.Value.Count > 0)
|
||||
{
|
||||
sb.Append("✓");
|
||||
foreach (var go in kvp.Value)
|
||||
sb.Append(' ').Append(go != null ? go.name : "null");
|
||||
}
|
||||
EditorGUILayout.LabelField(kvp.Key, kvp.Value.Count > 0 ? sb.ToString() : "—");
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cd6fa1109af6d04fafb6c1556eea81d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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