Files
Cielonos/Assets/Scripts/MainGame/GameRun/Map/Zone/ZoneData.cs
SoulliesOfficial 9a9e48f8a5
2026-06-27 12:52:03 -04:00

424 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using SLSUtilities.General;
using UnityEngine;
using UnityEngine.Serialization;
#if UNITY_EDITOR
using UnityEditor;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities.Editor;
#endif
namespace Cielonos.MainGame.Map
{
[CreateAssetMenu(fileName = "ZoneData", menuName = "Cielonos/MainGame/Map/ZoneData", order = 1)]
public partial class ZoneData : SerializedScriptableObject
{
[InlineProperty]
public struct SpawnPointKey : IEquatable<SpawnPointKey>
{
[HorizontalGroup("Row")] [LabelText("Spawn Point")] [LabelWidth(90)]
public string group;
[HorizontalGroup("Row", Width = 50, MarginLeft = 10)] [HideLabel]
public int index;
public SpawnPointKey(string group, int index)
{
this.group = group;
this.index = index;
}
public bool Equals(SpawnPointKey other)
{
return group == other.group && index == other.index;
}
public override bool Equals(object obj)
{
return obj is SpawnPointKey other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
return ((group != null ? group.GetHashCode() : 0) * 397) ^ index;
}
}
public static bool operator ==(SpawnPointKey left, SpawnPointKey right)
{
return left.Equals(right);
}
public static bool operator !=(SpawnPointKey left, SpawnPointKey right)
{
return !left.Equals(right);
}
public override string ToString() => $"{group}_{index}";
}
[System.Serializable]
[InlineProperty]
[HideReferenceObjectPicker]
public class EnemySpawnMapping
{
[HideInInspector] [System.NonSerialized]
public ZoneData parent;
[FormerlySerializedAs("Group")] [HorizontalGroup("SpawnInfo", 0.25f), HideLabel] [ReadOnly]
public string group;
[FormerlySerializedAs("Index")] [HorizontalGroup("SpawnInfo", 0.15f), HideLabel] [ReadOnly]
public int index;
[FormerlySerializedAs("EnemyID")]
[HorizontalGroup("SpawnInfo", 0.5f), HideLabel]
[ValueDropdown("GetEnemyKeys")]
[OnValueChanged("OnEnemyIDChanged")]
public string enemyID;
public EnemySpawnMapping()
{
}
public EnemySpawnMapping(SpawnPointKey key, string val, ZoneData parent)
{
group = key.group;
index = key.index;
enemyID = val;
this.parent = parent;
}
#if UNITY_EDITOR
private void OnEnemyIDChanged()
{
if (parent != null)
{
parent.UpdateDictionaryEntry(group, index, enemyID);
}
}
#endif
private IEnumerable<string> GetEnemyKeys()
{
return MapBaseCollection.Enemies.Keys;
}
}
}
public partial class ZoneData
{
#if UNITY_EDITOR
private static void AddButtonOnTitleBarGUI(Action buttonAction, EditorIcon icon = null)
{
icon ??= EditorIcons.MagnifyingGlass;
if (SirenixEditorGUI.ToolbarButton(icon))
{
buttonAction?.Invoke();
}
}
#endif
}
public partial class ZoneData
{
[Title("Scene Info")]
[ValueDropdown("GetAllSceneNames"), PropertyOrder(-1)]
[InfoBox("当前场景和ZoneData的关联场景不匹配", InfoMessageType.Error, VisibleIf = "@!IsCurrentSceneMatch")]
public string sceneName;
public bool IsCurrentSceneMatch => UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == sceneName;
#if UNITY_EDITOR
private List<string> GetAllSceneNames()
{
List<string> sceneNames = new List<string>();
foreach (var scene in EditorBuildSettings.scenes)
{
if (scene.enabled)
{
string name = System.IO.Path.GetFileNameWithoutExtension(scene.path);
sceneNames.Add(name);
}
}
return sceneNames;
}
#endif
}
public partial class ZoneData
{
private void CollectSpawnPoints(List<SpawnPointKey> list, string groupName)
{
ZoneManager zoneManager = ZoneManager.Instance;
zoneManager.RebuildMapData();
list.Clear();
if (zoneManager.spawnPoints.TryGetValue(groupName, out List<SpawnPoint> points))
{
for (int i = 0; i < points.Count; i++)
{
list.Add(new SpawnPointKey(groupName, i));
}
}
}
}
public partial class ZoneData
{
[Title("Spawn Settings")]
[Title("Player", HorizontalLine = false, Bold = false)]
[ListDrawerSettings(ShowIndexLabels = false, AddCopiesLastElement = true), PropertyOrder(10), LabelText("Player Spawns")]
public List<SpawnPointKey> playerSpawns = new List<SpawnPointKey>() { new SpawnPointKey("Player", 0) };
}
public partial class ZoneData
{
[SerializeField, HideInInspector] public Dictionary<SpawnPointKey, string> enemySpawns = new Dictionary<SpawnPointKey, string>();
#if UNITY_EDITOR
[HideInInspector] private List<string> selectedGroupsToSync = new List<string>();
[Title("Enemy", HorizontalLine = false)]
[ShowInInspector]
[ListDrawerSettings(
OnTitleBarGUI = "DrawEnemySpawnTitleBarGUI",
ShowFoldout = true,
CustomRemoveIndexFunction = "RemoveEnemySpawnsIndex",
CustomRemoveElementFunction = "RemoveEnemySpawnsElement")]
[LabelText("Enemy Spawns"), PropertyOrder(12)]
private List<EnemySpawnMapping> EnemySpawnsInEditor
{
get => GetSortedMappings();
set
{
enemySpawns = new Dictionary<SpawnPointKey, string>();
foreach (EnemySpawnMapping item in value)
{
enemySpawns[new SpawnPointKey(item.group, item.index)] = item.enemyID;
}
EditorUtility.SetDirty(this);
}
}
private void OnEnemySyncSettingsChanged()
{
ZoneManager zoneManager = ZoneManager.Instance;
zoneManager.RebuildMapData();
Dictionary<SpawnPointKey, string> newDict = new Dictionary<SpawnPointKey, string>();
foreach (string group in selectedGroupsToSync)
{
if (zoneManager.spawnPoints.TryGetValue(group, out List<SpawnPoint> points))
{
for (int i = 0; i < points.Count; i++)
{
SpawnPointKey key = new SpawnPointKey(group, i);
newDict[key] = enemySpawns != null ? enemySpawns.GetValueOrDefault(key, "") : "";
}
}
}
enemySpawns = newDict;
EditorUtility.SetDirty(this);
}
private List<EnemySpawnMapping> GetSortedMappings()
{
List<EnemySpawnMapping> list = new List<EnemySpawnMapping>();
if (enemySpawns == null) return list;
list.AddRange(enemySpawns.Select(kvp => new EnemySpawnMapping(kvp.Key, kvp.Value, this)));
list.Sort((a, b) =>
{
int comp = string.Compare(a.group, b.group, System.StringComparison.Ordinal);
if (comp != 0) return comp;
return a.index.CompareTo(b.index);
});
return list;
}
private void RemoveEnemySpawnsElement(EnemySpawnMapping element)
{
SpawnPointKey key = new SpawnPointKey(element.group, element.index);
if (enemySpawns.Remove(key))
{
EditorUtility.SetDirty(this);
}
}
private void RemoveEnemySpawnsIndex(int index)
{
var list = GetSortedMappings();
if (index >= 0 && index < list.Count)
{
EnemySpawnMapping item = list[index];
SpawnPointKey key = new SpawnPointKey(item.group, item.index);
if (enemySpawns.Remove(key))
{
EditorUtility.SetDirty(this);
}
}
}
public void UpdateDictionaryEntry(string group, int index, string newEnemyID)
{
SpawnPointKey key = new SpawnPointKey(group, index);
if (enemySpawns.ContainsKey(key))
{
enemySpawns[key] = newEnemyID;
EditorUtility.SetDirty(this);
}
}
private void DrawEnemySpawnTitleBarGUI() => AddButtonOnTitleBarGUI(OpenEnemySyncSelector);
private void OpenEnemySyncSelector()
{
ZoneManager zoneManager = ZoneManager.Instance;
zoneManager.RebuildMapData();
GroupSyncPopup popup = new GroupSyncPopup(zoneManager.spawnPoints.Keys, selectedGroupsToSync, (result) =>
{
selectedGroupsToSync = result;
OnEnemySyncSettingsChanged();
});
OdinEditorWindow window = OdinEditorWindow.InspectObject(popup);
float width = 400;
float height = 200;
var centerRect = GUIHelper.GetEditorWindowRect().AlignCenter(width, height);
window.position = centerRect;
window.ShowPopup();
}
private class GroupSyncPopup
{
[LabelText("Available Groups")]
[ListDrawerSettings(IsReadOnly = true, ShowIndexLabels = false, HideAddButton = true, HideRemoveButton = true)]
[TableList(AlwaysExpanded = true, HideToolbar = true)]
[ShowInInspector, PropertyOrder(1)]
public List<GroupItem> toggles = new List<GroupItem>();
private readonly System.Action<List<string>> onConfirm;
public GroupSyncPopup(IEnumerable<string> available, List<string> current, System.Action<List<string>> onConfirm)
{
this.onConfirm = onConfirm;
foreach (string name in available)
{
toggles.Add(new GroupItem(name, current.Contains(name)));
}
}
[Button, PropertyOrder(2)]
public void Confirm()
{
List<string> result = toggles.Where(t => t.sync).Select(t => t.name).ToList();
this.onConfirm?.Invoke(result);
GUIHelper.CurrentWindow?.Close();
}
public class GroupItem
{
[HideLabel, TableColumnWidth(30, Resizable = false)]
public bool sync;
[HideLabel, ReadOnly] public string name;
public GroupItem(string name, bool sync)
{
this.name = name;
this.sync = sync;
}
}
}
#endif
}
public partial class ZoneData
{
/// <summary>
/// 交互物生成定义。
/// 在单个 ZoneData 关卡数据中,配置某种特定的交互物(例如 ExitGate, MechanicalTable以及它在场景中对应的多个生成点。
/// </summary>
[HideReferenceObjectPicker]
public class InteractableSpawnDefinition
{
/// <summary>交互物的唯一 ID对应 MapBaseCollection.Interactables 中的 Key。</summary>
[ValueDropdown("GetInteractableIDs")] [LabelText("Interactable")]
[InlineButton("SyncFromGroup", SdfIconType.Search, "")]
public string interactableId;
/// <summary>该交互物在场景里配置的生成点列表。</summary>
[LabelText("Spawn Points")]
[ListDrawerSettings(ShowIndexLabels = false, CustomAddFunction = "AddSpawnPoint")]
public List<SpawnPointKey> spawnPoints = new List<SpawnPointKey>();
#if UNITY_EDITOR
/// <summary>
/// 从当前编辑的场景中,自动搜索并同步该交互物 ID 对应的所有场景 SpawnPoint。
/// </summary>
private void SyncFromGroup()
{
if (string.IsNullOrEmpty(interactableId)) return;
ZoneManager zoneManager = ZoneManager.Instance;
if (zoneManager == null) return;
// 强制重建场景的生成点缓存
zoneManager.RebuildMapData();
spawnPoints.Clear();
// 查找场景中组名与 interactableId 相同的所有生成点
if (zoneManager.spawnPoints.TryGetValue(interactableId, out List<SpawnPoint> points))
{
for (int i = 0; i < points.Count; i++)
{
spawnPoints.Add(new SpawnPointKey(interactableId, i));
}
}
Debug.Log($"[ZoneData] 成功为交互物 '{interactableId}' 同步了 {spawnPoints.Count} 个生成点。");
// 标记脏数据以确保保存修改
EditorUtility.SetDirty(zoneManager);
}
/// <summary>获取 MapBaseCollection 中注册的所有可用交互物 ID供下拉菜单选择。</summary>
private IEnumerable<string> GetInteractableIDs() => MapBaseCollection.Interactables.Keys;
/// <summary>添加新的生成点时的自定义初始方法。</summary>
private void AddSpawnPoint()
{
spawnPoints.Add(new ZoneData.SpawnPointKey(interactableId, 0));
}
#endif
}
}
public partial class ZoneData
{
/// <summary>
/// 配置本关卡中所有特定位置的交互物及其生成点的统一列表。
/// 替换了先前臃肿且不易扩展的零散 List 字段。
/// </summary>
[Title("Specified Locations", HorizontalLine = false)]
[PropertyOrder(20)]
[LabelText("Interactable Spawns")]
[ListDrawerSettings(ListElementLabelName = "interactableId", CustomAddFunction = "AddInteractableSpawn")]
public List<InteractableSpawnDefinition> interactableSpawns = new List<InteractableSpawnDefinition>();
#if UNITY_EDITOR
/// <summary>在 Odin 列表中点击添加交互物生成定义时的自定义创建方法。</summary>
private void AddInteractableSpawn()
{
interactableSpawns.Add(new InteractableSpawnDefinition());
}
#endif
}
}