角色能力,存档
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using Unity.Cinemachine;
|
||||
using BaseGames.Camera;
|
||||
@@ -17,7 +18,7 @@ namespace BaseGames.Editor
|
||||
/// FOV 优先级(降序):
|
||||
/// 专有 DedicatedCamera.Lens.FieldOfView
|
||||
/// → CameraLensConfigSO.fieldOfView(单一来源,无跨场景依赖)
|
||||
/// → CameraStateController._vcamA(Persistent 场景已加载时)
|
||||
/// → CameraStateController 活动 VCam 的 FieldOfView(编辑器备用)
|
||||
/// → Camera.main.fieldOfView
|
||||
/// → 60f(默认)
|
||||
/// </summary>
|
||||
@@ -41,6 +42,7 @@ namespace BaseGames.Editor
|
||||
private bool _foldFollow = true;
|
||||
private bool _foldLens = false;
|
||||
private bool _foldCamera = false;
|
||||
private bool _foldNoise = false;
|
||||
private bool _foldTools = false;
|
||||
|
||||
// ── 折叠标题样式缓存(深色背景 + 白色文字)────────────────────────────
|
||||
@@ -78,7 +80,7 @@ namespace BaseGames.Editor
|
||||
EditorGUILayout.PropertyField(confinerProp, new GUIContent("Confiner Collider"));
|
||||
}
|
||||
if (!confinerOk)
|
||||
EditorGUILayout.HelpBox("必须绑定子节点 PolygonCollider2D(AreaBoundary),否则 Cinemachine 无法限位。", MessageType.Error);
|
||||
EditorGUILayout.HelpBox("必须绑定子节点 BoxCollider(AreaBoundary),否则 CinemachineConfiner3D 无法限位。", MessageType.Error);
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("_visibleBounds"), new GUIContent("Visible Bounds(本地坐标)"));
|
||||
}
|
||||
@@ -86,11 +88,11 @@ namespace BaseGames.Editor
|
||||
|
||||
EditorGUILayout.Space(2f);
|
||||
|
||||
// ── 跟随参数覆盖 ─────────────────────────────────────────────────
|
||||
// ── 跟随行为参数 ─────────────────────────────────────────────────
|
||||
var overrideProp = serializedObject.FindProperty("_overrideFollowBehaviour");
|
||||
bool overrides = overrideProp.boolValue;
|
||||
_foldFollow = DrawFoldoutHeader(
|
||||
overrides ? "跟随参数覆盖 ●" : "跟随参数覆盖 ○ (使用全局默认)", _foldFollow);
|
||||
overrides ? "相机行为参数 ●" : "相机行为参数 ○ (使用 VCam 默认参数)", _foldFollow);
|
||||
if (_foldFollow)
|
||||
{
|
||||
using (new EditorGUI.IndentLevelScope())
|
||||
@@ -108,6 +110,25 @@ namespace BaseGames.Editor
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("_lookaheadSmoothing"),new GUIContent("Lookahead Smoothing"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("_lockHorizontal"), new GUIContent("Lock Horizontal"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("_lockVertical"), new GUIContent("Lock Vertical"));
|
||||
|
||||
// ── 方向感知偏置 ──────────────────────────────────
|
||||
EditorGUILayout.Space(4f);
|
||||
EditorGUILayout.LabelField("方向感知水平偏置", EditorStyles.miniLabel);
|
||||
var overrideFacingProp = serializedObject.FindProperty("_overrideFacingBias");
|
||||
EditorGUILayout.PropertyField(overrideFacingProp, new GUIContent("Override Facing Bias",
|
||||
"开启后可为此区域单独设置偏置量;关闭则使用 VCam 扩展组件的默认值。"));
|
||||
if (overrideFacingProp.boolValue)
|
||||
{
|
||||
using (new EditorGUI.IndentLevelScope())
|
||||
{
|
||||
var biasProp = serializedObject.FindProperty("_facingBiasOverride");
|
||||
EditorGUILayout.PropertyField(biasProp, new GUIContent("Facing Bias (units)",
|
||||
"方向感知偏置量(世界单位)。0 = 禁用此区域的方向偏置。\n" +
|
||||
"较窄走廊建议设 0,防止相机超出 Confiner 边界。"));
|
||||
if (biasProp.floatValue < 0.01f)
|
||||
EditorGUILayout.HelpBox("设为 0 将禁用此区域的方向感知偏置。", MessageType.Info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,8 +149,8 @@ namespace BaseGames.Editor
|
||||
|
||||
EditorGUILayout.Space(2f);
|
||||
|
||||
// ── 专有相机(可选) ──────────────────────────────────────────────
|
||||
_foldCamera = DrawFoldoutHeader("专有相机(可选)", _foldCamera);
|
||||
// ── 虚拟相机 ──────────────────────────────────────────────
|
||||
_foldCamera = DrawFoldoutHeader("虚拟相机", _foldCamera);
|
||||
if (_foldCamera)
|
||||
{
|
||||
using (new EditorGUI.IndentLevelScope())
|
||||
@@ -140,7 +161,29 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(2f);
|
||||
// ── 相机噪音(氛围震动) ────────────────────────────────────────
|
||||
_foldNoise = DrawFoldoutHeader("相机噪音(氛围震动)", _foldNoise);
|
||||
if (_foldNoise)
|
||||
{
|
||||
using (new EditorGUI.IndentLevelScope())
|
||||
{
|
||||
var noiseProp = serializedObject.FindProperty("_noiseProfile");
|
||||
EditorGUILayout.PropertyField(noiseProp, new GUIContent("Noise Profile",
|
||||
"氛围震动配置(Noise Settings 资产),留空则禁用噪音。"));
|
||||
if (noiseProp.objectReferenceValue != null)
|
||||
{
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("_noiseAmplitude"),
|
||||
new GUIContent("Amplitude Gain", "振幅增益(0 = 无震动,推荐 0.2 ~ 0.8)。"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("_noiseFrequency"),
|
||||
new GUIContent("Frequency Gain", "频率增益(1 = 资产原始频率)。"));
|
||||
}
|
||||
EditorGUILayout.HelpBox(
|
||||
"专属 VCam 已包含 CinemachineBasicMultiChannelPerlin 组件(使用工具创建时自动附加)。",
|
||||
MessageType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(2f);
|
||||
// ── 可视区域工具 ──────────────────────────────────────────────────
|
||||
_foldTools = DrawFoldoutHeader("可视区域工具", _foldTools);
|
||||
if (_foldTools)
|
||||
@@ -247,7 +290,7 @@ namespace BaseGames.Editor
|
||||
|
||||
EditorGUILayout.Space(4f);
|
||||
DrawLegend("■ 黄色矩形(Scene 视图)", kVisibleOutline, "可视区域 — 摄像机视口永不超出此范围");
|
||||
DrawLegend("■ 蓝色多边形(Scene 视图)", kConfinerLine, "限位区域 — CinemachineConfiner2D 的运动边界");
|
||||
DrawLegend("■ 蓝色矩形(Scene 视图)", kConfinerLine, "限位区域 — CinemachineConfiner3D 的运动边界");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +362,7 @@ namespace BaseGames.Editor
|
||||
{
|
||||
Undo.RecordObject(area, "Edit Visible Bounds");
|
||||
if (area.ConfinerCollider != null)
|
||||
Undo.RecordObject(area.ConfinerCollider, "Sync Confiner");
|
||||
Undo.RecordObject(area.ConfinerCollider, "Sync Confiner"); // BoxCollider
|
||||
|
||||
// 世界坐标 → 本地坐标,存入序列化字段
|
||||
boundsP.rectValue = new Rect(r.x - areaPos.x, r.y - areaPos.y, r.width, r.height);
|
||||
@@ -380,17 +423,21 @@ namespace BaseGames.Editor
|
||||
|
||||
private static void DrawConfinerGizmo(CameraArea area)
|
||||
{
|
||||
var poly = area.ConfinerCollider;
|
||||
if (poly == null || poly.pathCount == 0) return;
|
||||
var box = area.ConfinerCollider;
|
||||
if (box == null) return;
|
||||
|
||||
int ptCount = poly.GetTotalPointCount();
|
||||
if (ptCount < 3) return;
|
||||
// 将 BoxCollider 的 XY 范围投影到 Scene 视图(忽略 Z,2D 俯视)
|
||||
Vector3 centerWorld = box.transform.TransformPoint(box.center);
|
||||
float hw = box.size.x * 0.5f;
|
||||
float hh = box.size.y * 0.5f;
|
||||
|
||||
var pts2 = new System.Collections.Generic.List<Vector2>(ptCount);
|
||||
poly.GetPath(0, pts2);
|
||||
var pts3 = new Vector3[ptCount];
|
||||
for (int i = 0; i < ptCount; i++)
|
||||
pts3[i] = poly.transform.TransformPoint(pts2[i]);
|
||||
var pts3 = new Vector3[]
|
||||
{
|
||||
new Vector3(centerWorld.x - hw, centerWorld.y - hh, 0f), // BL
|
||||
new Vector3(centerWorld.x + hw, centerWorld.y - hh, 0f), // BR
|
||||
new Vector3(centerWorld.x + hw, centerWorld.y + hh, 0f), // TR
|
||||
new Vector3(centerWorld.x - hw, centerWorld.y + hh, 0f), // TL
|
||||
};
|
||||
|
||||
DrawPolyGizmo(pts3, kConfinerFill, kConfinerLine, 2.0f);
|
||||
|
||||
@@ -510,17 +557,16 @@ namespace BaseGames.Editor
|
||||
|
||||
internal static void SyncConfinerFromVisibleBounds(CameraArea area, float vFOV, float aspect)
|
||||
{
|
||||
var poly = area.ConfinerCollider;
|
||||
if (poly == null)
|
||||
var box = area.ConfinerCollider;
|
||||
if (box == null)
|
||||
{
|
||||
Debug.LogWarning($"[CameraAreaEditor] {area.name}:ConfinerCollider 未绑定,无法同步。");
|
||||
return;
|
||||
}
|
||||
|
||||
// VisibleBounds 已含 transform.position,为世界坐标。
|
||||
// 限位多边形 = 相机中心运动范围 = VisibleBounds 向内收缩视口半尺寸。
|
||||
// 运行时 ConfigureSlot 设置 OversizeWindow.MaxWindowSize ≈ 0,
|
||||
// 阻止 Cinemachine 再次收缩此多边形,确保边界精确匹配可视区域。
|
||||
// 限位体积 = 相机中心运动范围 = VisibleBounds 向内收缩视口半尺寸。
|
||||
// Confiner3D 以 BoxCollider 直接约束相机 3D 位置,无需 OversizeWindow 补偿。
|
||||
Rect visible = area.VisibleBounds; // 世界坐标
|
||||
float depth = area.CameraDepth;
|
||||
float halfH = depth * Mathf.Tan(vFOV * 0.5f * Mathf.Deg2Rad);
|
||||
@@ -532,25 +578,21 @@ namespace BaseGames.Editor
|
||||
float yMax = visible.yMax - halfH;
|
||||
|
||||
// 小房间:视口大于可视区域时收缩至中心点,相机固定在可视区域中心
|
||||
const float kMinSize = 0.001f;
|
||||
if (xMin > xMax) { float cx = visible.center.x; xMin = cx - kMinSize * 0.5f; xMax = cx + kMinSize * 0.5f; }
|
||||
if (yMin > yMax) { float cy = visible.center.y; yMin = cy - kMinSize * 0.5f; yMax = cy + kMinSize * 0.5f; }
|
||||
// BoxCollider 允许 size = 0,Confiner3D 不需要最小尺寸
|
||||
if (xMin > xMax) { float cx = visible.center.x; xMin = cx; xMax = cx; }
|
||||
if (yMin > yMax) { float cy = visible.center.y; yMin = cy; yMax = cy; }
|
||||
|
||||
Transform polyT = poly.transform;
|
||||
Vector2 Local(Vector3 w) => polyT.InverseTransformPoint(w);
|
||||
// BoxCollider center 以其 Transform 本地坐标表示
|
||||
// 相机在世界 Z = -depth,AreaBoundary 节点在 Z = 0,所以 center.z = -depth
|
||||
Transform boxT = box.transform;
|
||||
Vector3 centerWorld = new Vector3((xMin + xMax) * 0.5f, (yMin + yMax) * 0.5f, 0f);
|
||||
Vector3 centerLocal = boxT.InverseTransformPoint(centerWorld);
|
||||
centerLocal.z = -depth;
|
||||
|
||||
Undo.RecordObject(poly, "Sync Confiner from Visible Bounds");
|
||||
// 顶点必须 CCW(逆时针):Clipper 对 CW 多边形(area<0)会取反 delta,
|
||||
// 导致 Confiner 向外膨胀而非向内收缩,相机完全不受限。
|
||||
// CCW 顺序:BL → BR → TR → TL
|
||||
poly.SetPath(0, new[]
|
||||
{
|
||||
Local(new Vector3(xMin, yMin, 0f)), // BL
|
||||
Local(new Vector3(xMax, yMin, 0f)), // BR
|
||||
Local(new Vector3(xMax, yMax, 0f)), // TR
|
||||
Local(new Vector3(xMin, yMax, 0f)), // TL
|
||||
});
|
||||
EditorUtility.SetDirty(poly);
|
||||
Undo.RecordObject(box, "Sync Confiner from Visible Bounds");
|
||||
box.center = centerLocal;
|
||||
box.size = new Vector3(xMax - xMin, yMax - yMin, 1f);
|
||||
EditorUtility.SetDirty(box);
|
||||
|
||||
// 记录本次同步所用的 FOV,供编辑器过期检测使用
|
||||
var areaSO = new SerializedObject(area);
|
||||
@@ -575,6 +617,180 @@ namespace BaseGames.Editor
|
||||
internal static void SyncConfinerAuto(CameraArea area) =>
|
||||
SyncConfinerFromVisibleBounds(area, GetFOV(area), GetAspect());
|
||||
|
||||
// ── VCam 组件顺序检测 / 修复 ─────────────────────────────────────────
|
||||
|
||||
// 必须在 CinemachineConfiner3D 之前运行的扩展类型(修改 RawPosition,需被 Confiner 裁剪)
|
||||
private static readonly System.Type[] s_MustBeforeConfiner =
|
||||
{
|
||||
typeof(CameraAsymmetricDampingExtension),
|
||||
typeof(CameraFallBiasExtension),
|
||||
typeof(CameraFacingBiasExtension),
|
||||
typeof(CameraAdaptiveLookaheadExtension),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 检查 <paramref name="vcam"/> 的扩展组件顺序是否正确。
|
||||
/// 返回问题描述字符串;若无问题则返回 <c>null</c>。
|
||||
/// </summary>
|
||||
internal static string CheckVCamExtensionOrderIssue(CinemachineCamera vcam)
|
||||
{
|
||||
if (vcam == null) return null;
|
||||
|
||||
var confiner = vcam.GetComponent<CinemachineConfiner3D>();
|
||||
if (confiner == null) return "缺少 CinemachineConfiner3D 组件";
|
||||
|
||||
Component[] comps = vcam.GetComponents<Component>();
|
||||
int confinerIdx = System.Array.IndexOf(comps, confiner);
|
||||
var sb = new System.Text.StringBuilder();
|
||||
|
||||
foreach (var t in s_MustBeforeConfiner)
|
||||
{
|
||||
var comp = vcam.GetComponent(t);
|
||||
if (comp == null) continue;
|
||||
if (System.Array.IndexOf(comps, comp) > confinerIdx)
|
||||
sb.AppendLine($" · {t.Name} 在 Confiner 之后(偏置绕过限位 → 相机逃出区域)");
|
||||
}
|
||||
|
||||
var axisLock = vcam.GetComponent<CameraAxisLockExtension>();
|
||||
if (axisLock != null && System.Array.IndexOf(comps, axisLock) < confinerIdx)
|
||||
sb.AppendLine(" · CameraAxisLockExtension 在 Confiner 之前(轴向锁定会被 Confiner 覆盖失效)");
|
||||
|
||||
return sb.Length > 0 ? sb.ToString().TrimEnd() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动修正 <paramref name="vcam"/> 上扩展组件的挂载顺序:
|
||||
/// 将偏置扩展移到 <see cref="CinemachineConfiner3D"/> 之前,
|
||||
/// 将 <see cref="CameraAxisLockExtension"/> 移到之后。
|
||||
/// </summary>
|
||||
internal static void FixVCamExtensionOrder(CinemachineCamera vcam)
|
||||
{
|
||||
if (vcam == null) return;
|
||||
|
||||
var confiner = vcam.GetComponent<CinemachineConfiner3D>();
|
||||
if (confiner == null)
|
||||
{
|
||||
Debug.LogWarning($"[CameraAreaEditor] {vcam.name} 缺少 CinemachineConfiner3D,无法修正顺序。");
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(vcam.gameObject, "Fix VCam Extension Order");
|
||||
|
||||
// 将偏置扩展逐个移到 Confiner 之前
|
||||
foreach (var t in s_MustBeforeConfiner)
|
||||
{
|
||||
var comp = vcam.GetComponent(t);
|
||||
if (comp == null) continue;
|
||||
|
||||
// 反复上移,直到位于 Confiner 之前(最多 30 步防死循环)
|
||||
for (int guard = 0; guard < 30; guard++)
|
||||
{
|
||||
Component[] comps = vcam.GetComponents<Component>();
|
||||
int compIdx = System.Array.IndexOf(comps, comp);
|
||||
int confinerIdx = System.Array.IndexOf(comps, confiner);
|
||||
if (compIdx < confinerIdx) break;
|
||||
UnityEditorInternal.ComponentUtility.MoveComponentUp(comp);
|
||||
}
|
||||
}
|
||||
|
||||
// 将 AxisLock 移到 Confiner 之后
|
||||
var axisLock = vcam.GetComponent<CameraAxisLockExtension>();
|
||||
if (axisLock != null)
|
||||
{
|
||||
for (int guard = 0; guard < 30; guard++)
|
||||
{
|
||||
Component[] comps = vcam.GetComponents<Component>();
|
||||
int axisIdx = System.Array.IndexOf(comps, axisLock);
|
||||
int confinerIdx = System.Array.IndexOf(comps, confiner);
|
||||
if (axisIdx > confinerIdx) break;
|
||||
UnityEditorInternal.ComponentUtility.MoveComponentDown(axisLock);
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(vcam.gameObject);
|
||||
Debug.Log($"[CameraAreaEditor] 已修正 {vcam.name} 的扩展组件顺序。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为指定 <paramref name="area"/> 创建子节点专有 VCam,附加所有必要组件并绑定到
|
||||
/// <c>_dedicatedCamera</c>。若已有专有 VCam 则直接返回现有实例,不重复创建。
|
||||
/// </summary>
|
||||
internal static CinemachineCamera CreateDedicatedVCamForArea(CameraArea area)
|
||||
{
|
||||
if (area == null) return null;
|
||||
|
||||
if (area.DedicatedCamera != null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[CameraAreaEditor] {area.name} 已有专有 VCam:{area.DedicatedCamera.name},跳过创建。");
|
||||
return area.DedicatedCamera;
|
||||
}
|
||||
|
||||
string vcamName = $"VCam_{area.gameObject.name}";
|
||||
var vcamGO = new GameObject(vcamName);
|
||||
Undo.RegisterCreatedObjectUndo(vcamGO, "Create Dedicated VCam");
|
||||
vcamGO.transform.SetParent(area.transform);
|
||||
vcamGO.transform.localPosition = Vector3.zero;
|
||||
|
||||
// ── CinemachineCamera ─────────────────────────────────────────────
|
||||
var vcam = vcamGO.AddComponent<CinemachineCamera>();
|
||||
vcam.Priority = 0; // 非激活;由 CameraStateController.ActivateDedicated 提升优先级
|
||||
|
||||
// ── Body:位置合成器 ───────────────────────────────────────────────
|
||||
var composer = vcamGO.AddComponent<CinemachinePositionComposer>();
|
||||
|
||||
// ── 扩展组件(管线顺序:Body 阶段偏置扩展 → PostBody Confiner → 独立 AxisLock → Noise)
|
||||
// AsymmetricDamping → FallBias → FacingBias → AdaptiveLookahead
|
||||
// vcamGO.AddComponent<CameraAsymmetricDampingExtension>();
|
||||
// vcamGO.AddComponent<CameraFallBiasExtension>();
|
||||
// vcamGO.AddComponent<CameraFacingBiasExtension>();
|
||||
// vcamGO.AddComponent<CameraAdaptiveLookaheadExtension>();
|
||||
vcamGO.AddComponent<CinemachinePixelPerfect>();
|
||||
// ── 限位 Confiner(PostBody 阶段,须在位置偏置扩展之后)────────────
|
||||
var confiner3d = vcamGO.AddComponent<CinemachineConfiner3D>();
|
||||
if (area.ConfinerCollider != null)
|
||||
confiner3d.BoundingVolume = area.ConfinerCollider;
|
||||
|
||||
// ── 轴向约束(独立,PostBody)────────────────────────────────────
|
||||
vcamGO.AddComponent<CameraAxisLockExtension>();
|
||||
|
||||
// ── 噪音(Noise 阶段,须排在 Confiner / AxisLock 之后)───────────
|
||||
vcamGO.AddComponent<CinemachineBasicMultiChannelPerlin>();
|
||||
|
||||
// ── 应用镜头参数:FOV + CameraDistance + Transform Z ─────────────
|
||||
// 与 CameraStateController.ApplyLensToVcam 逻辑保持一致:
|
||||
// 1. Lens.FieldOfView
|
||||
// 2. composer.CameraDistance(控制运行时真实 Z 距离,否则管线会覆盖)
|
||||
// 3. transform.localPosition.z(编辑器预览与运行时保持一致)
|
||||
float fov = area.LensConfig != null ? area.LensConfig.fieldOfView : 60f;
|
||||
float depth = area.CameraDepth > 0f ? area.CameraDepth : 10f;
|
||||
|
||||
var lens = vcam.Lens;
|
||||
lens.FieldOfView = fov;
|
||||
vcam.Lens = lens;
|
||||
|
||||
composer.CameraDistance = depth;
|
||||
|
||||
var localPos = vcamGO.transform.localPosition;
|
||||
localPos.z = -depth;
|
||||
vcamGO.transform.localPosition = localPos;
|
||||
|
||||
// ── 写入 CameraArea._dedicatedCamera(及默认优先级)───────────────
|
||||
var so = new SerializedObject(area);
|
||||
so.Update();
|
||||
so.FindProperty("_dedicatedCamera").objectReferenceValue = vcam;
|
||||
var priProp = so.FindProperty("_dedicatedPriority");
|
||||
if (priProp.intValue == 0) priProp.intValue = 20;
|
||||
so.ApplyModifiedProperties();
|
||||
EditorUtility.SetDirty(area);
|
||||
EditorSceneManager.MarkSceneDirty(area.gameObject.scene);
|
||||
|
||||
EditorGUIUtility.PingObject(vcamGO);
|
||||
Debug.Log($"[CameraAreaEditor] 已为 {area.name} 创建专有 VCam:{vcamName}" +
|
||||
$" FOV={fov:F1}° Depth={depth:F1}");
|
||||
return vcam;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 与 <see cref="SyncConfinerFromVisibleBounds"/> 逻辑相同,但不记录 Undo、不输出日志。
|
||||
/// 供拖拽 Handle 时每帧调用,避免 Undo 堆积和 Console 刷屏。
|
||||
@@ -582,8 +798,8 @@ namespace BaseGames.Editor
|
||||
/// </summary>
|
||||
private static void SyncConfinerQuiet(CameraArea area, float vFOV, float aspect)
|
||||
{
|
||||
var poly = area.ConfinerCollider;
|
||||
if (poly == null) return;
|
||||
var box = area.ConfinerCollider;
|
||||
if (box == null) return;
|
||||
|
||||
// VisibleBounds 已含 transform.position,为世界坐标。
|
||||
Rect visible = area.VisibleBounds;
|
||||
@@ -596,22 +812,17 @@ namespace BaseGames.Editor
|
||||
float yMin = visible.yMin + halfH;
|
||||
float yMax = visible.yMax - halfH;
|
||||
|
||||
const float kMinSize = 0.001f;
|
||||
if (xMin > xMax) { float cx = visible.center.x; xMin = cx - kMinSize * 0.5f; xMax = cx + kMinSize * 0.5f; }
|
||||
if (yMin > yMax) { float cy = visible.center.y; yMin = cy - kMinSize * 0.5f; yMax = cy + kMinSize * 0.5f; }
|
||||
if (xMin > xMax) { float cx = visible.center.x; xMin = cx; xMax = cx; }
|
||||
if (yMin > yMax) { float cy = visible.center.y; yMin = cy; yMax = cy; }
|
||||
|
||||
Transform polyT = poly.transform;
|
||||
Vector2 Local(Vector3 w) => polyT.InverseTransformPoint(w);
|
||||
Transform boxT = box.transform;
|
||||
Vector3 centerWorld = new Vector3((xMin + xMax) * 0.5f, (yMin + yMax) * 0.5f, 0f);
|
||||
Vector3 centerLocal = boxT.InverseTransformPoint(centerWorld);
|
||||
centerLocal.z = -depth;
|
||||
|
||||
// CCW 顺序:BL → BR → TR → TL(同 SyncConfinerFromVisibleBounds)
|
||||
poly.SetPath(0, new[]
|
||||
{
|
||||
Local(new Vector3(xMin, yMin, 0f)), // BL
|
||||
Local(new Vector3(xMax, yMin, 0f)), // BR
|
||||
Local(new Vector3(xMax, yMax, 0f)), // TR
|
||||
Local(new Vector3(xMin, yMax, 0f)), // TL
|
||||
});
|
||||
EditorUtility.SetDirty(poly);
|
||||
box.center = centerLocal;
|
||||
box.size = new Vector3(xMax - xMin, yMax - yMin, 1f);
|
||||
EditorUtility.SetDirty(box);
|
||||
}
|
||||
|
||||
/// <summary>在 Scene 视图左上角绘制叠加信息面板(屏幕空间)。</summary>
|
||||
@@ -663,7 +874,7 @@ namespace BaseGames.Editor
|
||||
// ══ 工具方法 ══════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// 获取用于透视计算的 FOV(优先级:专有 VCam → 全局 VCamA → Camera.main → 60f)。
|
||||
/// 获取用于透视计算的 FOV(优先级:专属 VCam → CameraLensConfigSO → Camera.main → 60f)。
|
||||
/// </summary>
|
||||
private static float GetFOV(CameraArea area)
|
||||
{
|
||||
@@ -675,7 +886,7 @@ namespace BaseGames.Editor
|
||||
if (area.LensConfig != null)
|
||||
return area.LensConfig.fieldOfView;
|
||||
|
||||
// 3. Persistent 场景已加载时,实时读取全局 VCamA(兆底)
|
||||
// 3. CameraStateController 存在时,通过 LensConfig 读取 FOV(备用底线)
|
||||
#pragma warning disable UNT0023 // FindObjectOfType 在编辑器工具中可接受
|
||||
var ctrl = Object.FindObjectOfType<CameraStateController>();
|
||||
#pragma warning restore UNT0023
|
||||
|
||||
@@ -17,8 +17,9 @@ namespace BaseGames.Editor
|
||||
///
|
||||
/// 新格式:
|
||||
/// [新 CameraArea GO](CameraArea 组件,_visibleBounds = 本地 Rect)
|
||||
/// ├─ AreaBoundary(PolygonCollider2D,isTrigger=true,对应旧 Confiner)
|
||||
/// └─ TriggerZone(CameraTriggerZone + PolygonCollider2D,对应旧 TriggerRegion)
|
||||
/// ├─ AreaBoundary(BoxCollider,对应旧 Confiner)
|
||||
/// ├─ TriggerZone(CameraTriggerZone + PolygonCollider2D,对应旧 TriggerRegion)
|
||||
/// └─ VCam_xxx(CinemachineCamera + 所有扩展组件,专属虚拟相机)
|
||||
///
|
||||
/// 菜单:BaseGames → Camera → 相机区域迁移工具
|
||||
/// </summary>
|
||||
@@ -32,9 +33,10 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
// ── 设置字段 ──────────────────────────────────────────────────────────
|
||||
private Transform _sourcesParent; // 旧 Zone_xxx 的父节点(通常名为 Zones)
|
||||
private Transform _targetParent; // 新对象放置位置(留空 = 与旧区域同级)
|
||||
private CameraLensConfigSO _lensConfig; // 绑定到新 CameraArea._lensConfig
|
||||
private Transform _sourcesParent; // 旧 Zone_xxx 的父节点(通常名为 Zones)
|
||||
private Transform _targetParent; // 新对象放置位置(留空 = 与旧区域同级)
|
||||
private CameraLensConfigSO _lensConfig; // 绑定到新 CameraArea._lensConfig
|
||||
private bool _createDedicatedVCam = true; // 为每个区域创建专属 CinemachineCamera
|
||||
|
||||
|
||||
// ── 运行时状态 ────────────────────────────────────────────────────────
|
||||
@@ -86,6 +88,12 @@ namespace BaseGames.Editor
|
||||
new GUIContent("镜头配置 SO", "赋给所有新 CameraArea._lensConfig;留空则不赋值"),
|
||||
_lensConfig, typeof(CameraLensConfigSO), false);
|
||||
|
||||
_createDedicatedVCam = EditorGUILayout.Toggle(
|
||||
new GUIContent("创建专属 VCam",
|
||||
"为每个迁移区域创建子节点 CinemachineCamera(含所有扩展组件),\n" +
|
||||
"并绑定到 CameraArea._dedicatedCamera。"),
|
||||
_createDedicatedVCam);
|
||||
|
||||
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
@@ -220,17 +228,24 @@ namespace BaseGames.Editor
|
||||
|
||||
foreach (Transform child in _sourcesParent)
|
||||
{
|
||||
Debug.Log($"[root]:{child.name}");
|
||||
// 旧 Zone 的标识:子节点直属,且挂有 BoxCollider2D
|
||||
var box = child.GetComponent<BoxCollider2D>();
|
||||
if (box == null) continue;
|
||||
|
||||
var entry = new ZoneEntry { ZoneObj = child.gameObject, VisibleBox = box };
|
||||
|
||||
// 收集触发多边形顶点(TriggerRegion 子节点的各个点对象)
|
||||
Transform triggerRoot = FindChildContaining(child, "TriggerRegion");
|
||||
if (triggerRoot != null)
|
||||
foreach (Transform pt in triggerRoot)
|
||||
// 收集触发多边形顶点——xxx_TriggerRegion 下每个子节点的世界坐标即一个顶点,
|
||||
// 按子节点顺序依次连线围成多边形触发区域。
|
||||
Transform triggerRoot = FindChildContaining(child, "Trigger");
|
||||
|
||||
if (triggerRoot != null){
|
||||
Debug.Log($"[trigger]:{triggerRoot.name}");
|
||||
foreach (Transform pt in triggerRoot){
|
||||
Debug.Log($"{pt.name}");
|
||||
entry.TriggerWorldPts.Add((Vector2)pt.position);
|
||||
}
|
||||
}
|
||||
|
||||
// 读取限位碰撞体(Zone_xxx_Confiner 上的 Collider2D)
|
||||
Transform confinerT = FindChildContaining(child, "Confiner");
|
||||
@@ -298,20 +313,20 @@ namespace BaseGames.Editor
|
||||
soArea.FindProperty("_lensConfig").objectReferenceValue = _lensConfig;
|
||||
soArea.ApplyModifiedProperties();
|
||||
|
||||
// ── 3. 创建 AreaBoundary(限位多边形,isTrigger = true)──────────
|
||||
// ── 3. 创建 AreaBoundary(限位体积 BoxCollider)─────────────────────────────
|
||||
GameObject boundaryGO = new GameObject($"{zoneGO.name}_AreaBoundary");
|
||||
Undo.RegisterCreatedObjectUndo(boundaryGO, "Migrate Camera Zone");
|
||||
boundaryGO.transform.SetParent(areaGO.transform, worldPositionStays: false);
|
||||
boundaryGO.transform.localPosition = Vector3.zero;
|
||||
|
||||
PolygonCollider2D confinerPoly = boundaryGO.AddComponent<PolygonCollider2D>();
|
||||
confinerPoly.isTrigger = true;
|
||||
confinerPoly.pathCount = 1;
|
||||
confinerPoly.SetPath(0, BuildConfinerPath(entry, worldPos, localBounds));
|
||||
BoxCollider confinerBox = boundaryGO.AddComponent<BoxCollider>();
|
||||
confinerBox.isTrigger = true;
|
||||
confinerBox.center = new Vector3(0f, 0f, -10f); // Z 占位符,SyncConfiner 会立即重算
|
||||
confinerBox.size = new Vector3(localBounds.width, localBounds.height, 1f);
|
||||
|
||||
// 绑定 _confinerCollider
|
||||
soArea.Update();
|
||||
soArea.FindProperty("_confinerCollider").objectReferenceValue = confinerPoly;
|
||||
soArea.FindProperty("_confinerCollider").objectReferenceValue = confinerBox;
|
||||
soArea.ApplyModifiedProperties();
|
||||
EditorUtility.SetDirty(area);
|
||||
|
||||
@@ -330,9 +345,10 @@ namespace BaseGames.Editor
|
||||
// AddComponent 会因 [RequireComponent] 自动添加 PolygonCollider2D
|
||||
CameraTriggerZone triggerComp = triggerGO.AddComponent<CameraTriggerZone>();
|
||||
PolygonCollider2D triggerPoly = triggerGO.GetComponent<PolygonCollider2D>();
|
||||
// 将旧触发多边形路径(本地坐标,相对于新 CameraArea 世界位置)直接赋给 PolygonCollider2D
|
||||
Vector2[] triggerPath = BuildTriggerPath(entry, worldPos, localBounds);
|
||||
triggerPoly.isTrigger = true;
|
||||
triggerPoly.pathCount = 1;
|
||||
triggerPoly.SetPath(0, BuildTriggerPath(entry, worldPos, localBounds));
|
||||
triggerPoly.SetPath(0, triggerPath);
|
||||
|
||||
// _targetArea → 指向刚创建的 CameraArea
|
||||
var soTrigger = new SerializedObject(triggerComp);
|
||||
@@ -340,7 +356,11 @@ namespace BaseGames.Editor
|
||||
soTrigger.ApplyModifiedProperties();
|
||||
EditorUtility.SetDirty(triggerComp);
|
||||
|
||||
// ── 5. 处理旧对象 ──────────────────────────────────────────────
|
||||
// ── 5. 创建专属 VCam(每区域独立相机)─────────────────────────────
|
||||
if (_createDedicatedVCam)
|
||||
CameraAreaEditor.CreateDedicatedVCamForArea(area);
|
||||
|
||||
// ── 6. 处理旧对象 ──────────────────────────────────────────────
|
||||
// 先记录原始激活状态,再对旧对象做处理,避免 SetActive(false) 后误读
|
||||
bool wasActive = zoneGO.activeSelf;
|
||||
|
||||
@@ -356,8 +376,8 @@ namespace BaseGames.Editor
|
||||
|
||||
Debug.Log($"[迁移工具] {zoneGO.name} → {areaGO.name} " +
|
||||
$"可视 {localBounds.width:F0}×{localBounds.height:F0} " +
|
||||
$"触发 {triggerPoly.GetTotalPointCount()} pt " +
|
||||
$"限位 {confinerPoly.GetTotalPointCount()} pt");
|
||||
$"触发 PolygonCollider2D ({triggerPath.Length} 点) " +
|
||||
$"限位 BoxCollider ({confinerBox.size.x:F1}×{confinerBox.size.y:F1})");
|
||||
}
|
||||
|
||||
// ── 限位多边形路径 ────────────────────────────────────────────────────
|
||||
@@ -405,14 +425,40 @@ namespace BaseGames.Editor
|
||||
{
|
||||
if (entry.TriggerWorldPts.Count >= 3)
|
||||
{
|
||||
var path = new Vector2[entry.TriggerWorldPts.Count];
|
||||
for (int i = 0; i < path.Length; i++)
|
||||
path[i] = entry.TriggerWorldPts[i] - (Vector2)areaWorldPos;
|
||||
return path;
|
||||
// 将世界坐标转换为 areaGO 本地坐标
|
||||
var localPts = new Vector2[entry.TriggerWorldPts.Count];
|
||||
for (int i = 0; i < localPts.Length; i++)
|
||||
localPts[i] = entry.TriggerWorldPts[i] - (Vector2)areaWorldPos;
|
||||
|
||||
// 按照质心角度排序,确保顶点顺序能够围成合法多边形
|
||||
return SortPointsByAngle(localPts);
|
||||
}
|
||||
return RectToPolygon(fallback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一组点按照围绕质心的角度(逆时针)排序,使其能够围成合法的简单多边形。
|
||||
/// 适用于凸多边形及质心在多边形内部的凹多边形。
|
||||
/// </summary>
|
||||
private static Vector2[] SortPointsByAngle(Vector2[] points)
|
||||
{
|
||||
// 计算质心
|
||||
Vector2 centroid = Vector2.zero;
|
||||
foreach (var p in points)
|
||||
centroid += p;
|
||||
centroid /= points.Length;
|
||||
|
||||
// 按照相对质心的极角升序排列(逆时针)
|
||||
var sorted = new System.Collections.Generic.List<Vector2>(points);
|
||||
sorted.Sort((a, b) =>
|
||||
{
|
||||
float angleA = Mathf.Atan2(a.y - centroid.y, a.x - centroid.x);
|
||||
float angleB = Mathf.Atan2(b.y - centroid.y, b.x - centroid.x);
|
||||
return angleA.CompareTo(angleB);
|
||||
});
|
||||
return sorted.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动计算 BoxCollider2D 的世界 AABB,不依赖 .bounds(inactive 对象上 .bounds 无效)。
|
||||
/// </summary>
|
||||
|
||||
@@ -98,6 +98,9 @@ namespace BaseGames.Editor
|
||||
|
||||
if (GUILayout.Button("↺ 全部同步限位区域", EditorStyles.toolbarButton))
|
||||
SyncAllConfiners();
|
||||
|
||||
if (GUILayout.Button("✔ 批量创建专属 VCam", EditorStyles.toolbarButton))
|
||||
BatchCreateDedicatedVCams();
|
||||
}
|
||||
|
||||
// ── 创建 CameraArea 面板 ───────────────────────────────────────
|
||||
@@ -217,6 +220,40 @@ namespace BaseGames.Editor
|
||||
|
||||
Debug.Log($"[CameraAreaSetupTool] 已同步 {count} 个 CameraArea 的限位区域。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为所有尚未配置专有 VCam 的 CameraArea 批量创建专有 CinemachineCamera。
|
||||
/// 已有 _dedicatedCamera 的区域将跳过。
|
||||
/// </summary>
|
||||
private void BatchCreateDedicatedVCams()
|
||||
{
|
||||
if (_cameraAreas.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("[CameraAreaSetupTool] 场景中无 CameraArea,跳过批量创建。");
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
foreach (var area in _cameraAreas)
|
||||
{
|
||||
if (area == null) continue;
|
||||
if (area.DedicatedCamera != null) continue; // 已有专有 VCam,跳过
|
||||
|
||||
CameraAreaEditor.CreateDedicatedVCamForArea(area);
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
RescanScene();
|
||||
Debug.Log($"[CameraAreaSetupTool] 已为 {count} 个 CameraArea 创建专有 VCam。");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[CameraAreaSetupTool] 所有 CameraArea 均已有专有 VCam,无需创建。");
|
||||
}
|
||||
}
|
||||
|
||||
// ── CameraStateController ──────────────────────────────────────────
|
||||
|
||||
private void DrawControllerSection()
|
||||
@@ -240,8 +277,6 @@ namespace BaseGames.Editor
|
||||
}
|
||||
|
||||
SerializedObject so = new SerializedObject(_controller);
|
||||
DrawFieldCheck(so, "_vcamA", "全局 VCam A (CinemachineCamera)");
|
||||
DrawFieldCheck(so, "_vcamB", "全局 VCam B (CinemachineCamera)");
|
||||
DrawFieldCheck(so, "_brain", "CinemachineBrain");
|
||||
DrawFieldCheck(so, "_onPlayerSpawned", "玩家生成事件 (EVT_PlayerSpawned) → VCam 自动绑 Follow");
|
||||
DrawFieldCheck(so, "_impulseSource", "CinemachineImpulseSource", optional: true);
|
||||
@@ -334,7 +369,8 @@ namespace BaseGames.Editor
|
||||
bool confinerOk = so.FindProperty("_confinerCollider").objectReferenceValue != null;
|
||||
var boundZones = FindTriggerZonesForArea(area);
|
||||
bool hasZone = boundZones.Count > 0;
|
||||
bool allOk = confinerOk && hasZone;
|
||||
bool hasVCam = so.FindProperty("_dedicatedCamera").objectReferenceValue != null;
|
||||
bool allOk = confinerOk && hasZone && hasVCam;
|
||||
|
||||
using (new EditorGUILayout.VerticalScope(_boxStyle))
|
||||
{
|
||||
@@ -353,6 +389,10 @@ namespace BaseGames.Editor
|
||||
GUI.color = hasZone ? kOk : kError;
|
||||
GUILayout.Label(hasZone ? $"[{boundZones.Count} 触发器]" : "[无触发器]",
|
||||
EditorStyles.miniLabel, GUILayout.Width(74f));
|
||||
|
||||
GUI.color = hasVCam ? kOk : kError;
|
||||
GUILayout.Label(hasVCam ? "[VCam ✔]" : "[VCam ✗]",
|
||||
EditorStyles.miniLabel, GUILayout.Width(54f));
|
||||
GUI.color = prevC;
|
||||
|
||||
if (GUILayout.Button("⊙", GUILayout.Width(24f)))
|
||||
@@ -366,12 +406,61 @@ namespace BaseGames.Editor
|
||||
EditorGUILayout.Space(3f);
|
||||
|
||||
// ── 绑定字段 ────────────────────────────────────────────────
|
||||
DrawCheckRow("_confinerCollider(可视边界 PolygonCollider2D)", confinerOk);
|
||||
DrawCheckRow("_dedicatedCamera(专有 VCam,可选)",
|
||||
so.FindProperty("_dedicatedCamera").objectReferenceValue != null, optional: true);
|
||||
DrawCheckRow("_confinerCollider(可视边界 BoxCollider)", confinerOk);
|
||||
|
||||
// ── 专有 VCam 状态行(创建 / Ping 按鈕)────────────────────────
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
Color prevC2 = GUI.color;
|
||||
GUI.color = hasVCam ? kOk : kError;
|
||||
GUILayout.Label(hasVCam ? "●" : "✗", GUILayout.Width(16f));
|
||||
GUI.color = prevC2;
|
||||
|
||||
if (hasVCam)
|
||||
{
|
||||
var vcamObj = so.FindProperty("_dedicatedCamera").objectReferenceValue;
|
||||
EditorGUILayout.LabelField($"_dedicatedCamera:{vcamObj.name}",
|
||||
GUILayout.ExpandWidth(true));
|
||||
if (GUILayout.Button("⊙", GUILayout.Width(24f)))
|
||||
EditorGUIUtility.PingObject(vcamObj);
|
||||
if (GUILayout.Button("选中", GUILayout.Width(36f)))
|
||||
Selection.activeObject = vcamObj;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField("_dedicatedCamera(专有 VCam)未创建",
|
||||
GUILayout.ExpandWidth(true));
|
||||
if (GUILayout.Button("创建专有 VCam", GUILayout.Width(90f), GUILayout.Height(18f)))
|
||||
{
|
||||
CameraAreaEditor.CreateDedicatedVCamForArea(area);
|
||||
RescanScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DrawCheckRow("_blendProfile(可选,未设则用全局默认)",
|
||||
so.FindProperty("_blendProfile").objectReferenceValue != null, optional: true);
|
||||
|
||||
// ── VCam 扩展组件顺序检查 ────────────────────────────────────
|
||||
// AsymmetricDamping/FallBias/FacingBias 必须在 CinemachineConfiner3D 之前;
|
||||
// AxisLock 必须在之后。顺序错误会使相机逃出限位区域。
|
||||
if (hasVCam)
|
||||
{
|
||||
var vcam = so.FindProperty("_dedicatedCamera").objectReferenceValue as CinemachineCamera;
|
||||
string issue = CameraAreaEditor.CheckVCamExtensionOrderIssue(vcam);
|
||||
if (issue != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
$"VCam 扩展组件顺序错误!相机会逃出限位区域:\n{issue}",
|
||||
MessageType.Error);
|
||||
if (GUILayout.Button("⚙ 自动修正组件顺序", GUILayout.Height(22f)))
|
||||
{
|
||||
CameraAreaEditor.FixVCamExtensionOrder(vcam);
|
||||
RescanScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 触发区域列表 ─────────────────────────────────────────────
|
||||
EditorGUILayout.Space(3f);
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
@@ -412,16 +501,16 @@ namespace BaseGames.Editor
|
||||
EditorGUILayout.Space(3f);
|
||||
if (!confinerOk)
|
||||
{
|
||||
// 区分:有非 Trigger 的 PolygonCollider2D 可直接绑定 vs 完全没有 AreaBoundary
|
||||
var existingBoundary = FindBoundaryPoly(area);
|
||||
// 区分:有 BoxCollider 可直接绑定 vs 完全没有 AreaBoundary
|
||||
var existingBoundary = FindBoundaryBox(area);
|
||||
if (existingBoundary != null)
|
||||
{
|
||||
if (GUILayout.Button("修复:绑定子节点 PolygonCollider2D", GUILayout.Height(22f)))
|
||||
if (GUILayout.Button("修复:绑定子节点 BoxCollider", GUILayout.Height(22f)))
|
||||
FixConfinerBinding(area);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("创建 AreaBoundary(限位多边形,默认 24 × 12)", GUILayout.Height(22f)))
|
||||
if (GUILayout.Button("创建 AreaBoundary(限位体积,默认 24 × 12)", GUILayout.Height(22f)))
|
||||
{
|
||||
CreateAreaBoundary(area);
|
||||
RescanScene();
|
||||
@@ -433,7 +522,7 @@ namespace BaseGames.Editor
|
||||
Color helpC = GUI.color;
|
||||
GUI.color = kMuted;
|
||||
EditorGUILayout.LabelField(
|
||||
"★ 可视边界:选中子节点的 PolygonCollider2D,在 Scene 视图中编辑顶点。",
|
||||
"★ 限位体积:选中子节点的 BoxCollider,在 Inspector 中编辑 Center / Size。",
|
||||
EditorStyles.miniLabel);
|
||||
GUI.color = helpC;
|
||||
}
|
||||
@@ -557,34 +646,34 @@ namespace BaseGames.Editor
|
||||
Debug.LogWarning("[CameraAreaSetupTool] _vcamA/_vcamB 均未绑定,无法赋值 Follow。请先在 Inspector 中绑定。");
|
||||
}
|
||||
|
||||
/// <summary>将子节点中找到的第一个不含 CameraTriggerZone 的 PolygonCollider2D 绑定到 CameraArea._confinerCollider。</summary>
|
||||
/// <summary>将子节点中找到的第一个不含 CameraTriggerZone 的 BoxCollider 绑定到 CameraArea._confinerCollider。</summary>
|
||||
private static void FixConfinerBinding(CameraArea area)
|
||||
{
|
||||
PolygonCollider2D poly = FindBoundaryPoly(area)
|
||||
?? area.GetComponentInChildren<PolygonCollider2D>(true);
|
||||
if (poly == null)
|
||||
BoxCollider box = FindBoundaryBox(area)
|
||||
?? area.GetComponentInChildren<BoxCollider>(true);
|
||||
if (box == null)
|
||||
{
|
||||
Debug.LogWarning($"[CameraAreaSetupTool] {area.name}:子节点中未找到 PolygonCollider2D。");
|
||||
Debug.LogWarning($"[CameraAreaSetupTool] {area.name}:子节点中未找到 BoxCollider。");
|
||||
return;
|
||||
}
|
||||
|
||||
SerializedObject so = new SerializedObject(area);
|
||||
so.FindProperty("_confinerCollider").objectReferenceValue = poly;
|
||||
so.FindProperty("_confinerCollider").objectReferenceValue = box;
|
||||
so.ApplyModifiedProperties();
|
||||
|
||||
Debug.Log($"[CameraAreaSetupTool] {area.name}:_confinerCollider → {poly.gameObject.name}");
|
||||
Debug.Log($"[CameraAreaSetupTool] {area.name}:_confinerCollider → {box.gameObject.name}");
|
||||
}
|
||||
|
||||
/// <summary>返回 area 子节点中第一个不含 CameraTriggerZone 的 PolygonCollider2D(即 AreaBoundary 限位体)。</summary>
|
||||
private static PolygonCollider2D FindBoundaryPoly(CameraArea area)
|
||||
/// <summary>返回 area 子节点中第一个不含 CameraTriggerZone 的 BoxCollider(即 AreaBoundary 限位体)。</summary>
|
||||
private static BoxCollider FindBoundaryBox(CameraArea area)
|
||||
{
|
||||
foreach (var p in area.GetComponentsInChildren<PolygonCollider2D>(true))
|
||||
if (p.GetComponent<CameraTriggerZone>() == null) return p;
|
||||
foreach (var b in area.GetComponentsInChildren<BoxCollider>(true))
|
||||
if (b.GetComponent<CameraTriggerZone>() == null) return b;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为指定 CameraArea 创建 AreaBoundary 子节点(默认矩形限位多边形,isTrigger = false)并绑定到 _confinerCollider。
|
||||
/// 为指定 CameraArea 创建 AreaBoundary 子节点(默认 BoxCollider 限位体)并绑定到 _confinerCollider。
|
||||
/// </summary>
|
||||
private static void CreateAreaBoundary(CameraArea area)
|
||||
{
|
||||
@@ -602,27 +691,20 @@ namespace BaseGames.Editor
|
||||
childGo.transform.localPosition = Vector3.zero;
|
||||
}
|
||||
|
||||
PolygonCollider2D poly = childGo.GetComponent<PolygonCollider2D>()
|
||||
?? childGo.AddComponent<PolygonCollider2D>();
|
||||
poly.isTrigger = true; // 限位多边形,仅作为相机约束边界,不产生物理碰撞
|
||||
poly.pathCount = 1;
|
||||
poly.SetPath(0, new Vector2[]
|
||||
{
|
||||
new Vector2(-12f, -6f),
|
||||
new Vector2(-12f, 6f),
|
||||
new Vector2( 12f, 6f),
|
||||
new Vector2( 12f, -6f),
|
||||
});
|
||||
BoxCollider box = childGo.GetComponent<BoxCollider>()
|
||||
?? childGo.AddComponent<BoxCollider>();
|
||||
box.center = new Vector3(0f, 0f, -10f); // Z 占位符,绑定 LensConfig 后点击「同步限位区域」更新
|
||||
box.size = new Vector3(24f, 12f, 1f); // 默认 24 × 12 占位符
|
||||
EditorUtility.SetDirty(childGo);
|
||||
|
||||
SerializedObject so = new SerializedObject(area);
|
||||
so.Update();
|
||||
so.FindProperty("_confinerCollider").objectReferenceValue = poly;
|
||||
so.FindProperty("_confinerCollider").objectReferenceValue = box;
|
||||
so.ApplyModifiedProperties();
|
||||
EditorUtility.SetDirty(area);
|
||||
|
||||
EditorGUIUtility.PingObject(childGo);
|
||||
Debug.Log($"[CameraAreaSetupTool] 已为 {area.name} 创建 AreaBoundary(矩形 24 × 12)。");
|
||||
Debug.Log($"[CameraAreaSetupTool] 已为 {area.name} 创建 AreaBoundary(BoxCollider 默认 24 × 12)。");
|
||||
}
|
||||
|
||||
/// <summary>返回所有以此 area 为激活目标的 CameraTriggerZone。</summary>
|
||||
@@ -642,18 +724,10 @@ namespace BaseGames.Editor
|
||||
/// <summary>为指定 CameraArea 创建配对的 CameraTriggerZone,自动匹配 Confiner 范围。</summary>
|
||||
private static void CreateTriggerZoneForArea(CameraArea area)
|
||||
{
|
||||
// 用 PolygonCollider2D 包围盒作为放置中心和尺寸;没有则退回到 area 自身位置
|
||||
Vector3 center = area.transform.position;
|
||||
Vector2 size = new Vector2(4f, 4f);
|
||||
|
||||
var poly = area.GetComponentInChildren<PolygonCollider2D>(true);
|
||||
if (poly != null)
|
||||
{
|
||||
Bounds b = poly.bounds;
|
||||
center = b.center;
|
||||
center.z = area.transform.position.z;
|
||||
size = new Vector2(b.size.x, b.size.y);
|
||||
}
|
||||
// 用 VisibleBounds 作为放置中心和初始多边形范围
|
||||
Rect visible = area.VisibleBounds;
|
||||
Vector3 center = new Vector3(visible.center.x, visible.center.y, area.transform.position.z);
|
||||
Vector2 half = visible.size * 0.5f;
|
||||
|
||||
var go = new GameObject($"{area.gameObject.name}_TriggerZone");
|
||||
Undo.RegisterCreatedObjectUndo(go, "Create CameraTriggerZone");
|
||||
@@ -661,20 +735,21 @@ namespace BaseGames.Editor
|
||||
go.transform.SetParent(area.transform);
|
||||
go.transform.position = center;
|
||||
|
||||
var col = go.AddComponent<PolygonCollider2D>();
|
||||
// [RequireComponent] 会自动附加 PolygonCollider2D;先 AddComponent<CameraTriggerZone>
|
||||
// 再通过 GetComponent 引用,避免顺序依赖问题
|
||||
var zone = go.AddComponent<CameraTriggerZone>();
|
||||
var col = go.GetComponent<PolygonCollider2D>();
|
||||
col.isTrigger = true;
|
||||
float hw = size.x * 0.5f;
|
||||
float hh = size.y * 0.5f;
|
||||
// 以 VisibleBounds 矩形四角为默认路径(可在 Inspector 中进一步编辑顶点)
|
||||
col.SetPath(0, new Vector2[]
|
||||
{
|
||||
new Vector2(-hw, -hh),
|
||||
new Vector2(-hw, hh),
|
||||
new Vector2( hw, hh),
|
||||
new Vector2( hw, -hh),
|
||||
new Vector2(-half.x, -half.y),
|
||||
new Vector2(-half.x, half.y),
|
||||
new Vector2( half.x, half.y),
|
||||
new Vector2( half.x, -half.y),
|
||||
});
|
||||
|
||||
var zone = go.AddComponent<CameraTriggerZone>();
|
||||
var so = new SerializedObject(zone);
|
||||
var so = new SerializedObject(zone);
|
||||
so.Update();
|
||||
so.FindProperty("_targetArea").objectReferenceValue = area;
|
||||
so.ApplyModifiedProperties();
|
||||
|
||||
Reference in New Issue
Block a user