feat: Enhance CameraZoneMigrationTool with rectilinear polygon reconstruction and fallback handling
- Implemented a new method for reconstructing rectilinear polygons from trigger points. - Added a fallback mechanism using nearest neighbor ordering and 2-opt optimization for irregular shapes. - Updated point sorting to ensure counter-clockwise orientation for polygon colliders. fix: Suppress validation warnings during batch enemy placement in SceneObjectPlacerTool - Introduced a static property in EnemyBase to suppress validation warnings during editor object placement. - Updated SceneObjectPlacerTool to utilize this property when creating enemy game objects. refactor: Clean up OnValidate method in EnemyBase to respect suppression flag - Modified OnValidate to check for the suppression flag before logging warnings about missing configurations.
This commit is contained in:
@@ -12,14 +12,14 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: f7dd720bca19fcc49b22106fb65f7652, type: 3}
|
m_Script: {fileID: 11500000, guid: f7dd720bca19fcc49b22106fb65f7652, type: 3}
|
||||||
m_Name: ENM_E001_AnimConfig
|
m_Name: ENM_E001_AnimConfig
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
Idle: {fileID: 0}
|
Idle: {fileID: 7400000, guid: 74d1c2f7f8e5c66409e9090885e7e007, type: 2}
|
||||||
Walk: {fileID: 0}
|
Walk: {fileID: 7400000, guid: b6b9e34e957b9fa4b92e95aaa155099f, type: 2}
|
||||||
Run: {fileID: 0}
|
Run: {fileID: 7400000, guid: b6b9e34e957b9fa4b92e95aaa155099f, type: 2}
|
||||||
Turn: {fileID: 0}
|
Turn: {fileID: 7400000, guid: c6d78c8270549254f8c777e0c5d4f9bf, type: 2}
|
||||||
Attack: {fileID: 0}
|
Attack: {fileID: 0}
|
||||||
Hurt: {fileID: 0}
|
Hurt: {fileID: 7400000, guid: 9d5bb5bb32cdb344b80f01d998ed653f, type: 2}
|
||||||
Stagger: {fileID: 0}
|
Stagger: {fileID: 0}
|
||||||
KnockUp: {fileID: 0}
|
KnockUp: {fileID: 0}
|
||||||
Dead: {fileID: 0}
|
Dead: {fileID: 7400000, guid: 7f07e13e67a5aba4b8a27234e5a84ee6, type: 2}
|
||||||
Alert: {fileID: 0}
|
Alert: {fileID: 0}
|
||||||
Investigate: {fileID: 0}
|
Investigate: {fileID: 0}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -420,43 +420,195 @@ namespace BaseGames.Editor
|
|||||||
/// 构建触发多边形路径(本地坐标,相对于新 CameraArea):
|
/// 构建触发多边形路径(本地坐标,相对于新 CameraArea):
|
||||||
/// 取 TriggerRegion 各点的世界坐标减去 areaWorldPos;
|
/// 取 TriggerRegion 各点的世界坐标减去 areaWorldPos;
|
||||||
/// 若点数不足 3 则兜底使用可视矩形。
|
/// 若点数不足 3 则兜底使用可视矩形。
|
||||||
|
///
|
||||||
|
/// 处理流程:
|
||||||
|
/// 1. 优先尝试直角多边形重建(坐标分组配对算法)→ 精确还原横竖边构成的触发区域
|
||||||
|
/// 2. 降级:最近邻贪心 + 2-opt 消除自相交 → 适用于含斜边的多边形
|
||||||
|
/// 3. Shoelace 叉积校正绕向 → 确保 CCW(PolygonCollider2D 要求)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Vector2[] BuildTriggerPath(ZoneEntry entry, Vector3 areaWorldPos, Rect fallback)
|
private static Vector2[] BuildTriggerPath(ZoneEntry entry, Vector3 areaWorldPos, Rect fallback)
|
||||||
{
|
{
|
||||||
if (entry.TriggerWorldPts.Count >= 3)
|
if (entry.TriggerWorldPts.Count >= 3)
|
||||||
{
|
{
|
||||||
// 将世界坐标转换为 areaGO 本地坐标
|
|
||||||
var localPts = new Vector2[entry.TriggerWorldPts.Count];
|
var localPts = new Vector2[entry.TriggerWorldPts.Count];
|
||||||
for (int i = 0; i < localPts.Length; i++)
|
for (int i = 0; i < localPts.Length; i++)
|
||||||
localPts[i] = entry.TriggerWorldPts[i] - (Vector2)areaWorldPos;
|
localPts[i] = entry.TriggerWorldPts[i] - (Vector2)areaWorldPos;
|
||||||
|
|
||||||
// 按照质心角度排序,确保顶点顺序能够围成合法多边形
|
// 优先:直角多边形精确重建
|
||||||
return SortPointsByAngle(localPts);
|
var rectilinear = TryReconstructRectilinear(localPts);
|
||||||
|
if (rectilinear != null)
|
||||||
|
return EnsureCounterClockwise(rectilinear);
|
||||||
|
|
||||||
|
// 降级:最近邻 + 2-opt(适用于含斜边或点分布不规则的情况)
|
||||||
|
Debug.LogWarning($"[迁移工具] {entry.ZoneObj.name} 无法识别为直角多边形,已使用 2-opt 降级处理,请手动检查形状。");
|
||||||
|
var ordered = NearestNeighborOrder(localPts);
|
||||||
|
TwoOptFix(ordered);
|
||||||
|
return EnsureCounterClockwise(ordered);
|
||||||
}
|
}
|
||||||
return RectToPolygon(fallback);
|
return RectToPolygon(fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将一组点按照围绕质心的角度(逆时针)排序,使其能够围成合法的简单多边形。
|
/// 直角多边形(Rectilinear Polygon)顶点排序。
|
||||||
/// 适用于凸多边形及质心在多边形内部的凹多边形。
|
///
|
||||||
|
/// 算法原理:
|
||||||
|
/// 简单直角多边形中,每列(同 x)的顶点按 y 升序两两配对为纵向边,
|
||||||
|
/// 每行(同 y)的顶点按 x 升序两两配对为横向边(Scanline 奇偶规则)。
|
||||||
|
/// 每个顶点恰好有 1 条纵向边 + 1 条横向边,即恰好 2 个邻居,可遍历为完整环路。
|
||||||
|
///
|
||||||
|
/// 返回 null 表示点集不满足直角多边形条件(奇数分组、邻居数 ≠ 2 等),调用方应降级处理。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Vector2[] SortPointsByAngle(Vector2[] points)
|
private static Vector2[] TryReconstructRectilinear(Vector2[] points)
|
||||||
{
|
{
|
||||||
// 计算质心
|
// 同轴容差:小于此值的坐标差视为同行/同列(处理浮点误差)
|
||||||
Vector2 centroid = Vector2.zero;
|
const float kTol = 0.1f;
|
||||||
foreach (var p in points)
|
int n = points.Length;
|
||||||
centroid += p;
|
|
||||||
centroid /= points.Length;
|
|
||||||
|
|
||||||
// 按照相对质心的极角升序排列(逆时针)
|
var adj = new List<int>[n];
|
||||||
var sorted = new System.Collections.Generic.List<Vector2>(points);
|
for (int i = 0; i < n; i++) adj[i] = new List<int>();
|
||||||
sorted.Sort((a, b) =>
|
|
||||||
|
// 按 x 分组 → 纵向边;按 y 分组 → 横向边
|
||||||
|
if (!AddAxisEdges(points, adj, groupByX: true, tol: kTol)) return null;
|
||||||
|
if (!AddAxisEdges(points, adj, groupByX: false, tol: kTol)) return null;
|
||||||
|
|
||||||
|
// 每个顶点必须恰好有 2 个邻居(1 横 + 1 纵)才构成合法简单多边形
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
if (adj[i].Count != 2) return null;
|
||||||
|
|
||||||
|
// 从顶点 0 出发遍历环路
|
||||||
|
var result = new Vector2[n];
|
||||||
|
var visited = new bool[n];
|
||||||
|
result[0] = points[0];
|
||||||
|
visited[0] = true;
|
||||||
|
int prev = -1, curr = 0;
|
||||||
|
for (int step = 1; step < n; step++)
|
||||||
{
|
{
|
||||||
float angleA = Mathf.Atan2(a.y - centroid.y, a.x - centroid.x);
|
int next = -1;
|
||||||
float angleB = Mathf.Atan2(b.y - centroid.y, b.x - centroid.x);
|
foreach (int nb in adj[curr])
|
||||||
return angleA.CompareTo(angleB);
|
if (nb != prev && !visited[nb]) { next = nb; break; }
|
||||||
});
|
if (next == -1) return null; // 环路断裂
|
||||||
return sorted.ToArray();
|
result[step] = points[next];
|
||||||
|
visited[next] = true;
|
||||||
|
prev = curr;
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按指定轴(x 或 y)将顶点分组,每组内按垂直轴坐标排序后两两配对添加边。
|
||||||
|
/// 若任意组的顶点数为奇数(无法完整配对)则返回 false。
|
||||||
|
/// </summary>
|
||||||
|
private static bool AddAxisEdges(Vector2[] pts, List<int>[] adj, bool groupByX, float tol)
|
||||||
|
{
|
||||||
|
var groups = new Dictionary<int, List<int>>();
|
||||||
|
for (int i = 0; i < pts.Length; i++)
|
||||||
|
{
|
||||||
|
// 将坐标映射到整数 key,容差范围内的值归入同组
|
||||||
|
int key = Mathf.RoundToInt((groupByX ? pts[i].x : pts[i].y) / tol);
|
||||||
|
if (!groups.TryGetValue(key, out var list))
|
||||||
|
groups[key] = list = new List<int>();
|
||||||
|
list.Add(i);
|
||||||
|
}
|
||||||
|
foreach (var group in groups.Values)
|
||||||
|
{
|
||||||
|
if (group.Count % 2 != 0) return false; // 奇数个顶点无法配对
|
||||||
|
// 按垂直轴升序排列后两两配对为边
|
||||||
|
group.Sort((a, b) =>
|
||||||
|
(groupByX ? pts[a].y : pts[a].x).CompareTo(groupByX ? pts[b].y : pts[b].x));
|
||||||
|
for (int k = 0; k + 1 < group.Count; k += 2)
|
||||||
|
{
|
||||||
|
adj[group[k]].Add(group[k + 1]);
|
||||||
|
adj[group[k + 1]].Add(group[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最近邻贪心排序(降级路径):从最左侧点出发,每步选取欧氏距离最近的未访问点。
|
||||||
|
/// </summary>
|
||||||
|
private static Vector2[] NearestNeighborOrder(Vector2[] points)
|
||||||
|
{
|
||||||
|
int n = points.Length;
|
||||||
|
var visited = new bool[n];
|
||||||
|
var result = new Vector2[n];
|
||||||
|
|
||||||
|
int startIdx = 0;
|
||||||
|
for (int i = 1; i < n; i++)
|
||||||
|
if (points[i].x < points[startIdx].x) startIdx = i;
|
||||||
|
|
||||||
|
result[0] = points[startIdx];
|
||||||
|
visited[startIdx] = true;
|
||||||
|
for (int step = 1; step < n; step++)
|
||||||
|
{
|
||||||
|
float minSqDist = float.MaxValue;
|
||||||
|
int nearest = -1;
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
if (visited[i]) continue;
|
||||||
|
float sq = (points[i] - result[step - 1]).sqrMagnitude;
|
||||||
|
if (sq < minSqDist) { minSqDist = sq; nearest = i; }
|
||||||
|
}
|
||||||
|
result[step] = points[nearest];
|
||||||
|
visited[nearest] = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 2-opt 优化(降级路径):检测并翻转所有自相交边对,直到无交叉为止。
|
||||||
|
/// </summary>
|
||||||
|
private static void TwoOptFix(Vector2[] pts)
|
||||||
|
{
|
||||||
|
int n = pts.Length;
|
||||||
|
bool improved = true;
|
||||||
|
while (improved)
|
||||||
|
{
|
||||||
|
improved = false;
|
||||||
|
for (int i = 0; i < n - 1; i++)
|
||||||
|
for (int j = i + 2; j < n; j++)
|
||||||
|
{
|
||||||
|
if (i == 0 && j == n - 1) continue;
|
||||||
|
if (SegmentsIntersect(pts[i], pts[(i + 1) % n], pts[j], pts[(j + 1) % n]))
|
||||||
|
{
|
||||||
|
System.Array.Reverse(pts, i + 1, j - i);
|
||||||
|
improved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>用叉积判断线段 AB 与 CD 是否严格相交(不含端点接触)。</summary>
|
||||||
|
private static bool SegmentsIntersect(Vector2 a, Vector2 b, Vector2 c, Vector2 d)
|
||||||
|
{
|
||||||
|
float d1 = Cross2D(d - c, a - c), d2 = Cross2D(d - c, b - c);
|
||||||
|
float d3 = Cross2D(b - a, c - a), d4 = Cross2D(b - a, d - a);
|
||||||
|
return ((d1 > 0f && d2 < 0f) || (d1 < 0f && d2 > 0f)) &&
|
||||||
|
((d3 > 0f && d4 < 0f) || (d3 < 0f && d4 > 0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float Cross2D(Vector2 u, Vector2 v) => u.x * v.y - u.y * v.x;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 Shoelace 公式(叉积累加)验证绕向:
|
||||||
|
/// 2A = Σ (xᵢ · yᵢ₊₁ − xᵢ₊₁ · yᵢ)
|
||||||
|
/// Unity Y 轴朝上:A > 0 = CCW,A < 0 = CW。
|
||||||
|
/// 若为顺时针则翻转(不改变形状,只调整绕向)。
|
||||||
|
/// </summary>
|
||||||
|
private static Vector2[] EnsureCounterClockwise(Vector2[] points)
|
||||||
|
{
|
||||||
|
float signedArea = 0f;
|
||||||
|
int n = points.Length;
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
Vector2 curr = points[i];
|
||||||
|
Vector2 next = points[(i + 1) % n];
|
||||||
|
signedArea += curr.x * next.y - next.x * curr.y;
|
||||||
|
}
|
||||||
|
if (signedArea < 0f)
|
||||||
|
System.Array.Reverse(points);
|
||||||
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ namespace BaseGames.Editor
|
|||||||
var report = new List<string>();
|
var report = new List<string>();
|
||||||
int undoGroup = Undo.GetCurrentGroup();
|
int undoGroup = Undo.GetCurrentGroup();
|
||||||
Undo.SetCurrentGroupName("Place E001 草蛭");
|
Undo.SetCurrentGroupName("Place E001 草蛭");
|
||||||
|
EnemyBase.SuppressValidationWarnings = true;
|
||||||
GameObject go = new GameObject("ENM_CaoZhi");
|
GameObject go = new GameObject("ENM_CaoZhi");
|
||||||
Undo.RegisterCreatedObjectUndo(go, "Place E001");
|
Undo.RegisterCreatedObjectUndo(go, "Place E001");
|
||||||
go.transform.position = GetDropPosition();
|
go.transform.position = GetDropPosition();
|
||||||
@@ -470,6 +470,7 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
Undo.CollapseUndoOperations(undoGroup);
|
Undo.CollapseUndoOperations(undoGroup);
|
||||||
Selection.activeGameObject = go;
|
Selection.activeGameObject = go;
|
||||||
|
EnemyBase.SuppressValidationWarnings = false;
|
||||||
MarkDirtyAndLog("E001 草蛭", go, report);
|
MarkDirtyAndLog("E001 草蛭", go, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,7 +480,7 @@ namespace BaseGames.Editor
|
|||||||
var report = new List<string>();
|
var report = new List<string>();
|
||||||
int undoGroup = Undo.GetCurrentGroup();
|
int undoGroup = Undo.GetCurrentGroup();
|
||||||
Undo.SetCurrentGroupName("Place E002 簧蛭");
|
Undo.SetCurrentGroupName("Place E002 簧蛭");
|
||||||
|
EnemyBase.SuppressValidationWarnings = true;
|
||||||
GameObject go = new GameObject("ENM_HuangZhi");
|
GameObject go = new GameObject("ENM_HuangZhi");
|
||||||
Undo.RegisterCreatedObjectUndo(go, "Place E002");
|
Undo.RegisterCreatedObjectUndo(go, "Place E002");
|
||||||
go.transform.position = GetDropPosition();
|
go.transform.position = GetDropPosition();
|
||||||
@@ -548,6 +549,7 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
Undo.CollapseUndoOperations(undoGroup);
|
Undo.CollapseUndoOperations(undoGroup);
|
||||||
Selection.activeGameObject = go;
|
Selection.activeGameObject = go;
|
||||||
|
EnemyBase.SuppressValidationWarnings = false;
|
||||||
MarkDirtyAndLog("E002 簧蛭", go, report);
|
MarkDirtyAndLog("E002 簧蛭", go, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,7 +559,7 @@ namespace BaseGames.Editor
|
|||||||
var report = new List<string>();
|
var report = new List<string>();
|
||||||
int undoGroup = Undo.GetCurrentGroup();
|
int undoGroup = Undo.GetCurrentGroup();
|
||||||
Undo.SetCurrentGroupName("Place E003 幼蛭");
|
Undo.SetCurrentGroupName("Place E003 幼蛭");
|
||||||
|
EnemyBase.SuppressValidationWarnings = true;
|
||||||
GameObject go = new GameObject("ENM_YouZhi");
|
GameObject go = new GameObject("ENM_YouZhi");
|
||||||
Undo.RegisterCreatedObjectUndo(go, "Place E003");
|
Undo.RegisterCreatedObjectUndo(go, "Place E003");
|
||||||
go.transform.position = GetDropPosition();
|
go.transform.position = GetDropPosition();
|
||||||
@@ -631,6 +633,7 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
Undo.CollapseUndoOperations(undoGroup);
|
Undo.CollapseUndoOperations(undoGroup);
|
||||||
Selection.activeGameObject = go;
|
Selection.activeGameObject = go;
|
||||||
|
EnemyBase.SuppressValidationWarnings = false;
|
||||||
MarkDirtyAndLog("E003 幼蛭", go, report);
|
MarkDirtyAndLog("E003 幼蛭", go, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,7 +643,7 @@ namespace BaseGames.Editor
|
|||||||
var report = new List<string>();
|
var report = new List<string>();
|
||||||
int undoGroup = Undo.GetCurrentGroup();
|
int undoGroup = Undo.GetCurrentGroup();
|
||||||
Undo.SetCurrentGroupName("Place E004 蛭母");
|
Undo.SetCurrentGroupName("Place E004 蛭母");
|
||||||
|
EnemyBase.SuppressValidationWarnings = true;
|
||||||
GameObject go = new GameObject("ENM_ZhiMu");
|
GameObject go = new GameObject("ENM_ZhiMu");
|
||||||
Undo.RegisterCreatedObjectUndo(go, "Place E004");
|
Undo.RegisterCreatedObjectUndo(go, "Place E004");
|
||||||
go.transform.position = GetDropPosition();
|
go.transform.position = GetDropPosition();
|
||||||
@@ -757,6 +760,7 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
Undo.CollapseUndoOperations(undoGroup);
|
Undo.CollapseUndoOperations(undoGroup);
|
||||||
Selection.activeGameObject = go;
|
Selection.activeGameObject = go;
|
||||||
|
EnemyBase.SuppressValidationWarnings = false;
|
||||||
MarkDirtyAndLog("E004 蛭母", go, report);
|
MarkDirtyAndLog("E004 蛭母", go, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -766,7 +770,7 @@ namespace BaseGames.Editor
|
|||||||
var report = new List<string>();
|
var report = new List<string>();
|
||||||
int undoGroup = Undo.GetCurrentGroup();
|
int undoGroup = Undo.GetCurrentGroup();
|
||||||
Undo.SetCurrentGroupName("Place E005 肥蛭");
|
Undo.SetCurrentGroupName("Place E005 肥蛭");
|
||||||
|
EnemyBase.SuppressValidationWarnings = true;
|
||||||
GameObject go = new GameObject("ENM_FeiZhi");
|
GameObject go = new GameObject("ENM_FeiZhi");
|
||||||
Undo.RegisterCreatedObjectUndo(go, "Place E005");
|
Undo.RegisterCreatedObjectUndo(go, "Place E005");
|
||||||
go.transform.position = GetDropPosition();
|
go.transform.position = GetDropPosition();
|
||||||
@@ -849,6 +853,7 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
Undo.CollapseUndoOperations(undoGroup);
|
Undo.CollapseUndoOperations(undoGroup);
|
||||||
Selection.activeGameObject = go;
|
Selection.activeGameObject = go;
|
||||||
|
EnemyBase.SuppressValidationWarnings = false;
|
||||||
MarkDirtyAndLog("E005 肥蛭", go, report);
|
MarkDirtyAndLog("E005 肥蛭", go, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -858,7 +863,7 @@ namespace BaseGames.Editor
|
|||||||
var report = new List<string>();
|
var report = new List<string>();
|
||||||
int undoGroup = Undo.GetCurrentGroup();
|
int undoGroup = Undo.GetCurrentGroup();
|
||||||
Undo.SetCurrentGroupName("Place E006 讙");
|
Undo.SetCurrentGroupName("Place E006 讙");
|
||||||
|
EnemyBase.SuppressValidationWarnings = true;
|
||||||
GameObject go = new GameObject("ENM_Huan");
|
GameObject go = new GameObject("ENM_Huan");
|
||||||
Undo.RegisterCreatedObjectUndo(go, "Place E006");
|
Undo.RegisterCreatedObjectUndo(go, "Place E006");
|
||||||
go.transform.position = GetDropPosition();
|
go.transform.position = GetDropPosition();
|
||||||
@@ -951,6 +956,7 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
Undo.CollapseUndoOperations(undoGroup);
|
Undo.CollapseUndoOperations(undoGroup);
|
||||||
Selection.activeGameObject = go;
|
Selection.activeGameObject = go;
|
||||||
|
EnemyBase.SuppressValidationWarnings = false;
|
||||||
MarkDirtyAndLog("E006 讙", go, report);
|
MarkDirtyAndLog("E006 讙", go, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -960,7 +966,7 @@ namespace BaseGames.Editor
|
|||||||
var report = new List<string>();
|
var report = new List<string>();
|
||||||
int undoGroup = Undo.GetCurrentGroup();
|
int undoGroup = Undo.GetCurrentGroup();
|
||||||
Undo.SetCurrentGroupName("Place Boss 嘲风");
|
Undo.SetCurrentGroupName("Place Boss 嘲风");
|
||||||
|
EnemyBase.SuppressValidationWarnings = true;
|
||||||
GameObject go = new GameObject("ENM_ChaoFeng");
|
GameObject go = new GameObject("ENM_ChaoFeng");
|
||||||
Undo.RegisterCreatedObjectUndo(go, "Place ChaoFeng");
|
Undo.RegisterCreatedObjectUndo(go, "Place ChaoFeng");
|
||||||
go.transform.position = GetDropPosition();
|
go.transform.position = GetDropPosition();
|
||||||
@@ -1067,6 +1073,7 @@ namespace BaseGames.Editor
|
|||||||
|
|
||||||
Undo.CollapseUndoOperations(undoGroup);
|
Undo.CollapseUndoOperations(undoGroup);
|
||||||
Selection.activeGameObject = go;
|
Selection.activeGameObject = go;
|
||||||
|
EnemyBase.SuppressValidationWarnings = false;
|
||||||
MarkDirtyAndLog("Boss 嘲风 (ChaoFeng)", go, report);
|
MarkDirtyAndLog("Boss 嘲风 (ChaoFeng)", go, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -710,8 +710,12 @@ namespace BaseGames.Enemies
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
|
/// <summary>Set to true during batch editor placement to suppress mid-wiring OnValidate warnings.</summary>
|
||||||
|
public static bool SuppressValidationWarnings { get; set; }
|
||||||
|
|
||||||
protected virtual void OnValidate()
|
protected virtual void OnValidate()
|
||||||
{
|
{
|
||||||
|
if (SuppressValidationWarnings) return;
|
||||||
if (_statsSO == null)
|
if (_statsSO == null)
|
||||||
Debug.LogWarning($"[EnemyBase] {gameObject.name} 缺少 EnemyStatsSO 配置(运行时会 NullRef)。", this);
|
Debug.LogWarning($"[EnemyBase] {gameObject.name} 缺少 EnemyStatsSO 配置(运行时会 NullRef)。", this);
|
||||||
if (_stats == null)
|
if (_stats == null)
|
||||||
|
|||||||
Reference in New Issue
Block a user