Files
Cielonos/Assets/Scripts/MainGame/GameRun/Map/Zone/ZoneData.cs

424 lines
15 KiB
C#
Raw Normal View History

2026-02-13 09:22:11 -05:00
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]
2026-06-27 12:52:03 -04:00
public struct SpawnPointKey : IEquatable<SpawnPointKey>
2026-02-13 09:22:11 -05:00
{
[HorizontalGroup("Row")] [LabelText("Spawn Point")] [LabelWidth(90)]
public string group;
[HorizontalGroup("Row", Width = 50, MarginLeft = 10)] [HideLabel]
public int index;
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
public SpawnPointKey(string group, int index)
{
this.group = group;
this.index = index;
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
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);
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
public override string ToString() => $"{group}_{index}";
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
[System.Serializable]
[InlineProperty]
[HideReferenceObjectPicker]
public class EnemySpawnMapping
{
2026-03-20 12:07:44 -04:00
[HideInInspector] [System.NonSerialized]
2026-02-13 09:22:11 -05:00
public ZoneData parent;
2026-03-20 12:07:44 -04:00
[FormerlySerializedAs("Group")] [HorizontalGroup("SpawnInfo", 0.25f), HideLabel] [ReadOnly]
2026-02-13 09:22:11 -05:00
public string group;
2026-03-20 12:07:44 -04:00
[FormerlySerializedAs("Index")] [HorizontalGroup("SpawnInfo", 0.15f), HideLabel] [ReadOnly]
2026-02-13 09:22:11 -05:00
public int index;
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
[FormerlySerializedAs("EnemyID")]
[HorizontalGroup("SpawnInfo", 0.5f), HideLabel]
[ValueDropdown("GetEnemyKeys")]
[OnValueChanged("OnEnemyIDChanged")]
public string enemyID;
2026-03-20 12:07:44 -04:00
public EnemySpawnMapping()
{
}
2026-02-13 09:22:11 -05:00
public EnemySpawnMapping(SpawnPointKey key, string val, ZoneData parent)
{
group = key.group;
index = key.index;
enemyID = val;
this.parent = parent;
}
2026-03-20 12:07:44 -04:00
#if UNITY_EDITOR
2026-02-13 09:22:11 -05:00
private void OnEnemyIDChanged()
{
if (parent != null)
{
parent.UpdateDictionaryEntry(group, index, enemyID);
}
}
2026-03-20 12:07:44 -04:00
#endif
2026-02-13 09:22:11 -05:00
private IEnumerable<string> GetEnemyKeys()
{
2026-05-10 11:47:55 -04:00
return MapBaseCollection.Enemies.Keys;
2026-02-13 09:22:11 -05:00
}
}
}
public partial class ZoneData
{
2026-03-20 12:07:44 -04:00
#if UNITY_EDITOR
2026-02-13 09:22:11 -05:00
private static void AddButtonOnTitleBarGUI(Action buttonAction, EditorIcon icon = null)
{
icon ??= EditorIcons.MagnifyingGlass;
if (SirenixEditorGUI.ToolbarButton(icon))
{
buttonAction?.Invoke();
}
}
2026-03-20 12:07:44 -04:00
#endif
2026-02-13 09:22:11 -05:00
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
public partial class ZoneData
{
[Title("Scene Info")]
[ValueDropdown("GetAllSceneNames"), PropertyOrder(-1)]
[InfoBox("当前场景和ZoneData的关联场景不匹配", InfoMessageType.Error, VisibleIf = "@!IsCurrentSceneMatch")]
public string sceneName;
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
public bool IsCurrentSceneMatch => UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == sceneName;
2026-03-20 12:07:44 -04:00
#if UNITY_EDITOR
2026-02-13 09:22:11 -05:00
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);
}
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
return sceneNames;
}
2026-03-20 12:07:44 -04:00
#endif
2026-02-13 09:22:11 -05:00
}
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) };
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
public partial class ZoneData
{
2026-03-20 12:07:44 -04:00
[SerializeField, HideInInspector] public Dictionary<SpawnPointKey, string> enemySpawns = new Dictionary<SpawnPointKey, string>();
2026-02-13 09:22:11 -05:00
#if UNITY_EDITOR
2026-03-20 12:07:44 -04:00
[HideInInspector] private List<string> selectedGroupsToSync = new List<string>();
2026-02-13 09:22:11 -05:00
[Title("Enemy", HorizontalLine = false)]
[ShowInInspector]
[ListDrawerSettings(
OnTitleBarGUI = "DrawEnemySpawnTitleBarGUI",
ShowFoldout = true,
2026-03-20 12:07:44 -04:00
CustomRemoveIndexFunction = "RemoveEnemySpawnsIndex",
2026-02-13 09:22:11 -05:00
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;
}
2026-04-18 13:57:19 -04:00
2026-02-13 09:22:11 -05:00
EditorUtility.SetDirty(this);
}
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
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, "") : "";
}
}
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
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);
});
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
return list;
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
private void RemoveEnemySpawnsElement(EnemySpawnMapping element)
{
SpawnPointKey key = new SpawnPointKey(element.group, element.index);
if (enemySpawns.Remove(key))
{
EditorUtility.SetDirty(this);
}
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
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);
}
}
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
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);
}
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
private void DrawEnemySpawnTitleBarGUI() => AddButtonOnTitleBarGUI(OpenEnemySyncSelector);
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
private void OpenEnemySyncSelector()
{
ZoneManager zoneManager = ZoneManager.Instance;
zoneManager.RebuildMapData();
2026-03-20 12:07:44 -04:00
GroupSyncPopup popup = new GroupSyncPopup(zoneManager.spawnPoints.Keys, selectedGroupsToSync, (result) =>
2026-02-13 09:22:11 -05:00
{
selectedGroupsToSync = result;
OnEnemySyncSettingsChanged();
});
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
OdinEditorWindow window = OdinEditorWindow.InspectObject(popup);
float width = 400;
float height = 200;
var centerRect = GUIHelper.GetEditorWindowRect().AlignCenter(width, height);
window.position = centerRect;
2026-03-20 12:07:44 -04:00
window.ShowPopup();
2026-02-13 09:22:11 -05:00
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
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>();
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
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();
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
public class GroupItem
{
[HideLabel, TableColumnWidth(30, Resizable = false)]
public bool sync;
2026-03-20 12:07:44 -04:00
[HideLabel, ReadOnly] public string name;
2026-02-13 09:22:11 -05:00
public GroupItem(string name, bool sync)
{
this.name = name;
this.sync = sync;
}
}
}
#endif
}
2026-03-20 12:07:44 -04:00
2026-02-13 09:22:11 -05:00
public partial class ZoneData
{
2026-06-27 12:52:03 -04:00
/// <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>();
2026-03-20 12:07:44 -04:00
#if UNITY_EDITOR
2026-06-27 12:52:03 -04:00
/// <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));
}
2026-03-20 12:07:44 -04:00
#endif
2026-06-27 12:52:03 -04:00
}
2026-02-13 09:22:11 -05:00
}
public partial class ZoneData
{
2026-06-27 12:52:03 -04:00
/// <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>();
2026-03-20 12:07:44 -04:00
2026-06-27 12:52:03 -04:00
#if UNITY_EDITOR
/// <summary>在 Odin 列表中点击添加交互物生成定义时的自定义创建方法。</summary>
private void AddInteractableSpawn()
2026-02-13 09:22:11 -05:00
{
2026-06-27 12:52:03 -04:00
interactableSpawns.Add(new InteractableSpawnDefinition());
2026-02-13 09:22:11 -05:00
}
2026-06-27 12:52:03 -04:00
#endif
2026-02-13 09:22:11 -05:00
}
}