角色能力,存档

This commit is contained in:
2026-05-19 11:50:21 +08:00
parent d25f237e76
commit 2dcb7a961a
136 changed files with 36035 additions and 27551 deletions

View File

@@ -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 视图中编辑顶点。",
"★ 限位体积:选中子节点的 BoxColliderInspector 中编辑 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} 创建 AreaBoundaryBoxCollider 默认 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();