NLDClient-yudde/ProjectNLD/Assets/Editor/LevelEditor/LevelEditorWindow.MainLife.cs

1550 lines
54 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using TGS;
using UnityEditor;
using UnityEngine;
using System.Linq;
using Gameplay.Level;
using cfg;
using cfg.LevelCfg;
using Gameplay;
using Gameplay.Character;
using Gameplay.Utils;
using System.Collections.Generic;
using System.IO;
using static Gameplay.Level.LevelData;
public partial class LevelEditorWindow
{
#region MainLife
private void Awake()
{
SceneView.duringSceneGui += OnSceneGUI;
}
protected override async void OnEnable()
{
await TerrainTypeConfig.Load();
_enemyNpcObjectWaveDic = new Dictionary<int, Dictionary<NpcEditorInfo, GameObject>>();
_midNpcObjectWaveDic = new Dictionary<int, Dictionary<NpcEditorInfo, GameObject>>();
_selfNpcObjectDic = new Dictionary<Vector2Int, GameObject>();
_showNpcInfos = new List<NpcEditorInfo>();
_blockPrefabDic = new Dictionary<int, GameObject>();
base.OnEnable();
}
protected override void OnDisable()
{
OnSetRetreatRegionColor();
OnCloseCaptureRegionColor();
ClearShowRegionColor();
StopMonitoringSphere();
ShowHiddenObjects();
SaveBlockSphereInfo();
DestroyBlockSphere();
currWindow = null;
DestroyNpcPrefab();
_enemyNpcObjectWaveDic = null;
_midNpcObjectWaveDic = null;
_selfNpcObjectDic = null;
_blockPrefabDic = null;
base.OnDisable();
}
protected override void OnDestroy()
{
OnSetRetreatRegionColor();
OnCloseCaptureRegionColor();
ClearShowRegionColor();
StopMonitoringSphere();
ShowHiddenObjects();
SaveBlockSphereInfo();
DestroyBlockSphere();
DestroyAllBlockObj();
ClearMinesRegionColor();
ClearBlockRegionColor();
ClearMonsterTriggerColor();
base.OnDestroy();
if (_curMap != null)
_curMap.TerrainGridSystem.OnMouseClickOnGrid -= OnCellClick;
SaveCurrentLevel();
SceneView.duringSceneGui -= OnSceneGUI;
}
protected override void OnGUI()
{
base.OnGUI();
if (_levelEditor == null || _mapManager == null || _mapManager.Map != _curMap ||
_levelEditor.LevelData != currentLevelData)
{
Debug.LogError("数据已变更,请重新打开关卡编辑器");
Close();
}
}
#endregion
#region 点击添加事件
private bool ResponseClickOnCell =>
isEditingEnemyNpc || isEditingSelfNpc || isEditingMidNpc || isEditingNpcPos || isEditingCaptureInfo ||
isEditingFallbackReusable || isEditingRetreat || isEditingRoadInfo || isEditingAreaInfo || canEditorBlockArea ||
canEditorMonsterTrigger || canEditorMinesArea || canEditorSearchLight || canEditorHoldFlagArea;
private void OnCellClick(TerrainGridSystem tgs, int cellIndex, int buttonIndex)
{
if (!ResponseClickOnCell) return;
var clickPosition = _curMap.TGSCellIndex2BlockPosition(cellIndex);
int leftButton = 0;
int rightButton = 1;
if (buttonIndex != leftButton && buttonIndex != rightButton)
return;
bool opTrue = buttonIndex == leftButton;
if (isEditingRetreat)
{
EditingRetreat(cellIndex, opTrue);
}
else if (isEditingFallbackReusable)
{
EditingFallBack(clickPosition, opTrue);
}
else if (isEditingCaptureInfo)
{
EditingCapture(cellIndex, opTrue);
}
else if (isEditingNpcPos)
{
if (opTrue)
ChangeNpcPos(clickPosition, npcTroopId);
}
else if (isEditingEnemyNpc)
{
EditEnemyNpc(clickPosition, opTrue);
}
else if (isEditingSelfNpc)
{
EditSelfNpc(clickPosition, opTrue);
}
else if (isEditingMidNpc)
{
EditMidNpc(clickPosition, opTrue);
}
else if (isEditingRoadInfo && isEditingRoad)
{
EditRoad(cellIndex, opTrue);
}
else if (isEditingAreaInfo && isEditingArea)
{
EditArea(cellIndex, opTrue);
}
else if (_isEditorSingleMinesRegion && _currEditMinesRegionArea != null)
{
EditingMinesRegion(cellIndex, opTrue);
}
else if (_isEditorSingleBlockRegion && _currEditBlockRegion != null)
{
EditingBlockRegion(cellIndex, opTrue);
}
else if (_isEditorSingleDefenseRegion && _currEditDefenseRegion != null)
{
EditingDefenseRegion(cellIndex, opTrue);
}
else if (_isEditorSingleSearchLight && _currEditSearchLight != null)
{
EditingSearchLight(cellIndex, opTrue);
}
else if (_isEditorSingleHoldFlag && _currEditHoldFlagInfo != null)
{
EditingHoldFlag(cellIndex, opTrue);
}
}
private void EditingMinesRegion(int clickPosition, bool isAdd)
{
if (isAdd)
_currEditMinesRegionArea.AddPosition(clickPosition);
else
_currEditMinesRegionArea.RemovePosition(clickPosition);
}
private void EditingBlockRegion(int clickPosition, bool isAdd)
{
if (isAdd)
_currEditBlockRegion.AddPosition(clickPosition);
else
_currEditBlockRegion.RemovePosition(clickPosition);
}
private void EditingDefenseRegion(int clickPosition, bool isAdd)
{
if (isAdd)
_currEditDefenseRegion.AddPosition(clickPosition);
else
_currEditDefenseRegion.RemovePosition(clickPosition);
}
private void EditingSearchLight(int clickPosition, bool isAdd)
{
if (isAdd)
_currEditSearchLight.AddPosition(clickPosition);
else
_currEditSearchLight.RemovePosition(clickPosition);
}
private void EditingHoldFlag(int clickPosition, bool isAdd)
{
if (isAdd)
_currEditHoldFlagInfo.AddPosition(clickPosition);
else
_currEditHoldFlagInfo.RemovePosition(clickPosition);
}
private void EditingFallBack(Vector2Int clickPosition, bool isAdd)
{
if (isAdd && !enemyRetreatEditorInfo.fallbackReusablePoints.Contains(clickPosition))
{
DebugUtil.Log("复用撤离点添加: {0}", clickPosition);
enemyRetreatEditorInfo.fallbackReusablePoints.Add(clickPosition);
}
else if (!isAdd && enemyRetreatEditorInfo.fallbackReusablePoints.Contains(clickPosition))
{
DebugUtil.Log("复用撤离点移除: {0}", clickPosition);
enemyRetreatEditorInfo.fallbackReusablePoints.Remove(clickPosition);
}
}
private void EditingRetreat(int clickPosition, bool isAdd)
{
if (isAdd && retreatEditorInfo.All(x => x.retreatPoint != clickPosition))
{
retreatEditorInfo.Add(new RetreatEditorInfo
{
retreatPoint = clickPosition,
retreatFlag = false,
});
_curMap.TerrainGridSystem.CellToggleRegionSurface(clickPosition, true, RetreatRegionColor);
DebugUtil.Log("撤离区添加: {0}", clickPosition);
}
else if (!isAdd && retreatEditorInfo.Any(x => x.retreatPoint == clickPosition))
{
retreatEditorInfo =
retreatEditorInfo.Where(x => x.retreatPoint != clickPosition).ToList();
_curMap.TerrainGridSystem.CellHideRegionSurface(clickPosition);
DebugUtil.Log("撤离区移除: {0}", clickPosition);
}
}
private void EditingCapture(int clickPosition, bool isAdd)
{
if (_currEditingCaptureInfo == null) return;
if (isAdd && _currEditingCaptureInfo.capturePointEditorInfos.All(x => x.capturePoint != clickPosition))
{
_currEditingCaptureInfo.capturePointEditorInfos.Add(new CaptureEditorInfo.CapturePointEditorInfo
{
belongRegionID = _currEditingCaptureInfo.captureRegionID,
capturePoint = clickPosition,
captureFlag = false,
});
_curMap.TerrainGridSystem.CellToggleRegionSurface(clickPosition, true, CaptureRegionColor);
DebugUtil.Log($"占领区{_currEditingCaptureInfo.captureRegionID} 添加: {clickPosition}");
}
else if (!isAdd && _currEditingCaptureInfo.capturePointEditorInfos.Any(x => x.capturePoint == clickPosition))
{
_currEditingCaptureInfo.capturePointEditorInfos =
_currEditingCaptureInfo.capturePointEditorInfos.Where(x => x.capturePoint != clickPosition).ToList();
_curMap.TerrainGridSystem.CellHideRegionSurface(clickPosition);
DebugUtil.Log($"占领区{_currEditingCaptureInfo.captureRegionID} 移除: {clickPosition}");
}
}
private void ChangeNpcPos(Vector2Int clickPosition, int npcTroopID)
{
if (null == _currNpcEditorInfo)
return;
var oldPos = _currEditingNpcInfo.position;
if (oldPos == clickPosition)
return;
var newWorldPos = _curMap.BlockPosition2WorldPosition(clickPosition);
if (ETroopsId.Enemy == npcTroopID &&
_enemyNpcObjectWaveDic.TryGetValue(_currEditingNpcInfo.waveIndex, out var enemyNpcObjectDic))
{
if (enemyNpcObjectDic.TryGetValue(_currNpcEditorInfo, out var npcObj))
{
npcObj.transform.position = newWorldPos;
_currEditingNpcInfo.position = clickPosition;
DebugUtil.Log("怪物: [ {0} ] 移动到 [ {1} ]位置上", _currEditingNpcInfo.id, clickPosition);
}
}
else if (ETroopsId.Neutral == npcTroopID &&
_midNpcObjectWaveDic.TryGetValue(_currEditingNpcInfo.waveIndex, out var midNpcObjectDic))
{
if (midNpcObjectDic.TryGetValue(_currNpcEditorInfo, out var npcObj))
{
npcObj.transform.position = newWorldPos;
_currEditingNpcInfo.position = clickPosition;
DebugUtil.Log("怪物: [ {0} ] 移动到 [ {1} ]位置上", _currEditingNpcInfo.id, clickPosition);
}
}
else if (ETroopsId.Self == npcTroopID && _selfNpcObjectDic.TryGetValue(oldPos, out var selfNpcObj))
{
selfNpcObj.transform.position = newWorldPos;
_currEditingNpcInfo.position = clickPosition;
_selfNpcObjectDic.Remove(oldPos);
_selfNpcObjectDic.Add(clickPosition, selfNpcObj);
DebugUtil.Log("怪物: [ {0} ] 移动到 [ {1} ]位置上", _currEditingNpcInfo.id, clickPosition);
}
else
{
DebugUtil.LogError("需要更改的旧位置上没有怪物:{0}", oldPos);
}
}
#region 敌方NPC编辑方法
private void EditEnemyNpc(Vector2Int clickPosition, bool isAdd)
{
if (isAdd)
{
AddEnemyNpc(clickPosition);
}
else
{
if (Event.current != null && (Event.current.control || Event.current.command))
{
RemoveEnemyNpc(clickPosition);
}
else
{
Debug.LogWarning("删除NPC需要按住Ctrl键");
}
}
}
private void AddEnemyNpc(Vector2Int clickPosition)
{
if (enemyNpcObject == null || curEnemyMonsterID == 0)
{
DebugUtil.LogError("请选择正确的NPC");
return;
}
_enemyNpcObjectWaveDic ??= new Dictionary<int, Dictionary<NpcEditorInfo, GameObject>>();
NPCInfo npcInfo = new()
{
id = curEnemyMonsterID,
position = clickPosition,
index = currentEnemyNpcInfo.Count + 1,
troopId = ETroopsId.Enemy,
};
var level = 0;
var aiType = 0;
var monsterName = "";
var monsterConfigDic = MonsterCfg.DataMap;
if (monsterConfigDic.TryGetValue(curEnemyMonsterID, out var monster))
{
level = monster.MonsterLevel;
aiType = monster.AIType;
monsterName = monster.NameRead;
}
else
{
DebugUtil.LogError("获取怪物[{0}]配置数据错误", curEnemyMonsterID);
}
var npcEditor = new NpcEditorInfo(npcInfo, currentEnemyNpcInfo.Count + 1, level, aiType, ETroopsId.Enemy,
monsterName);
currentEnemyNpcInfo.Add(npcEditor);
var pos = _curMap.BlockPosition2WorldPosition(npcInfo.position);
if (_enemyNpcObjectWaveDic.TryGetValue(npcInfo.waveIndex, out var npcObjectDic))
{
var obj = Instantiate(enemyNpcObject, pos, enemyNpcObject.transform.rotation);
if (enemyObjRoot != null)
obj.transform.SetParent(enemyObjRoot.transform);
npcObjectDic.Add(npcEditor, obj);
}
else
{
var obj = Instantiate(enemyNpcObject, pos, enemyNpcObject.transform.rotation);
if (enemyObjRoot != null)
obj.transform.SetParent(enemyObjRoot.transform);
npcObjectDic = new Dictionary<NpcEditorInfo, GameObject>
{ { npcEditor, obj } };
_enemyNpcObjectWaveDic.Add(npcInfo.waveIndex, npcObjectDic);
}
DebugUtil.Log(
$"添加了第[ {npcInfo.waveIndex} ]波的[ {npcInfo.index} ]怪物到 [ {npcInfo.position} ]位置上, 其预制体为: {enemyNpcObject.name}");
RefreshEnemyNpcGroups();
}
private void RemoveEnemyNpc(Vector2Int clickPosition)
{
var npc = currentEnemyNpcInfo.LastOrDefault(npc => npc.npcInfo.position == clickPosition);
if (npc != null)
{
if (_enemyNpcObjectWaveDic.TryGetValue(npc.npcInfo.waveIndex, out var npcObjectDic))
{
if (npcObjectDic.TryGetValue(npc, out var obj))
{
DebugUtil.Log(
$"从[ {clickPosition} ]位置上移除了[ {npc.npcInfo.waveIndex} ]波的[{npc.id}]NPC, 其预制体名字为:{obj.name}");
DestroyImmediate(obj);
npcObjectDic.Remove(npc);
}
else
{
DebugUtil.LogError($"波次{npc.npcInfo.waveIndex}中没有怪物{npc.id}");
}
}
else
{
DebugUtil.LogError("没有波次{0}的怪物信息", npc.npcInfo.waveIndex);
}
currentEnemyNpcInfo.Remove(npc);
}
ReorderEnemyNpcID();
RefreshEnemyNpcGroups();
}
#endregion
#region 已方NPC编辑方法
private void EditSelfNpc(Vector2Int clickPosition, bool isAdd)
{
if (isAdd)
{
AddSelfNpc(clickPosition);
}
else
{
if (Event.current != null && (Event.current.control || Event.current.command))
{
RemoveSelfNpc(clickPosition);
}
else
{
Debug.LogWarning("删除NPC需要按住Ctrl键");
}
}
}
private void AddSelfNpc(Vector2Int clickPosition)
{
if (selfNpcObject == null || curSelfNpcID == 0)
{
DebugUtil.LogError("请选择正确的己方NPC");
return;
}
_selfNpcObjectDic ??= new Dictionary<Vector2Int, GameObject>();
if (_selfNpcObjectDic.ContainsKey(clickPosition))
{
DebugUtil.LogError("该位置已有NPC存在请重新选");
return;
}
NPCInfo npcInfo = new()
{
id = curSelfNpcID,
position = clickPosition,
troopId = ETroopsId.Self,
};
var level = 0;
var aiType = 0;
var monsterName = "";
var monsterConfigDic = MonsterCfg.DataMap;
if (monsterConfigDic.TryGetValue(curSelfNpcID, out var monster))
{
level = monster.MonsterLevel;
aiType = monster.AIType;
monsterName = monster.NameRead;
}
else
{
DebugUtil.LogError("获取己方Npc[{0}]配置数据错误", curSelfNpcID);
}
currentSelfNpcInfo.Add(new NpcEditorInfo(npcInfo, SelfID + currentSelfNpcInfo.Count + 1, level, aiType,
ETroopsId.Self, monsterName));
var pos = _curMap.BlockPosition2WorldPosition(npcInfo.position);
var obj = Instantiate(selfNpcObject, pos, selfNpcObject.transform.rotation);
if (selfObjRoot != null)
{
obj.transform.SetParent(selfObjRoot.transform);
}
_selfNpcObjectDic.Add(npcInfo.position, obj);
DebugUtil.Log("添加了己方Npc: [ {0} ] 到 [ {1} ]位置上, 其预制体为: {2}", npcInfo.id, npcInfo.position, selfNpcObject.name);
}
private void RemoveSelfNpc(Vector2Int clickPosition)
{
if (_selfNpcObjectDic.TryGetValue(clickPosition, out var npcObj))
{
var npc = currentSelfNpcInfo.FirstOrDefault(npcFight => npcFight.npcInfo.position == clickPosition);
if (npc != null)
{
currentSelfNpcInfo.Remove(npc);
DebugUtil.Log("从[ {0} ]位置上移除了[{1}] 己方NPC", clickPosition, npcObj.name);
DestroyImmediate(npcObj);
_selfNpcObjectDic.Remove(clickPosition);
}
}
ReorderSelfNpcID();
}
#endregion
#region 中立NPC编辑方法
private void EditMidNpc(Vector2Int clickPosition, bool isAdd)
{
if (isAdd)
AddMidNpc(clickPosition);
else
{
if (Event.current != null && (Event.current.control || Event.current.command))
{
RemoveMidNpc(clickPosition);
}
else
{
Debug.LogWarning("删除NPC需要按住Ctrl键");
}
}
}
private void AddMidNpc(Vector2Int clickPosition)
{
if (midNpcObject == null || curMidMonsterID == 0)
{
DebugUtil.LogError("请选择正确的NPC");
return;
}
_midNpcObjectWaveDic ??= new Dictionary<int, Dictionary<NpcEditorInfo, GameObject>>();
NPCInfo npcInfo = new()
{
id = curMidMonsterID,
position = clickPosition,
index = currentMidNpcInfo.Count + 1,
troopId = ETroopsId.Neutral,
isOpenInvestigate = false,
};
var level = 0;
var aiType = 0;
var monsterName = "";
var monsterConfigDic = MonsterCfg.DataMap;
if (monsterConfigDic.TryGetValue(curMidMonsterID, out var monster))
{
level = monster.MonsterLevel;
aiType = monster.AIType;
monsterName = monster.NameRead;
}
else
{
DebugUtil.LogError("获取怪物[{0}]配置数据错误", curMidMonsterID);
}
var npcEditor = new NpcEditorInfo(npcInfo, MidID + currentMidNpcInfo.Count + 1, level, aiType, ETroopsId.Neutral,
monsterName);
currentMidNpcInfo.Add(npcEditor);
var pos = _curMap.BlockPosition2WorldPosition(npcInfo.position);
if (_midNpcObjectWaveDic.TryGetValue(npcInfo.waveIndex, out var npcObjectDic))
{
var obj = Instantiate(midNpcObject, pos, midNpcObject.transform.rotation);
if (midObjRoot != null)
obj.transform.SetParent(midObjRoot.transform);
npcObjectDic.Add(npcEditor, obj);
}
else
{
var obj = Instantiate(midNpcObject, pos, midNpcObject.transform.rotation);
if (midObjRoot != null)
obj.transform.SetParent(midObjRoot.transform);
npcObjectDic = new Dictionary<NpcEditorInfo, GameObject>
{ { npcEditor, obj } };
_midNpcObjectWaveDic.Add(npcInfo.waveIndex, npcObjectDic);
}
DebugUtil.Log(
$"添加了第[ {npcInfo.waveIndex} ]波的[ {npcInfo.index} ] 中立怪物到 [ {npcInfo.position} ]位置上, 其预制体为: {midNpcObject.name}");
}
private void RemoveMidNpc(Vector2Int clickPosition)
{
var npc = currentMidNpcInfo.LastOrDefault(npc => npc.npcInfo.position == clickPosition);
if (npc != null)
{
if (_midNpcObjectWaveDic.TryGetValue(npc.npcInfo.waveIndex, out var npcObjectDic))
{
if (npcObjectDic.TryGetValue(npc, out var obj))
{
DebugUtil.Log(
$"从[ {clickPosition} ]位置上移除了[ {npc.npcInfo.waveIndex} ]波的[{npc.id}] 中立NPC, 其预制体名字为:{obj.name}");
DestroyImmediate(obj);
npcObjectDic.Remove(npc);
}
else
{
DebugUtil.LogError($"波次{npc.npcInfo.waveIndex}中没有怪物{npc.id}");
}
}
else
{
DebugUtil.LogError("没有波次{0}的怪物信息", npc.npcInfo.waveIndex);
}
currentMidNpcInfo.Remove(npc);
}
ReorderMidNpcID();
}
#endregion
/// <summary>
/// 编辑路线
/// </summary>
/// <param name="clickPosition"></param>
/// <param name="isAdd"></param>
private void EditRoad(int clickPosition, bool isAdd)
{
if (_currEditingRoadInfo == null) return;
if (isAdd && !_currEditingRoadInfo.roadPoints.Any(x => x.cellIndex == clickPosition))
{
_currEditingRoadInfo.roadPoints.Add(new PatrolInfo()
{
cellIndex = clickPosition,
waitTime = 0f,
});
}
else if (!isAdd && _currEditingRoadInfo.roadPoints.Any(x => x.cellIndex == clickPosition))
{
_currEditingRoadInfo.roadPoints =
_currEditingRoadInfo.roadPoints.Where(x => x.cellIndex != clickPosition).ToList();
}
}
/// <summary>
/// 编辑区域
/// </summary>
/// <param name="clickPosition"></param>
/// <param name="isAdd"></param>
private void EditArea(int clickPosition, bool isAdd)
{
if (null == _currEditingAreaInfo)
return;
if (isAdd && !_currEditingAreaInfo.areaPoints.Any(x => x.cellIndex == clickPosition))
{
_currEditingAreaInfo.areaPoints.Add(new PatrolInfo()
{
cellIndex = clickPosition,
waitTime = 0f,
});
}
else if (!isAdd && _currEditingAreaInfo.areaPoints.Any(x => x.cellIndex == clickPosition))
{
_currEditingAreaInfo.areaPoints =
_currEditingAreaInfo.areaPoints.Where(x => x.cellIndex != clickPosition).ToList();
}
}
#endregion
#region 加载
private void LoadLevel(LevelData levelData)
{
DestroyNpcPrefab();
_enemyNpcObjectWaveDic.Clear();
_midNpcObjectWaveDic.Clear();
_showNpcInfos.Clear();
_selfNpcObjectDic.Clear();
currentLevelData = levelData;
_mapManager = FindObjectOfType<MapManager>();
_curMap = _mapManager.Map;
_curMap.TerrainGridSystem.OnMouseClickOnGrid += OnCellClick;
// 加载关卡任务
taskInfos = currentLevelData.taskInfos.Select(t => new TaskInfoEditor(t)).ToList();
// 加载评分条件
scoreTasks = currentLevelData.scoreTaskID.Select(s => new ScoreTaskEditor(s)).ToList();
// 加载触发器
levelTriggerData = currentLevelData.levelTriggerDatas.Select(e => new LevelTriggerDataEditor(e)).ToList();
//加载地区信息
curRegionData = new RegionData
{
preparingRegionId = currentLevelData.preparingRegionId,
preparingRegionToward = currentLevelData.preparingRegionToward,
pvpDefendRegionId = currentLevelData.pvpDefendRegionId,
pvpDefendToward = currentLevelData.pvpDefendToward
};
// 加载占领数据
captureEditorInfos.Clear();
foreach (var captureInfo in currentLevelData.CapturePoints)
{
var captureEditor = new CaptureEditorInfo()
{
captureRegionID = captureInfo.captureRegionID,
captureProgress = captureInfo.CaptureProgress,
captureEffectID = captureInfo.captureEffectID,
captureEffectPoint = captureInfo.captureEffectPoint,
};
foreach (var point in captureInfo.capturePoints)
{
var capturePoint = new CaptureEditorInfo.CapturePointEditorInfo()
{
belongRegionID = captureInfo.captureRegionID,
capturePoint = point,
captureFlag = point == captureInfo.captureFlag,
};
captureEditor.capturePointEditorInfos.Add(capturePoint);
}
captureEditorInfos.Add(captureEditor);
}
DestroyAllBlockObj();
UpdateMinesRegion();
UpdateBlockRegion();
UpdateMonsterTrigger();
// 加载撤离数据
retreatEditorInfo.Clear();
foreach (var point in currentLevelData.retreatPoints.retreatPoints)
{
var retreatInfo = new RetreatEditorInfo()
{
retreatPoint = point,
retreatFlag = point == currentLevelData.retreatPoints.retreatFlag,
};
retreatEditorInfo.Add(retreatInfo);
}
// 加载敌人撤离数据
enemyRetreatEditorInfo = new EnemyRetreatEditorInfo();
foreach (var fallback in currentLevelData.enemyRetreatReusablePoints)
{
var pos = _curMap.TGSCellIndex2BlockPosition(fallback);
enemyRetreatEditorInfo.fallbackReusablePoints.Add(pos);
}
// 加载指定路线
roadEditorInfos = currentLevelData.patrolRoadData.Select(roadInfo => new PrescribedRoadEditorInfo
{
roadID = roadInfo.id,
roadPoints = roadInfo.patrolInfos,
}).ToList();
// 加载指定区域
areaEditorInfos = currentLevelData.patrolRegionData.Select(areaInfo => new PrescribedAreaEditorInfo
{
areaID = areaInfo.id,
areaPoints = areaInfo.patrolInfos,
}).ToList();
// 加载关卡进度数据
progressGuideDatas = currentLevelData.progressGuideDatas.Select(p => new ProgressGuideDataEditor(p)).ToList();
// 加载关卡引导立标数据
setFlagEditorInfos = currentLevelData.setFlagGuideDatas.Select(f => new SetFlagGuideDataEditor(f)).ToList();
// 加载信息板数据
infoPanelGuideDatas =
currentLevelData.infoPanelGuideDatas.Select(i => new InfoPanelGuideDataEditor(i)).ToList();
// 加载关卡广播
listAIBroadcastData = currentLevelData.listAIBroadcastData.Select(a => new AIBroadcastDataEditor(a)).ToList();
// 加载雷信息
radarInfoList = currentLevelData.radarInfos.Select(r => new RadarEditorInfo(r)).ToList();
// 探照灯
searchLightList = currentLevelData.searchLightInfos.Select(s => new SearchLightEditorInfo(s)).ToList();
// 轰炸区
bombAreaInfoList = currentLevelData.bombAreaInfos.Select(b => new BombAreaInfoEditor(b)).ToList();
// 战旗
holdFlagEditorList = currentLevelData.holdGFlagInfos.Select(h => new HoldFlagEditorInfo(h))
.ToList();
// 加载当前关卡的Monster配置
LoadAllNpcConfig();
// 加载敌方NPC到场景中
LoadEnemyNpcInfo();
// 加载中立NPC到场景中
LoadMidNpcInfo();
// 加载己方NPC到场景中
LoadSelfNpcInfo();
// 加载波次信息
LoadWaveInfo();
// 加载阻挡球
LoadBlockSphereInfo();
}
private void LoadAllNpcConfig()
{
var monsterConfigDic = MonsterCfg.DataList;
var levelName = Path.GetFileNameWithoutExtension(_levelEditor.LevelDataJson.name);
_allMonsterIDs.Clear();
foreach (var dataMonster in monsterConfigDic)
{
if (!dataMonster.LevelJsonName.EndsWith(levelName))
continue;
var monsterName = $"{dataMonster.MonsterID} - {dataMonster.NameRead}";
_allMonsterIDs.Add(monsterName, dataMonster.MonsterID);
}
}
private void LoadEnemyNpcInfo()
{
var monsterConfigDic = MonsterCfg.DataMap;
currentEnemyNpcInfo = new();
var index = 1;
foreach (var npcInfo in currentLevelData.npcInfos)
{
if (!monsterConfigDic.TryGetValue(npcInfo.id, out var monsterClassData))
{
DebugUtil.LogError("请检查配置,没有 {0} 的怪物类ID", npcInfo.id);
continue;
}
var npcEditor = new NpcEditorInfo(npcInfo, index, monsterClassData.MonsterLevel, monsterClassData.AIType,
ETroopsId.Enemy, monsterClassData.NameRead);
currentEnemyNpcInfo.Add(npcEditor);
var npcObj = LoadEnemyNpcPrefab(npcInfo.id);
if (npcObj != null)
{
var setPos = _curMap.BlockPosition2WorldPosition(npcInfo.position);
var yaw = LevelUtils.SetCharaterToward(npcInfo.npcToward);
var obj = Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0));
if (enemyObjRoot == null)
enemyObjRoot = new GameObject("enemyObjRoot");
if (obj != null)
obj.transform.SetParent(enemyObjRoot.transform);
if (_enemyNpcObjectWaveDic.TryGetValue(npcInfo.waveIndex, out var npcObjectDic))
{
npcObjectDic.Add(npcEditor, obj);
}
else
{
npcObjectDic = new Dictionary<NpcEditorInfo, GameObject>
{
{ npcEditor, obj }
};
_enemyNpcObjectWaveDic.Add(npcInfo.waveIndex, npcObjectDic);
}
}
index++;
}
// 敌方 NPC 加载完成后刷新一次分组视图
RefreshEnemyNpcGroups();
}
private void LoadMidNpcInfo()
{
var monsterConfigDic = MonsterCfg.DataMap;
currentMidNpcInfo = new();
var index = MidID + 1;
foreach (var npcInfo in currentLevelData.midNpcInfos)
{
if (!monsterConfigDic.TryGetValue(npcInfo.id, out var monsterClassData))
{
DebugUtil.LogError("请检查配置,没有 {0} 的怪物类ID", npcInfo.id);
continue;
}
var npcEditor = new NpcEditorInfo(npcInfo, index, monsterClassData.MonsterLevel, monsterClassData.AIType,
ETroopsId.Neutral, monsterClassData.NameRead);
currentMidNpcInfo.Add(npcEditor);
var npcObj = LoadMidNpcPrefab(npcInfo.id);
if (npcObj != null)
{
var setPos = _curMap.BlockPosition2WorldPosition(npcInfo.position);
var yaw = LevelUtils.SetCharaterToward(npcInfo.npcToward);
var obj = Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0));
if (midObjRoot == null)
midObjRoot = new GameObject("midObjRoot");
if (obj != null)
obj.transform.SetParent(midObjRoot.transform);
if (_midNpcObjectWaveDic.TryGetValue(npcInfo.waveIndex, out var npcObjectDic))
{
npcObjectDic.Add(npcEditor, obj);
}
else
{
npcObjectDic = new Dictionary<NpcEditorInfo, GameObject>
{ { npcEditor, obj } };
_midNpcObjectWaveDic.Add(npcInfo.waveIndex, npcObjectDic);
}
}
index++;
}
}
private void LoadSelfNpcInfo()
{
var monsterConfigDic = MonsterCfg.DataMap;
currentSelfNpcInfo = new();
var index = SelfID + 1;
foreach (var selfNpcInfo in currentLevelData.selfNpcInfos)
{
if (!monsterConfigDic.TryGetValue(selfNpcInfo.id, out var monsterClassData))
{
DebugUtil.LogError("请检查配置,没有 {0} 的怪物类ID, 已经删除对应", selfNpcInfo.id);
continue;
}
var npcEditor = new NpcEditorInfo(selfNpcInfo, index, monsterClassData.MonsterLevel,
monsterClassData.AIType, ETroopsId.Self, monsterClassData.NameRead);
currentSelfNpcInfo.Add(npcEditor);
var npcObj = LoadSelfNpcPrefab(selfNpcInfo.id);
if (npcObj != null)
{
var setPos = _curMap.BlockPosition2WorldPosition(selfNpcInfo.position);
var yaw = LevelUtils.SetCharaterToward(selfNpcInfo.npcToward);
var obj = Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0));
if (selfObjRoot == null)
selfObjRoot = new GameObject("selfObjRoot");
if (obj != null)
obj.transform.SetParent(selfObjRoot.transform);
_selfNpcObjectDic.Add(selfNpcInfo.position, obj);
}
index++;
}
}
#endregion
#region 检查关卡
private const string PreparingRegionErrorMessage = "关卡出战区设置错误,该地图没有:<{0}>号地区。\n";
private const string FixedMessage = "已将错误区域设置为默认<0>号地区。";
private void CheckCorrectLevel()
{
if (_curMap == null)
{
return;
}
var errorMessage = string.Empty;
if (!_curMap.MapData.IsRegionExist(curRegionData.preparingRegionId))
{
errorMessage += string.Format(PreparingRegionErrorMessage,
curRegionData.preparingRegionId);
curRegionData.preparingRegionId = 0;
}
if (!string.IsNullOrEmpty(errorMessage))
{
CustomPopUpWindow.PopUp(errorMessage + FixedMessage);
}
}
private bool CheckTask()
{
foreach (var taskInfoEditor in taskInfos)
{
if (taskInfoEditor.isErrorTaskID)
{
DebugUtil.LogError($"有错误任务ID {taskInfoEditor.taskID}, 无法保存关卡");
CustomPopUpWindow.PopUp($"有错误任务ID {taskInfoEditor.taskID}, 无法保存关卡");
return false;
}
}
/*if (!IsTaskInvalid(successTaskID))
{
DebugUtil.LogError("胜利条件错误, 无法保存关卡");
CustomPopUpWindow.PopUp("胜利条件错误, 无法保存关卡");
return false;
}
if (!IsTaskInvalid(failTaskID))
DebugUtil.LogError("失败条件错误");
for (var i = 0; i < starTaskIDs.Count; i++)
{
if (!IsTaskInvalid(starTaskIDs[i]))
DebugUtil.LogError($"{i + 1}星级条件错误");
}*/
return true;
}
#endregion
#region 内部方法
private void ProcessCondition()
{
foreach (var taskInfo in currentLevelData.taskInfos)
{
ProcessCondition(taskInfo.taskCondition);
}
}
private void ProcessCondition(ConditionModifier conditionModifier)
{
// 撤退数量特殊处理
if (conditionModifier.conditionType == ConditionType.CombatEvacuationCount &&
conditionModifier.conditionArgs.Count > 0)
{
currentLevelData.fallBackCount = (int)conditionModifier.conditionArgs[0].Arg;
}
conditionModifier._args = new();
foreach (var param in conditionModifier.conditionArgs)
{
conditionModifier._args.Add(param.Arg);
}
if (ConditionUtil.IsNeedUncertainParamCondition(conditionModifier.conditionType))
{
foreach (var param in conditionModifier.uncertainParam)
{
conditionModifier._args.Add(param);
}
}
}
/// <summary>
/// 关卡加载时 加载敌方预制体
/// </summary>
/// <param name="monsterID">Monster表ID</param>
/// <returns></returns>
private GameObject LoadEnemyNpcPrefab(int monsterID)
{
GameObject obj = null;
var curMonsterClassID = 0;
if (MonsterCfg.DataMap.TryGetValue(monsterID, out var monster))
{
curMonsterClassID = monster.MonsterClassID;
}
else
{
return null;
}
switch (monster.NpcType)
{
case EMonsterType.Vehicle:
{
if (VehicleCfg.DataMap.TryGetValue(curMonsterClassID, out var vehicle))
{
var prefabPath = PathEx.GetVehicleGameModelPath(vehicle.ID);
obj = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (obj == null)
{
DebugUtil.LogError("请检查敌方载具类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath);
}
}
else
{
DebugUtil.LogError("请检查敌方载具类: {0}的配置是否存在", curMonsterClassID);
}
return obj;
}
case EMonsterType.EnemyCharacter:
{
if (MonsterClassCfg.DataMap.TryGetValue(curMonsterClassID, out var monsterClass))
{
var prefabPath = PathEx.GetCharacterFightModelPath(monsterClass.NameId, monsterClass.WeaponId);
obj = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (obj == null)
{
DebugUtil.LogError("请检查敌方怪物类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath);
}
}
else
{
DebugUtil.LogError("请检查敌方怪物类: {0}的配置是否存在", curMonsterClassID);
}
return obj;
}
case EMonsterType.FightBuild:
{
if (FightBuildCfg.DataMap.TryGetValue(curMonsterClassID, out var buildData))
{
var prefabPath = PathEx.GetFightBuildingPath(buildData.ModelPath);
obj = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (obj == null)
{
DebugUtil.LogError("请检查敌方建筑类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath);
}
}
else
{
DebugUtil.LogError("请检查敌方建筑类: {0}的配置是否存在", curMonsterClassID);
}
return obj;
}
default: return null;
}
}
/// <summary>
/// 关卡加载时 加载中立预制体
/// </summary>
/// <param name="monsterID">Monster表ID</param>
/// <returns></returns>
private GameObject LoadMidNpcPrefab(int monsterID)
{
GameObject obj = null;
var curMonsterClassID = 0;
if (MonsterCfg.DataMap.TryGetValue(monsterID, out var monster))
{
curMonsterClassID = monster.MonsterClassID;
}
else
{
return null;
}
switch (monster.NpcType)
{
case EMonsterType.Vehicle:
{
if (VehicleCfg.DataMap.TryGetValue(curMonsterClassID, out var vehicle))
{
var prefabPath = PathEx.GetVehicleGameModelPath(vehicle.ID);
obj = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (obj == null)
{
DebugUtil.LogError("请检查敌方载具类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath);
}
}
else
{
DebugUtil.LogError("请检查敌方载具类: {0}的配置是否存在", curMonsterClassID);
}
return obj;
}
case EMonsterType.EnemyCharacter:
{
if (MonsterClassCfg.DataMap.TryGetValue(curMonsterClassID, out var monsterClass))
{
var prefabPath = PathEx.GetCharacterFightModelPath(monsterClass.NameId, monsterClass.WeaponId);
obj = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (obj == null)
{
DebugUtil.LogError("请检查敌方怪物类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath);
}
}
else
{
DebugUtil.LogError("请检查敌方怪物类: {0}的配置是否存在", curMonsterClassID);
}
return obj;
}
case EMonsterType.FightBuild:
{
if (FightBuildCfg.DataMap.TryGetValue(curMonsterClassID, out var buildData))
{
var prefabPath = PathEx.GetFightBuildingPath(buildData.ModelPath);
obj = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (obj == null)
{
DebugUtil.LogError("请检查敌方建筑类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath);
}
}
else
{
DebugUtil.LogError("请检查敌方建筑类: {0}的配置是否存在", curMonsterClassID);
}
return obj;
}
default: return null;
}
}
/// <summary>
/// 关卡加载时 加载己方预制体
/// </summary>
/// <param name="monsterID">Monster表ID</param>
/// <returns></returns>
private GameObject LoadSelfNpcPrefab(int monsterID)
{
GameObject obj = null;
var curFightNpcID = 0;
if (MonsterCfg.DataMap.TryGetValue(monsterID, out var monster))
{
curFightNpcID = monster.MonsterClassID;
}
else
{
return null;
}
switch (monster.NpcType)
{
case EMonsterType.Vehicle:
{
if (VehicleCfg.DataMap.TryGetValue(curFightNpcID, out var vehicle))
{
var prefabPath = PathEx.GetVehicleGameModelPath(vehicle.ID);
obj = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (obj == null)
{
DebugUtil.LogError("请检查己方载具类: {0}的预制体路径是否存在,对应的完整路径: {1}", curFightNpcID, prefabPath);
}
}
else
{
DebugUtil.LogError("请检查己方载具类: {0}的配置是否存在", curFightNpcID);
}
return obj;
}
case EMonsterType.SelfCharacter:
{
if (FightNpcCfg.DataMap.TryGetValue(curFightNpcID, out var fightNpc))
{
var prefabPath = PathEx.GetCharacterFightModelPath(fightNpc.NameId, fightNpc.WeaponId);
obj = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (obj == null)
{
DebugUtil.LogError("请检查己方NPC类: {0}的预制体路径是否存在,对应的完整路径: {1}", curFightNpcID, prefabPath);
}
}
else
{
DebugUtil.LogError("请检查己方NPC类: {0}的配置是否存在", curFightNpcID);
}
return obj;
}
case EMonsterType.FightBuild:
{
if (FightBuildCfg.DataMap.TryGetValue(curFightNpcID, out var buildData))
{
var prefabPath = PathEx.GetFightBuildingPath(buildData.ModelPath);
obj = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (obj == null)
{
DebugUtil.LogError("请检查己方建筑类: {0}的预制体路径是否存在,对应的完整路径: {1}", curFightNpcID, prefabPath);
}
}
else
{
DebugUtil.LogError("请检查己方建筑类: {0}的配置是否存在", curFightNpcID);
}
return obj;
}
default: return null;
}
}
private void DestroyNpcPrefab()
{
if (selfObjRoot != null)
DestroyImmediate(selfObjRoot);
if (midObjRoot != null)
DestroyImmediate(midObjRoot);
if (enemyObjRoot != null)
DestroyImmediate(enemyObjRoot);
}
private void ChangeEnemyNpcToward(NpcEditorInfo npcEditorInfo, CharacterToward npcToward)
{
if (_enemyNpcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic))
{
if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj))
{
var yaw = LevelUtils.SetCharaterToward(npcToward);
npcObj.transform.eulerAngles = new Vector3(0, yaw, 0);
}
}
}
private void ChangeMidNpcToward(NpcEditorInfo npcEditorInfo, CharacterToward npcToward)
{
if (_midNpcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic))
{
if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj))
{
var yaw = LevelUtils.SetCharaterToward(npcToward);
npcObj.transform.eulerAngles = new Vector3(0, yaw, 0);
}
}
}
private void ChangeSelfNpcToward(Vector2Int pos, CharacterToward npcToward)
{
if (_selfNpcObjectDic.TryGetValue(pos, out var npcObj))
{
var yaw = LevelUtils.SetCharaterToward(npcToward);
npcObj.transform.eulerAngles = new Vector3(0, yaw, 0);
}
}
private bool CheckConditionSame(ConditionType conditionType)
{
if (currentLevelData.scoreTarget.Any(modifier => modifier.conditionType == conditionType))
{
return true;
}
return false;
}
#endregion
#region Draw
private bool IsShowNpcID => _showNpcInfos != null && _showNpcInfos.Count > 0;
private List<NpcEditorInfo> _showNpcInfos;
private void OnSceneGUI(SceneView sceneView)
{
DrawRoadPath();
DrawAreaPath();
DrawNpcID();
DrawSphereID();
}
private void DrawRoadPath()
{
if (null == _currEditingRoadInfo)
return;
Vector3 lastWorldPos = Vector3.zero;
for (int i = 0; i < _currEditingRoadInfo.roadPoints.Count; i++)
{
var gridPos = _currEditingRoadInfo.roadPoints[i];
var worldPos = _curMap.TGSCellIndex2WorldPosition(gridPos.cellIndex);
Handles.color = Color.blue;
Handles.DrawSolidDisc(worldPos, Vector3.up, 0.4f);
if (i > 0)
{
Handles.color = Color.yellow;
EditorUtil.DrawArrowInScene(lastWorldPos, worldPos, 0.5f);
}
//Handles.color = Color.black;
Handles.Label(worldPos, i.ToString());
lastWorldPos = worldPos;
}
}
private void DrawAreaPath()
{
if (null == _currEditingAreaInfo)
return;
for (int i = 0; i < _currEditingAreaInfo.areaPoints.Count; ++i)
{
var gridPos = _currEditingAreaInfo.areaPoints[i];
var worldPos = _curMap.TGSCellIndex2WorldPosition(gridPos.cellIndex);
Handles.color = Color.blue;
Handles.DrawSolidDisc(worldPos, Vector3.up, 1.4f);
}
}
private GUIStyle labelStyle;
private GUIStyle labelStyleSelf;
private GUIStyle labelStyleSphere;
private void DrawNpcID()
{
if (!IsShowNpcID)
return;
labelStyle ??= new GUIStyle
{
fontSize = 38,
fontStyle = FontStyle.Bold,
normal =
{
textColor = Color.red
}
};
labelStyleSelf ??= new GUIStyle
{
fontSize = 38,
fontStyle = FontStyle.Bold,
normal =
{
textColor = Color.green
}
};
foreach (var npc in _showNpcInfos)
{
if (isShowAllEnemyNpcID &&
_enemyNpcObjectWaveDic.TryGetValue(npc.npcInfo.waveIndex, out var enemyNpcObjectDic))
{
var waveID = npc.npcInfo.waveIndex;
if (enemyNpcObjectDic.TryGetValue(npc, out var npcObj))
{
var groupsText = npc.npcInfo.groups is { Count: > 0 }
? string.Join(",", npc.npcInfo.groups)
: "-";
var content = $"{waveID},{npc.id}|{groupsText}";
Handles.Label(npcObj.transform.position, content, labelStyle);
}
}
else if (isShowAllMidNpcID &&
_midNpcObjectWaveDic.TryGetValue(npc.npcInfo.waveIndex, out var midNpcObjectDic))
{
var waveID = npc.npcInfo.waveIndex;
if (midNpcObjectDic.TryGetValue(npc, out var npcObj))
{
var groupsText = npc.npcInfo.groups is { Count: > 0 }
? string.Join(",", npc.npcInfo.groups)
: "-";
var content = $"{waveID},{npc.id}|{groupsText}";
Handles.Label(npcObj.transform.position, content, labelStyle);
}
}
else if (isShowAllSelfNpcID && _selfNpcObjectDic.TryGetValue(npc.npcInfo.position, out var selfNpcObj))
{
Handles.Label(selfNpcObj.transform.position, npc.id.ToString(), labelStyleSelf);
}
}
}
private void DrawSphereID()
{
if (!canEditorBlockSphere)
return;
labelStyleSphere ??= new GUIStyle
{
fontSize = 30,
fontStyle = FontStyle.Bold,
normal =
{
textColor = Color.black
}
};
foreach (var blockSpereEditor in blockSphereEditorList)
{
if (blockSpereEditor.sphere != null)
{
var pos = new Vector3(blockSpereEditor.sphere.transform.position.x, Calibre,
blockSpereEditor.sphere.transform.position.z);
Handles.Label(pos, blockSpereEditor.cellIndex.ToString(),
labelStyleSphere);
}
}
}
#endregion
#region 波次信息加载
/// <summary>
/// 加载波次信息从NPC数据中重建
/// </summary>
private void LoadWaveInfo()
{
waveInfoEditors.Clear();
if (currentEnemyNpcInfo == null || currentEnemyNpcInfo.Count == 0)
{
DebugUtil.Log("没有敌方NPC无需加载波次信息");
return;
}
// 从NPC数据中收集波次信息
// 结构: waveID -> List<groupID>
var waveGroupsMap = new Dictionary<int, HashSet<int>>();
foreach (var npc in currentEnemyNpcInfo)
{
// 只处理标记为中途生成的NPC
if (npc.npcInfo.isHideOnLoad && npc.npcInfo.waveIndex > 0)
{
if (!waveGroupsMap.ContainsKey(npc.npcInfo.waveIndex))
{
waveGroupsMap[npc.npcInfo.waveIndex] = new HashSet<int>();
}
// 收集该NPC所属的所有组
if (npc.npcInfo.groups != null)
{
foreach (var groupId in npc.npcInfo.groups)
{
waveGroupsMap[npc.npcInfo.waveIndex].Add(groupId);
}
}
}
}
// 创建波次编辑器
foreach (var kvp in waveGroupsMap.OrderBy(x => x.Key))
{
var waveEditor = new WaveInfoEditor(kvp.Key, kvp.Value.ToList());
waveInfoEditors.Add(waveEditor);
}
DebugUtil.Log($"成功从NPC数据中重建 {waveInfoEditors.Count} 个波次信息");
}
#endregion
}