using TGS; using System; using Gameplay; using Framework; using System.IO; using UnityEditor; using UnityEngine; using System.Linq; using Gameplay.Utils; using Gameplay.Level; using Sirenix.Utilities; using Sirenix.OdinInspector; using Sirenix.Utilities.Editor; using System.Collections.Generic; using cfg; using cfg.AICfg; using cfg.FightCfg; using cfg.GuideCfg; using cfg.LevelCfg; using cfg.MonsterCfg; using cfg.VehicleCfg; using Sirenix.OdinInspector.Editor; using Level = cfg.LevelCfg.Level; using ObjectFieldAlignment = Sirenix.OdinInspector.ObjectFieldAlignment; using static Gameplay.Level.LevelData; using static LevelEditorWindow; using NPOI.SS.Formula.Functions; using Gameplay.Character; public class LevelEditorWindow : OdinEditorWindow { [MenuItem("Tools/*地图场景/关卡编辑器", false, (int)NLDMenuID.LevelEditor)] private static void OpenLevelEditorWindow() { var window = GetWindow(); window.position = GUIHelper.GetEditorWindowRect().AlignCenter(800, 600); window.titleContent = new GUIContent("关卡编辑器"); } public static void OpenWindow(LevelEditor levelEditor) { MapEditorSettings.Init(); currWindow = GetWindow(); currWindow.position = GUIHelper.GetEditorWindowRect().AlignCenter(800, 600); currWindow.currentLevelData = null; currWindow._levelEditor = levelEditor; currWindow.LoadLevel(levelEditor.LevelData); var level = Path.GetFileNameWithoutExtension(levelEditor.LevelDataJson.name); currWindow.levelID = level.Substring(level.Length - 6); currWindow.GetLevelMonsterClass(); DebugUtil.Log("关卡数据:{0}", currWindow.levelID); } private static LevelEditorWindow currWindow; private MapManager _mapManager; private Map _curMap; private LevelEditor _levelEditor; #region 配置表 private Level LevelCfg => EditorTableManager.instance.tables.Level; private Monster MonsterCfg => EditorTableManager.instance.tables.Monster; private MonsterClass MonsterClassCfg => EditorTableManager.instance.tables.MonsterClass; private VehicleConfig VehicleCfg => EditorTableManager.instance.tables.VehicleConfig; private FightNPCConfig FightNpcCfg => EditorTableManager.instance.tables.FightNPCConfig; private BuildConfig FightBuildCfg => EditorTableManager.instance.tables.BuildConfig; #endregion #region 地块颜色 private static readonly Color RegionColor = MapUtils.GetRGBAColor("FF960072"); #endregion #region 关卡数据 [BoxGroup("当前关卡数据")] [LabelText("当前关卡数据"), Indent] public LevelData currentLevelData = null; #endregion #region 关卡进度 [BoxGroup("关卡引导")] [LabelText("关卡进度"), Indent] public List progressGuideDatas = new(); #endregion #region 引导立标 [Serializable] public class SetFlagGuideEditorInfo { public SetFlagGuideEditorInfo(LevelData.SetFlagGuideData flagGuideData) { this.flagGuideData = flagGuideData; guideTagID = flagGuideData.guideTagID; if (GuideTagConfig.DataMap.TryGetValue(guideTagID, out var guideData)) { flagGuideData.guideTagPath = guideData.ImagePath; guideTagName = guideData.Name; var sprite = AssetDatabase.LoadAssetAtPath(guideData.ImagePath); if (sprite != null) guideTagSprite = sprite; } } private GuideTagConfig GuideTagConfig => EditorTableManager.instance.tables.GuideTagConfig; [HorizontalGroup("1")] [LabelText("立标配置ID")] [ValueDropdown("GetGuideTagIDs")] [OnValueChanged("ChangeTagID")] public int guideTagID; [HorizontalGroup("1")] [LabelText("立标名字")] [ReadOnly] public string guideTagName; [HorizontalGroup("2")] [LabelText("预览图")] [PreviewField(150, ObjectFieldAlignment.Center), Indent] public Sprite guideTagSprite; [HorizontalGroup("3")] [LabelText("具体数据")] public LevelData.SetFlagGuideData flagGuideData; private IEnumerable GetGuideTagIDs() { return GuideTagConfig.DataMap.Keys; } private void ChangeTagID() { if (GuideTagConfig.DataMap.TryGetValue(guideTagID, out var guideData)) { flagGuideData.guideTagPath = guideData.ImagePath; flagGuideData.guideTagID = guideTagID; guideTagName = guideData.Name; var sprite = AssetDatabase.LoadAssetAtPath(guideData.ImagePath); if (sprite != null) guideTagSprite = sprite; } } } [BoxGroup("关卡引导")] [LabelText("立标数据"), Indent] public List setFlagEditorInfos = new(); #endregion #region 关卡进度 [BoxGroup("关卡引导")] [LabelText("信息板数据"), Indent] public List infoPanelGuideDatas = new(); #endregion #region 占领相关 [Serializable] public class CaptureEditorInfo { [LabelText("占领点位")] public Vector2Int capturePoint; [LabelText("占领时间")] public int captureProgress; } [BoxGroup("占领相关")] [VerticalGroup("占领相关/数据")] [LabelText("当前占领数据"), Indent] public List captureEditorInfo = new(); [BoxGroup("占领相关")] [VerticalGroup("占领相关/编辑")] [LabelText("编辑占领数据"), Indent] public bool isEditingCapture = false; #endregion #region 扛旗相关 [Serializable] public class FlagEditorInfo { [LabelText("扛旗起点")] public List flagStartPoints = new(); [LabelText("扛旗终点")] public List flagEndPoints = new(); [LabelText("添加起点")] [OnValueChanged("EditingStartPoints")] public bool isEditingStart; [LabelText("添加终点")] [OnValueChanged("EditingEndPoints")] public bool isEditingEnd; private void EditingStartPoints() { currWindow.isEditingFlagStart = isEditingStart; if (isEditingStart) { isEditingEnd = false; currWindow.isEditingFlagEnd = false; } } private void EditingEndPoints() { currWindow.isEditingFlagEnd = isEditingEnd; if (isEditingEnd) { isEditingStart = false; currWindow.isEditingFlagStart = false; } } } private bool isEditingFlagStart; private bool isEditingFlagEnd; [BoxGroup("扛棋相关")] [LabelText("当前扛旗数据"), Indent] public FlagEditorInfo flagEditorInfo = new(); #endregion #region 撤离相关 [Serializable] public class FallbackEditorInfo { [LabelText("单次撤离点")] public List fallbackSinglePoints = new(); [LabelText("复用撤离点")] public List fallbackReusablePoints = new(); [LabelText("添加单次撤离点")] [OnValueChanged("EditingFallbackPoints")] public bool isEditingSinglePoints; [LabelText("添加复用撤离点")] [OnValueChanged("EditingMultiplexPoint")] public bool isEditingReusablePoints; private void EditingFallbackPoints() { currWindow.isEditingFallbackSingle = isEditingSinglePoints; if (isEditingSinglePoints) { isEditingReusablePoints = false; currWindow.isEditingFallbackReusable = false; } } private void EditingMultiplexPoint() { currWindow.isEditingFallbackReusable = isEditingReusablePoints; if (isEditingReusablePoints) { isEditingSinglePoints = false; currWindow.isEditingFallbackSingle = false; } } } private bool isEditingFallbackSingle; private bool isEditingFallbackReusable; [BoxGroup("撤离相关")] [LabelText("当前撤离数据"), Indent] public FallbackEditorInfo fallbackEditorInfo = new(); #endregion #region 地区相关 [Serializable] public class RegionData { [LabelText("出战区")] [OnValueChanged("OnRegionChanged")] public int preparingRegionId; [LabelText("出战区朝向")] public LevelData.CharacterToward preparingRegionToward; [LabelText("撤退区")] public int fallbackRegionId; [LabelText("敌方撤退区")] public int enemyFallbackRegionId; [LabelText("PVP防守区")] public int pvpDefendRegionId; [LabelText("PVP防守方朝向")] public LevelData.CharacterToward pvpDefendToward; private void OnRegionChanged() { var regionsList = currWindow._curMap.MapData.regions; for (int i = 0; i < regionsList.Count; i++) { currWindow._curMap.SetRegionColor(i, false, RegionColor); } currWindow._curMap.SetRegionColor(preparingRegionId, true, RegionColor); } } [BoxGroup("地区相关")] [LabelText("当前地区数据"), Indent] public RegionData curRegionData = null; #endregion #region NPC刷新点位相关 [HideInInspector] [BoxGroup("刷新点位")] [LabelText("编辑刷新点"), Indent, OnValueChanged("OnIsEditingRefreshIDChanged")] public bool isEditingRefreshID; [HideInInspector] [BoxGroup("刷新点位")] [LabelText("刷新点位"), Indent] [ShowIf("isEditingRefreshID")] public List refreshPoints = new List(); private void OnIsEditingRefreshIDChanged() { if (!isEditingRefreshID) { foreach (var point in refreshPoints) { _curMap.TerrainGridSystem.CellHideRegionSurface(point); } } else { foreach (var point in refreshPoints) { _curMap.TerrainGridSystem.CellToggleRegionSurface(point, true, RegionColor); } } } #endregion #region 指定路线 [Serializable] public class PrescribedRoadEditorInfo { [LabelText("路线ID")] public int roadID; [LabelText("路线点位")] public List roadPoints = new(); private bool isEditingRoadInfo; public PrescribedRoadEditorInfo() { roadID = currWindow.roadEditorInfos.Count + 1; } [Button("开始编辑路线"), Indent] [HideIf("isEditingRoadInfo")] private void StartEditRoad() { isEditingRoadInfo = true; currWindow.StartEditRoadInfo(this); } [Button("完成路线编辑"), Indent] [ShowIf("isEditingRoadInfo")] private void EndEditRoad() { isEditingRoadInfo = false; currWindow.FinishEditRoadInfo(); } } [BoxGroup("制定路线")] [LabelText("编辑指定路径"), Indent] public bool isEditingRoad; [BoxGroup("制定路线")] [LabelText("编辑指定路线"), Indent] [ShowIf("isEditingRoad")] public List roadEditorInfos = new(); private bool isEditingRoadInfo; private PrescribedRoadEditorInfo _currEditingRoadInfo; private void StartEditRoadInfo(PrescribedRoadEditorInfo roadInfo) { isEditingRoadInfo = true; _currEditingRoadInfo = roadInfo; } private void FinishEditRoadInfo() { isEditingRoadInfo = false; _currEditingRoadInfo = null; } #endregion #region 指定区域 [Serializable] public class PrescribedAreaEditorInfo { [LabelText("区域ID")] public int areaID; [LabelText("区域点位")] public List areaPoints = new(); private bool isEditingAreaInfo; public PrescribedAreaEditorInfo() { areaID = currWindow.areaEditorInfos.Count + 1; } [Button("开始编辑区域"), Indent] [HideIf("isEditingAreaInfo")] private void StartEditArea() { isEditingAreaInfo = true; currWindow.StartEditAreaInfo(this); } [Button("完成区域编辑"), Indent] [ShowIf("isEditingAreaInfo")] private void EndEditArea() { isEditingAreaInfo = false; currWindow.FinishEditAreaInfo(); } } [BoxGroup("制定区域")] [LabelText("编辑指定区域"), Indent] public bool isEditingArea; [BoxGroup("制定区域")] [LabelText("编辑指定区域"), Indent] [ShowIf("isEditingArea")] public List areaEditorInfos = new(); private bool isEditingAreaInfo; private PrescribedAreaEditorInfo _currEditingAreaInfo; private void StartEditAreaInfo(PrescribedAreaEditorInfo roadInfo) { isEditingAreaInfo = true; _currEditingAreaInfo = roadInfo; } private void FinishEditAreaInfo() { isEditingAreaInfo = false; _currEditingAreaInfo = null; } #endregion #region 显示地图索引 [BoxGroup("辅助功能")] [LabelText("显示索引地图"), Indent] [LabelWidth(100)] [OnValueChanged("ShowCellCoordinates")] [SerializeField] public bool isShowCellCoordinates; private void ShowCellCoordinates() { if (_curMap == null || _curMap.TerrainGridSystem == null) return; _curMap.TerrainGridSystem.displayCellDebugInfo = isShowCellCoordinates ? CellDebugInfo.CellCoordinates : CellDebugInfo.Nothing; var obj = FindObjectOfType().gameObject; Selection.activeGameObject = obj; } #endregion #region NPC编辑器类 [Serializable] public class NpcEditorInfo { private AITypeConfig AITypeCfg => EditorTableManager.instance.tables.AITypeConfig; [HideInInspector] public int npcTroopId; [ReadOnly] [LabelText("序号ID")] public int id; [FoldoutGroup("详细信息")] [LabelText("MonsterID")] [ValueDropdown("GetMonsterIDs")] [OnValueChanged("ChangeNpcID")] public int monsterId; [FoldoutGroup("详细信息")] [LabelText("等级")] [ReadOnly] public int level; private IEnumerable GetMonsterIDs() { return npcTroopId switch { ETroopsId.Neutral => currWindow.GetMidMonsterIDs(), // 中立 ETroopsId.Self => currWindow.GetSelfNpcIDs(), // 友方 ETroopsId.Enemy => currWindow.GetEnemyMonsterIDs(), // 敌人 _ => null }; } private void ChangeNpcID() { switch (npcTroopId) { case 0: // 中立 currWindow.ChangeMidNpcID(this, monsterId); break; case 1: // 友方 currWindow.ChangeSelfNpcID(npcInfo, monsterId); break; case 2: // 敌人 currWindow.ChangeEnemyNpcID(this, monsterId); break; default: DebugUtil.LogError($"未处理类型:{npcTroopId}"); break; } } [FoldoutGroup("详细信息")] [LabelText("朝向")] [OnValueChanged("NpcTowardOnChange")] public CharacterToward npcToward; private void NpcTowardOnChange() { npcInfo.npcToward = npcToward; switch (npcTroopId) { case 0: // 中立 currWindow.ChangeMidNpcToward(this, npcToward); break; case 1: // 友方 currWindow.ChangeSelfNpcToward(npcInfo.position, npcToward); break; case 2: // 敌人 currWindow.ChangeEnemyNpcToward(this, npcToward); break; default: DebugUtil.LogError($"未处理类型:{npcTroopId}"); break; } } [FoldoutGroup("详细信息")] [HideLabel] public NPCInfo npcInfo; [FoldoutGroup("详细信息")] [LabelText("行为树")] [ValueDropdown(nameof(GetFilesFromFolder))] public string aiConfigName; [FoldoutGroup("详细信息")] [LabelText("AI预设")] [ReadOnly] public string aiType; private IEnumerable GetFilesFromFolder() { string rootPath = "Assets/Config/AI/"; return new string[] { "" }.Concat(Directory.GetFiles(rootPath, "*.asset", SearchOption.AllDirectories) .Select(Path.GetFileNameWithoutExtension)); } public NpcEditorInfo(NPCInfo npcInfo, int index, int level, int aiType, int npcTroopId) { this.npcInfo = npcInfo; this.level = level; this.npcInfo.aiTypeID = aiType; this.aiType = GetAITypeData(aiType); monsterId = npcInfo.id; id = index; this.npcTroopId = npcTroopId; npcToward = this.npcInfo.npcToward; aiConfigName = npcInfo.configName; } [FoldoutGroup("详细信息")] [LabelText("位置调整")] [OnValueChanged("ChangeNpcPos")] public bool isChangePos; private void ChangeNpcPos() { if (isChangePos) { if (currWindow._currEditingNpcInfo != null) DebugUtil.LogWarning("检查其他怪物是否处于位置调整或编辑巡逻信息状态"); } currWindow._currNpcEditorInfo = isChangePos ? this : null; currWindow.isEditingNpcPos = isChangePos; currWindow.npcTroopId = npcTroopId; currWindow._currEditingNpcInfo = isChangePos ? npcInfo : null; } [FoldoutGroup("详细信息")] [LabelText("显示该怪物序号")] [OnValueChanged("ShowNpcID")] public bool isShowNpcID; private void ShowNpcID() { if (isShowNpcID && !currWindow._showNpcInfos.Contains(this)) currWindow._showNpcInfos.Add(this); else if (!isShowNpcID) { currWindow._showNpcInfos.Remove(this); } } private string GetAITypeData(int aiTypeID) { if (AITypeCfg.DataMap.TryGetValue(aiTypeID, out var dataAIType)) { return dataAIType.Name; } return $"无效AI类型: {aiTypeID},请检查配置表"; } } private NPCInfo _currEditingNpcInfo; private NpcEditorInfo _currNpcEditorInfo; private bool isEditingNpcPos; private int npcTroopId; #endregion #region 敌方Npc /// /// 波次/怪物ID对应的NPC预制体 /// private Dictionary> _enemyNpcObjectWaveDic; [BoxGroup("敌方Npc")] [LabelText("编辑NPC"), Indent] [LabelWidth(100)] [SerializeField] [OnValueChanged("OnIsEditingEnemyNpcChanged")] public bool isEditingEnemyNpc; [BoxGroup("敌方Npc")] [LabelText("显示所有怪物序号ID"), Indent] [OnValueChanged("ShowAllEnemyNpcId")] [SerializeField] public bool isShowAllEnemyNpcID; [BoxGroup("敌方Npc")] [LabelText("添加怪物ID"), Indent, LabelWidth(100)] [SerializeField] [ShowIf("isEditingEnemyNpc")] public int addEnemyMonsterId; [BoxGroup("敌方Npc")] [Button("添加怪物id"), Indent, ShowIf("isEditingEnemyNpc")] private void AddEnemyMonsterId() { if (!MonsterCfg.DataMap.ContainsKey(addEnemyMonsterId)) { DebugUtil.LogError($"没有该怪物id:{addEnemyMonsterId}"); return; } enemyMonsterIDs.Add(addEnemyMonsterId); DebugUtil.LogError($"添加怪物id:{addEnemyMonsterId}成功"); } [BoxGroup("敌方Npc")] [LabelText("怪物ID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("EnemyMonsterIDChanged")] [ValueDropdown("GetEnemyMonsterIDs")] [ShowIf("isEditingEnemyNpc")] public int curEnemyMonsterID; [BoxGroup("敌方Npc")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("NPC预制体预览图"), ShowIf("isEditingEnemyNpc")] public GameObject enemyNpcObject; [BoxGroup("敌方Npc")] [LabelText("当前怪物列表"), Indent, ShowIf("isEditingEnemyNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true, ShowFoldout = true)] public List currentEenemyNpcInfo = null; /// /// 该关卡的敌方NPCID /// [HideInInspector] public List enemyMonsterIDs; private IEnumerable GetEnemyMonsterIDs() { return enemyMonsterIDs; } private void EnemyMonsterIDChanged() { var obj = LoadEnemyNpcPrefab(curEnemyMonsterID); if (obj != null) enemyNpcObject = obj; } private void OnIsEditingEnemyNpcChanged() { if (isEditingEnemyNpc && isEditingSelfNpc && isEditingMidNpc) { isEditingSelfNpc = false; isEditingMidNpc = false; } } private void ShowAllEnemyNpcId() { foreach (var npc in currentEenemyNpcInfo) { npc.isShowNpcID = isShowAllEnemyNpcID; if (isShowAllEnemyNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 己方Npc private const int SelfID = 200; private Dictionary _selfNpcObjectDic; [BoxGroup("己方Npc")] [LabelText("编辑己方NPC"), Indent] [LabelWidth(100)] [OnValueChanged("OnIsEditingSelfNpcChanged")] [SerializeField] public bool isEditingSelfNpc; [BoxGroup("己方Npc")] [LabelText("显示所有己方序号ID"), Indent] [OnValueChanged("ShowAllSelfNpcId")] [SerializeField] public bool isShowAllSelfNpcID; [BoxGroup("己方Npc")] [LabelText("添加怪物ID"), Indent, LabelWidth(100)] [SerializeField] [ShowIf("isEditingSelfNpc")] public int addSelfMonsterId; [BoxGroup("己方Npc")] [Button("添加怪物id"), Indent, ShowIf("isEditingSelfNpc")] private void AddSelfMonsterId() { if (!MonsterCfg.DataMap.ContainsKey(addSelfMonsterId)) { DebugUtil.LogError($"没有该怪物id:{addSelfMonsterId}"); return; } selfMonsterIDs.Add(addSelfMonsterId); DebugUtil.LogError($"添加怪物id:{addSelfMonsterId}成功"); } [BoxGroup("己方Npc")] [LabelText("己方NpcID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("SelfNpcIDChanged")] [ValueDropdown("GetSelfNpcIDs")] [ShowIf("isEditingSelfNpc")] public int curSelfNpcID; [BoxGroup("己方Npc")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("己方NPC预制体预览图"), ShowIf("isEditingSelfNpc")] public GameObject selfNpcObject; [BoxGroup("己方Npc")] [LabelText("当前己方NPC列表"), Indent, ShowIf("isEditingSelfNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true)] public List currentSelfNpcInfo = null; /// /// 该关卡的己方NPCID /// [HideInInspector] public List selfMonsterIDs; private IEnumerable GetSelfNpcIDs() { return selfMonsterIDs; } private void SelfNpcIDChanged() { var obj = LoadSelfNpcPrefab(curSelfNpcID); if (obj != null) selfNpcObject = obj; } private void OnIsEditingSelfNpcChanged() { if (isEditingEnemyNpc && isEditingSelfNpc && isEditingMidNpc) { isEditingEnemyNpc = false; isEditingMidNpc = false; } } private void ShowAllSelfNpcId() { foreach (var npc in currentSelfNpcInfo) { npc.isShowNpcID = isShowAllSelfNpcID; if (isShowAllSelfNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 中立Npc /// /// 中立 波次/怪物ID对应的NPC预制体 /// private Dictionary> _midNpcObjectWaveDic; [BoxGroup("中立Npc")] [LabelText("编辑NPC"), Indent] [LabelWidth(100)] [SerializeField] [OnValueChanged("OnIsEditingMidNpcChanged")] public bool isEditingMidNpc; [BoxGroup("中立Npc")] [LabelText("显示所有怪物序号ID"), Indent] [OnValueChanged("ShowAllMidNpcId")] [SerializeField] public bool isShowAllMidNpcID; [BoxGroup("中立Npc")] [LabelText("添加怪物ID"), Indent, LabelWidth(100)] [SerializeField] [ShowIf("isEditingMidNpc")] public int addMidMonsterId; [BoxGroup("中立Npc")] [Button("添加怪物id"), Indent, ShowIf("isEditingMidNpc")] private void AddMidMonsterId() { if (!MonsterCfg.DataMap.ContainsKey(addMidMonsterId)) { DebugUtil.LogError($"没有该怪物id:{addMidMonsterId}"); return; } midMonsterIDs.Add(addMidMonsterId); DebugUtil.LogError($"添加怪物id:{addMidMonsterId}成功"); } [BoxGroup("中立Npc")] [LabelText("怪物ID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("MidMonsterIDChanged")] [ValueDropdown("GetMidMonsterIDs")] [ShowIf("isEditingMidNpc")] public int curMidMonsterID; [BoxGroup("中立Npc")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("NPC预制体预览图"), ShowIf("isEditingMidNpc")] public GameObject midNpcObject; [BoxGroup("中立Npc")] [LabelText("当前怪物列表"), Indent, ShowIf("isEditingMidNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true, ShowFoldout = true)] public List currentMidNpcInfo = null; /// /// 该关卡的中立NPCID /// [HideInInspector] public List midMonsterIDs; private IEnumerable GetMidMonsterIDs() { return midMonsterIDs; } private void MidMonsterIDChanged() { var obj = LoadMidNpcPrefab(curMidMonsterID); if (obj != null) midNpcObject = obj; } private void OnIsEditingMidNpcChanged() { if (isEditingEnemyNpc && isEditingSelfNpc && isEditingMidNpc) { isEditingEnemyNpc = false; isEditingSelfNpc = false; } } private void ShowAllMidNpcId() { foreach (var npc in currentMidNpcInfo) { npc.isShowNpcID = isShowAllMidNpcID; if (isShowAllMidNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 保存 [Button("保存当前关卡"), Indent] private void SaveCurrentLevel() { CheckCorrectLevel(); // 地区 currentLevelData.preparingRegionId = curRegionData.preparingRegionId; currentLevelData.fallbackRegionId = curRegionData.fallbackRegionId; currentLevelData.enemyFallbackRegionId = curRegionData.enemyFallbackRegionId; currentLevelData.preparingRegionToward = curRegionData.preparingRegionToward; currentLevelData.pvpDefendRegionId = curRegionData.pvpDefendRegionId; currentLevelData.pvpDefendToward = curRegionData.pvpDefendToward; #region 敌方NPC foreach (var npcEditor in currentEenemyNpcInfo) { npcEditor.npcInfo.index = npcEditor.id; npcEditor.npcInfo.configName = npcEditor.aiConfigName; } currentLevelData.npcInfos = currentEenemyNpcInfo.Select(editorNpcInfo => editorNpcInfo.npcInfo).ToList(); #endregion #region 己方NPC foreach (var npcFightEditorInfo in currentSelfNpcInfo) { npcFightEditorInfo.npcInfo.index = npcFightEditorInfo.id; npcFightEditorInfo.npcInfo.configName = npcFightEditorInfo.aiConfigName; } currentLevelData.selfNpcInfos = currentSelfNpcInfo.Select(editorFightNpcInfo => editorFightNpcInfo.npcInfo).ToList(); #endregion #region 中立NPC foreach (var npcEditor in currentMidNpcInfo) { npcEditor.npcInfo.index = npcEditor.id; npcEditor.npcInfo.configName = npcEditor.aiConfigName; } currentLevelData.midNpcInfos = currentMidNpcInfo.Select(editorNpcInfo => editorNpcInfo.npcInfo).ToList(); #endregion // 占领数据 currentLevelData.CapturePoints.Clear(); foreach (var captureInfo in captureEditorInfo) { var capture = new LevelData.CaptureInfo { CapturePoint = _curMap.BlockPosition2TGSCellIndex(captureInfo.capturePoint), CaptureProgress = captureInfo.captureProgress, }; currentLevelData.CapturePoints.Add(capture); } // 扛旗数据 currentLevelData.flagStartPoints.Clear(); currentLevelData.flagEndPoints.Clear(); foreach (var flag in flagEditorInfo.flagStartPoints) { var cellIndex = _curMap.BlockPosition2TGSCellIndex(flag); currentLevelData.flagStartPoints.Add(cellIndex); } foreach (var flag in flagEditorInfo.flagEndPoints) { var cellIndex = _curMap.BlockPosition2TGSCellIndex(flag); currentLevelData.flagEndPoints.Add(cellIndex); } // 撤离数据 currentLevelData.fallbackSinglePoints.Clear(); currentLevelData.fallbackReusablePoints.Clear(); foreach (var fallback in fallbackEditorInfo.fallbackSinglePoints) { var cellIndex = _curMap.BlockPosition2TGSCellIndex(fallback); currentLevelData.fallbackSinglePoints.Add(cellIndex); } foreach (var fallback in fallbackEditorInfo.fallbackReusablePoints) { var cellIndex = _curMap.BlockPosition2TGSCellIndex(fallback); currentLevelData.fallbackReusablePoints.Add(cellIndex); } // 刷新点位 currentLevelData.refreshPoints.Clear(); currentLevelData.refreshPoints.AddRange(refreshPoints); // 指定路径 currentLevelData.patrolRoadData = roadEditorInfos.Select(editorInfo => new PatrolData { id = editorInfo.roadID, patrolInfos = editorInfo.roadPoints, }).ToList(); // 指定区域 currentLevelData.patrolRegionData = areaEditorInfos.Select(editorInfo => new PatrolData { id = editorInfo.areaID, patrolInfos = editorInfo.areaPoints, }).ToList(); // 关卡进度 currentLevelData.progressGuideDatas = progressGuideDatas; // 立标数据 currentLevelData.setFlagGuideDatas = setFlagEditorInfos.Select(x => x.flagGuideData).ToList(); // 信息板数据 currentLevelData.infoPanelGuideDatas = infoPanelGuideDatas; // 处理条件(击杀对象列表) ProcessCondition(); _levelEditor.SaveLevelData(); } private void ProcessCondition() { foreach (var condition in currentLevelData.setFlagGuideDatas) { ProcessCondition(condition.triggerCondition); ProcessCondition(condition.finishCondition); } foreach (var condition in currentLevelData.progressGuideDatas) { ProcessCondition(condition.triggerCondition); ProcessCondition(condition.finishCondition); } foreach (var condition in currentLevelData.infoPanelGuideDatas) { ProcessCondition(condition.triggerCondition); ProcessCondition(condition.finishCondition); } ProcessConditionForList(currentLevelData.starTarget); ProcessConditionForList(currentLevelData.scoreTarget); } private void ProcessConditionForList(IEnumerable conditionModifiers) { foreach (var conditionModifier in conditionModifiers) { ProcessCondition(conditionModifier); } } private void ProcessCondition(ConditionModifier conditionModifier) { // 击杀条件特殊处理 if (conditionModifier.conditionType == ConditionType.CombatKillEnemy) { conditionModifier.conditionArgs.Clear(); foreach (var enemy in conditionModifier.killEnemyIDList) { conditionModifier.conditionArgs.Add(new Param("", enemy, "")); } } else if (conditionModifier.conditionType == ConditionType.CombatInTargetGridByJob) { conditionModifier.conditionArgs.Clear(); conditionModifier.conditionArgs.Add(new Param("职业", conditionModifier.job, typeof(cfg.CharacterCfg.Ejob).FullName)); conditionModifier.conditionArgs.Add(new Param("是否路过", conditionModifier.isPass, "")); foreach (var unitID in conditionModifier.unitIDList) { conditionModifier.conditionArgs.Add(new Param("地格位置", unitID, "")); } } else if (conditionModifier.conditionType == ConditionType.CombatInTargetGridByCharacterId) { conditionModifier.conditionArgs.Clear(); conditionModifier.conditionArgs.Add(new Param("角色ID", conditionModifier.roleID, "")); conditionModifier.conditionArgs.Add(new Param("是否路过", conditionModifier.isPass, "")); foreach (var unitID in conditionModifier.unitIDList) { conditionModifier.conditionArgs.Add(new Param("地格位置", unitID, "")); } } // 任意角色在目标位置特殊处理 else if (conditionModifier.conditionType == ConditionType.CombatInTargetGrid) { conditionModifier.conditionArgs.Clear(); conditionModifier.conditionArgs.Add(new Param("是否路过", conditionModifier.isPass, "")); foreach (var unitID in conditionModifier.unitIDList) { conditionModifier.conditionArgs.Add(new Param("地格位置", unitID, "")); } } // 撤退数量特殊处理 else if (conditionModifier.conditionType == ConditionType.CombatFallBack && conditionModifier.conditionArgs.Count > 0) { currentLevelData.fallBackCount = (int)conditionModifier.conditionArgs[0].Arg; } } #endregion #region 关卡ID相关 /// /// 关卡ID 通过关卡数据 json文件名 获得 /// [LabelText("手动指定关卡ID"), Indent, LabelWidth(100), ShowIf("needManualInputLevelID")] [OnValueChanged("GetLevelMonsterClass")] [InfoBox("无法正确获得关卡ID,请手动指定")] [SerializeField] public string levelID; /// /// 需要手动输入关卡ID /// [HideInInspector] public bool needManualInputLevelID; /// /// 获取关卡怪物列表 /// public void GetLevelMonsterClass() { if (!int.TryParse(levelID, out int levelIndex) || !LevelCfg.DataMap.TryGetValue(levelIndex, out _)) { needManualInputLevelID = true; DebugUtil.LogError("无对应的关卡ID请手动输入关卡ID"); curEnemyMonsterID = 0; enemyNpcObject = null; return; } enemyMonsterIDs = new List(); selfMonsterIDs = new List(); midMonsterIDs = new List(); foreach (var monster in MonsterCfg.DataMap) { string strID = monster.Key.ToString(); string tempID = strID[..6]; // 关卡ID相同 if (!tempID.Equals(levelID)) continue; switch (monster.Value.NpcType) { case EMonsterType.EnemyCharacter: { enemyMonsterIDs.Add(monster.Key); } break; case EMonsterType.SelfCharacter: { selfMonsterIDs.Add(monster.Key); } break; case EMonsterType.Vehicle: case EMonsterType.FightBuild: { midMonsterIDs.Add(monster.Key); enemyMonsterIDs.Add(monster.Key); selfMonsterIDs.Add(monster.Key); } break; default: DebugUtil.LogError($"未处理类型:{monster.Value.NpcType}"); break; } } if (enemyMonsterIDs.Count <= 0 && midMonsterIDs.Count <= 0) DebugUtil.LogError($"请检查Monster表,没有{levelID}关卡的怪物数据"); } #endregion #region 加载 private void LoadLevel(LevelData levelData) { DestroyNpcPrefab(); _enemyNpcObjectWaveDic.Clear(); _midNpcObjectWaveDic.Clear(); _showNpcInfos.Clear(); _selfNpcObjectDic.Clear(); currentLevelData = levelData; //加载地区信息 curRegionData = new RegionData { preparingRegionId = currentLevelData.preparingRegionId, fallbackRegionId = currentLevelData.fallbackRegionId, enemyFallbackRegionId = currentLevelData.enemyFallbackRegionId, preparingRegionToward = currentLevelData.preparingRegionToward, pvpDefendRegionId = currentLevelData.pvpDefendRegionId, pvpDefendToward = currentLevelData.pvpDefendToward }; _mapManager = FindObjectOfType(); _curMap = _mapManager.Map; _curMap.TerrainGridSystem.OnMouseClickOnGrid += OnCellClick; // 加载占领数据 captureEditorInfo = new List(); foreach (var captureInfo in currentLevelData.CapturePoints) { var captureEditor = new CaptureEditorInfo() { capturePoint = _curMap.TGSCellIndex2BlockPosition(captureInfo.CapturePoint), captureProgress = captureInfo.CaptureProgress, }; captureEditorInfo.Add(captureEditor); } // 加载扛旗数据 flagEditorInfo = new FlagEditorInfo(); foreach (var flagStartPoint in currentLevelData.flagStartPoints) { var pos = _curMap.TGSCellIndex2BlockPosition(flagStartPoint); flagEditorInfo.flagStartPoints.Add(pos); } foreach (var flagEndPoint in currentLevelData.flagEndPoints) { var pos = _curMap.TGSCellIndex2BlockPosition(flagEndPoint); flagEditorInfo.flagEndPoints.Add(pos); } // 加载撤离数据 fallbackEditorInfo = new FallbackEditorInfo(); foreach (var fallback in currentLevelData.fallbackSinglePoints) { var pos = _curMap.TGSCellIndex2BlockPosition(fallback); fallbackEditorInfo.fallbackSinglePoints.Add(pos); } foreach (var fallback in currentLevelData.fallbackReusablePoints) { var pos = _curMap.TGSCellIndex2BlockPosition(fallback); fallbackEditorInfo.fallbackReusablePoints.Add(pos); } // 加载刷新点位 refreshPoints = new List(); refreshPoints.AddRange(currentLevelData.refreshPoints); // 加载指定路线 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; // 加载关卡引导立标数据 setFlagEditorInfos = currentLevelData.setFlagGuideDatas.Select(x => new SetFlagGuideEditorInfo(x)).ToList(); // 加载信息板数据 infoPanelGuideDatas = currentLevelData.infoPanelGuideDatas; // 加载敌方NPC到场景中 LoadEnemyNpcInfo(); // 加载中立NPC到场景中 LoadMidNpcInfo(); // 加载己方NPC到场景中 LoadSelfNpcInfo(); } private void LoadEnemyNpcInfo() { var monsterConfigDic = MonsterCfg.DataMap; currentEenemyNpcInfo = 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); currentEenemyNpcInfo.Add(npcEditor); var npcObj = LoadEnemyNpcPrefab(npcInfo.id); if (npcObj != null) { var setPos = _curMap.BlockPosition2WorldPosition(npcInfo.position); var yaw = LevelUtils.SetCharaterToward(npcInfo.npcToward); if (_enemyNpcObjectWaveDic.TryGetValue(npcInfo.waveIndex, out var npcObjectDic)) { npcObjectDic.Add(npcEditor, Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0))); } else { npcObjectDic = new Dictionary { { npcEditor, Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0)) } }; _enemyNpcObjectWaveDic.Add(npcInfo.waveIndex, npcObjectDic); } } index++; } } private void LoadMidNpcInfo() { var monsterConfigDic = MonsterCfg.DataMap; currentMidNpcInfo = new(); var index = 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); currentMidNpcInfo.Add(npcEditor); var npcObj = LoadMidNpcPrefab(npcInfo.id); if (npcObj != null) { var setPos = _curMap.BlockPosition2WorldPosition(npcInfo.position); var yaw = LevelUtils.SetCharaterToward(npcInfo.npcToward); if (_midNpcObjectWaveDic.TryGetValue(npcInfo.waveIndex, out var npcObjectDic)) { npcObjectDic.Add(npcEditor, Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0))); } else { npcObjectDic = new Dictionary { { npcEditor, Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0)) } }; _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); currentSelfNpcInfo.Add(npcEditor); var npcObj = LoadSelfNpcPrefab(selfNpcInfo.id); if (npcObj != null) { var setPos = _curMap.BlockPosition2WorldPosition(selfNpcInfo.position); var yaw = LevelUtils.SetCharaterToward(selfNpcInfo.npcToward); _selfNpcObjectDic.Add(selfNpcInfo.position, Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0))); } index++; } } #endregion #region 点击添加事件 private bool ResponseClickOnCell => isEditingEnemyNpc || isEditingSelfNpc || isEditingMidNpc || isEditingNpcPos || isEditingFlagStart || isEditingFlagEnd || isEditingCapture || isEditingFallbackReusable || isEditingFallbackSingle || isEditingRefreshID || isEditingRoadInfo || isEditingAreaInfo; 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 (isEditingFlagStart) { EditingFlagStart(clickPosition, true, opTrue); } else if (isEditingFlagEnd) { EditingFlagStart(clickPosition, false, opTrue); } else if (isEditingFallbackSingle) { EditingFallBack(clickPosition, true, opTrue); } else if (isEditingFallbackReusable) { EditingFallBack(clickPosition, false, opTrue); } else if (isEditingCapture) { EditingCapture(clickPosition, 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 (isEditingRefreshID) { EditRefreshID(cellindex, opTrue); } else if (isEditingRoadInfo && isEditingRoad) { EditRoad(cellindex, opTrue); } else if (isEditingAreaInfo && isEditingArea) { EditArea(cellindex, opTrue); } } private void EditingFallBack(Vector2Int clickPosition, bool isEditingSingle, bool isAdd) { if (isEditingSingle) { if (isAdd && !fallbackEditorInfo.fallbackSinglePoints.Contains(clickPosition)) { DebugUtil.Log("单次撤离点添加: {0}", clickPosition); fallbackEditorInfo.fallbackSinglePoints.Add(clickPosition); } else if (!isAdd && fallbackEditorInfo.fallbackSinglePoints.Contains(clickPosition)) { DebugUtil.Log("单次撤离点移除: {0}", clickPosition); fallbackEditorInfo.fallbackSinglePoints.Remove(clickPosition); } } else { if (isAdd && !fallbackEditorInfo.fallbackReusablePoints.Contains(clickPosition)) { DebugUtil.Log("复用撤离点添加: {0}", clickPosition); fallbackEditorInfo.fallbackReusablePoints.Add(clickPosition); } else if (!isAdd && fallbackEditorInfo.fallbackReusablePoints.Contains(clickPosition)) { DebugUtil.Log("复用撤离点移除: {0}", clickPosition); fallbackEditorInfo.fallbackReusablePoints.Remove(clickPosition); } } } private void EditingFlagStart(Vector2Int clickPosition, bool isEditingStart, bool isAdd) { if (isEditingStart) { if (isAdd && !flagEditorInfo.flagStartPoints.Contains(clickPosition)) { DebugUtil.Log("扛旗起点添加: {0}", clickPosition); flagEditorInfo.flagStartPoints.Add(clickPosition); } else if (!isAdd && flagEditorInfo.flagStartPoints.Contains(clickPosition)) { DebugUtil.Log("扛旗起点移除: {0}", clickPosition); flagEditorInfo.flagStartPoints.Remove(clickPosition); } } else { if (isAdd && !flagEditorInfo.flagEndPoints.Contains(clickPosition)) { DebugUtil.Log("扛旗终点添加: {0}", clickPosition); flagEditorInfo.flagEndPoints.Add(clickPosition); } else if (!isAdd && flagEditorInfo.flagEndPoints.Contains(clickPosition)) { DebugUtil.Log("扛旗终点移除: {0}", clickPosition); flagEditorInfo.flagEndPoints.Remove(clickPosition); } } } private void EditingCapture(Vector2Int clickPosition, bool isAdd) { var isExist = false; CaptureEditorInfo existCapture = null; foreach (var capture in captureEditorInfo) { if (capture.capturePoint == clickPosition) { isExist = true; existCapture = capture; } } if (isAdd) { if (!isExist) { DebugUtil.Log("占领点添加: {0}", clickPosition); captureEditorInfo.Add(new CaptureEditorInfo() { capturePoint = clickPosition }); } } else { if (existCapture != null) { DebugUtil.Log("占领点移除: {0}", clickPosition); captureEditorInfo.Remove(existCapture); } } } 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 (2 == 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 (0 == npcTroopId && _enemyNpcObjectWaveDic.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 (1 == 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 { RemoveEnemyNpc(clickPosition); } } private void AddEnemyNpc(Vector2Int clickPosition) { if (enemyNpcObject == null || curEnemyMonsterID == 0) { DebugUtil.LogError("请选择正确的NPC!!"); return; } _enemyNpcObjectWaveDic ??= new Dictionary>(); NPCInfo npcInfo = new() { id = curEnemyMonsterID, position = clickPosition, index = currentEenemyNpcInfo.Count + 1, troopId = ETroopsId.Enemy, }; var level = 0; var aiType = 0; var monsterConfigDic = MonsterCfg.DataMap; if (monsterConfigDic.TryGetValue(curEnemyMonsterID, out var monster)) { level = monster.MonsterLevel; aiType = monster.AIType; } else { DebugUtil.LogError("获取怪物[{0}]配置数据错误", curEnemyMonsterID); } var npcEditor = new NpcEditorInfo(npcInfo, currentEenemyNpcInfo.Count + 1, level, aiType, ETroopsId.Enemy); currentEenemyNpcInfo.Add(npcEditor); var pos = _curMap.BlockPosition2WorldPosition(npcInfo.position); if (_enemyNpcObjectWaveDic.TryGetValue(npcInfo.waveIndex, out var npcObjectDic)) { var obj = Instantiate(enemyNpcObject, pos, enemyNpcObject.transform.rotation); npcObjectDic.Add(npcEditor, obj); } else { var obj = Instantiate(enemyNpcObject, pos, enemyNpcObject.transform.rotation); npcObjectDic = new Dictionary { { npcEditor, obj } }; _enemyNpcObjectWaveDic.Add(npcInfo.waveIndex, npcObjectDic); } DebugUtil.Log( $"添加了第[ {npcInfo.waveIndex} ]波的[ {npcInfo.index} ]怪物到 [ {npcInfo.position} ]位置上, 其预制体为: {enemyNpcObject.name}"); } private void RemoveEnemyNpc(Vector2Int clickPosition) { var npc = currentEenemyNpcInfo.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); } currentEenemyNpcInfo.Remove(npc); } ReorderEnemyNpcID(); } #endregion #region 已放NPC编辑方法 private void EditSelfNpc(Vector2Int clickPosition, bool isAdd) { if (isAdd) { AddSelfNpc(clickPosition); } else { RemoveSelfNpc(clickPosition); } } private void AddSelfNpc(Vector2Int clickPosition) { if (selfNpcObject == null || curSelfNpcID == 0) { DebugUtil.LogError("请选择正确的己方NPC!!"); return; } _selfNpcObjectDic ??= new Dictionary(); 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 monsterConfigDic = MonsterCfg.DataMap; if (monsterConfigDic.TryGetValue(curSelfNpcID, out var monster)) { level = monster.MonsterLevel; aiType = monster.AIType; } else { DebugUtil.LogError("获取己方Npc[{0}]配置数据错误", curSelfNpcID); } currentSelfNpcInfo.Add(new NpcEditorInfo(npcInfo, SelfID + currentSelfNpcInfo.Count + 1, level, aiType, ETroopsId.Self)); var pos = _curMap.BlockPosition2WorldPosition(npcInfo.position); _selfNpcObjectDic.Add(npcInfo.position, Instantiate(selfNpcObject, pos, selfNpcObject.transform.rotation)); 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 RemoveMidNpc(clickPosition); } private void AddMidNpc(Vector2Int clickPosition) { if (midNpcObject == null || curMidMonsterID == 0) { DebugUtil.LogError("请选择正确的NPC!!"); return; } _midNpcObjectWaveDic ??= new Dictionary>(); NPCInfo npcInfo = new() { id = curMidMonsterID, position = clickPosition, index = currentMidNpcInfo.Count + 1, troopId = ETroopsId.Neutral, }; var level = 0; var aiType = 0; var monsterConfigDic = MonsterCfg.DataMap; if (monsterConfigDic.TryGetValue(curMidMonsterID, out var monster)) { level = monster.MonsterLevel; aiType = monster.AIType; } else { DebugUtil.LogError("获取怪物[{0}]配置数据错误", curMidMonsterID); } var npcEditor = new NpcEditorInfo(npcInfo, currentMidNpcInfo.Count + 1, level, aiType, ETroopsId.Neutral); 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); npcObjectDic.Add(npcEditor, obj); } else { var obj = Instantiate(midNpcObject, pos, midNpcObject.transform.rotation); npcObjectDic = new Dictionary { { 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 private void EditRefreshID(int clickPosition, bool isAdd) { if (isAdd && !refreshPoints.Contains(clickPosition)) { refreshPoints.Add(clickPosition); _curMap.TerrainGridSystem.CellToggleRegionSurface(clickPosition, true, RegionColor); } else if (!isAdd && refreshPoints.Contains(clickPosition)) { refreshPoints.Remove(clickPosition); _curMap.TerrainGridSystem.CellHideRegionSurface(clickPosition); } } /// /// 编辑路线 /// /// /// 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(); } } /// /// 编辑区域 /// /// /// 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 敌方Npc某些值改变时响应事件 private void ReorderEnemyNpcID() { for (var i = 0; i < currentEenemyNpcInfo.Count; i++) { currentEenemyNpcInfo[i].id = i + 1; currentEenemyNpcInfo[i].npcInfo.index = currentEenemyNpcInfo[i].id; } } private void ReorderMidNpcID() { for (var i = 0; i < currentMidNpcInfo.Count; i++) { currentMidNpcInfo[i].id = i + 1; currentMidNpcInfo[i].npcInfo.index = currentMidNpcInfo[i].id; } } private void ReorderSelfNpcID() { for (var i = 0; i < currentSelfNpcInfo.Count; i++) { currentSelfNpcInfo[i].id = SelfID + i + 1; } } private void ChangeEnemyNpcID(NpcEditorInfo npcEditorInfo, int newMonsterID) { if (_enemyNpcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj)) { var newObj = LoadEnemyNpcPrefab(newMonsterID); if (newObj != null) { npcEditorInfo.npcInfo.id = newMonsterID; npcEditorInfo.npcInfo.aiTypeID = MonsterCfg.DataMap[newMonsterID].AIType; npcEditorInfo.level = MonsterCfg.DataMap[newMonsterID].MonsterLevel; npcObjectDic[npcEditorInfo] = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); DestroyImmediate(npcObj); DebugUtil.Log( $"从[ {npcEditorInfo.npcInfo.position} ]的怪物ID改为:[ {npcEditorInfo.npcInfo.id} ]"); } } } } private void ChangeMidNpcID(NpcEditorInfo npcEditorInfo, int newMonsterID) { if (_midNpcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj)) { var newObj = LoadMidNpcPrefab(newMonsterID); if (newObj != null) { npcEditorInfo.npcInfo.id = newMonsterID; npcEditorInfo.npcInfo.aiTypeID = MonsterCfg.DataMap[newMonsterID].AIType; npcEditorInfo.level = MonsterCfg.DataMap[newMonsterID].MonsterLevel; npcObjectDic[npcEditorInfo] = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); DestroyImmediate(npcObj); DebugUtil.Log( $"从[ {npcEditorInfo.npcInfo.position} ]的怪物ID改为:[ {npcEditorInfo.npcInfo.id} ]"); } } } } private void ChangeSelfNpcID(NPCInfo npcInfoInfo, int newMonsterID) { if (_selfNpcObjectDic.TryGetValue(npcInfoInfo.position, out var npcObj)) { var newObj = LoadSelfNpcPrefab(newMonsterID); if (newObj != null) { npcInfoInfo.id = newMonsterID; npcInfoInfo.aiTypeID = MonsterCfg.DataMap[newMonsterID].AIType; _selfNpcObjectDic[npcInfoInfo.position] = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); DestroyImmediate(npcObj); DebugUtil.Log("位置上[ {0} ]的fight怪物ID改为: [ {1} ] ", npcInfoInfo.position, npcInfoInfo.id); } } } #endregion #region 检查关卡 private const string PreparingRegionErrorMessage = "关卡出战区设置错误,该地图没有:<{0}>号地区。\n"; private const string FallbackRegionErrorMessage = "关卡撤退区设置错误,该地图没有:<{0}>号地区。\n"; private const string EnemyFallbackRegionErrorMessage = "关卡敌方撤退区设置错误,该地图没有:<{0}>号地区。\n"; private const string FixedMessage = "已将错误区域设置为默认<0>号地区。"; private const string CaptureErrorMessage = "占领条件参数大于配置的占领区域。"; 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 (!_curMap.MapData.IsRegionExist(curRegionData.fallbackRegionId)) { errorMessage += string.Format(FallbackRegionErrorMessage, curRegionData.fallbackRegionId); curRegionData.fallbackRegionId = 0; } if (!_curMap.MapData.IsRegionExist(curRegionData.enemyFallbackRegionId)) { errorMessage += string.Format(EnemyFallbackRegionErrorMessage, curRegionData.enemyFallbackRegionId); curRegionData.enemyFallbackRegionId = 0; } // 检查占领条件相关 /* if (!CheckLevelCondition()) { CustomPopUpWindow.PopUp(CaptureErrorMessage); } */ if (!string.IsNullOrEmpty(errorMessage)) { CustomPopUpWindow.PopUp(errorMessage + FixedMessage); } } /*private bool CheckLevelCondition() { if (currentLevelData.stages.Any(stage => stage.successCondition.conditionType == ConditionType.CombatLeastOccupy && stage.successCondition.conditionArgs[0].Arg > captureEditorInfo.Count)) { return false; } if (currentLevelData.starTarget.Any(modifier => modifier.conditionType == ConditionType.CombatLeastOccupy && modifier.conditionArgs[0].Arg > captureEditorInfo.Count)) { return false; } if (currentLevelData.scoreTarget.Any(modifier => modifier.conditionType == ConditionType.CombatLeastOccupy && modifier.conditionArgs[0].Arg > captureEditorInfo.Count)) { return false; } return true; }*/ #endregion #region 内部方法 /// /// 关卡加载时 加载敌方预制体 /// /// Monster表ID /// 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(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(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(prefabPath); if (obj == null) { DebugUtil.LogError("请检查敌方建筑类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath); } } else { DebugUtil.LogError("请检查敌方建筑类: {0}的配置是否存在", curMonsterClassID); } return obj; } default: return null; } } /// /// 关卡加载时 加载中立预制体 /// /// Monster表ID /// 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(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(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(prefabPath); if (obj == null) { DebugUtil.LogError("请检查敌方建筑类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath); } } else { DebugUtil.LogError("请检查敌方建筑类: {0}的配置是否存在", curMonsterClassID); } return obj; } default: return null; } } /// /// 关卡加载时 加载己方预制体 /// /// Monster表ID /// 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(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(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(prefabPath); if (obj == null) { DebugUtil.LogError("请检查己方建筑类: {0}的预制体路径是否存在,对应的完整路径: {1}", curFightNpcID, prefabPath); } } else { DebugUtil.LogError("请检查己方建筑类: {0}的配置是否存在", curFightNpcID); } return obj; } default: return null; } } private void DestroyNpcPrefab() { foreach (var npcData in _enemyNpcObjectWaveDic.Values) { foreach (var npc in npcData.Values) { DestroyImmediate(npc); } } foreach (var npcData in _midNpcObjectWaveDic.Values) { foreach (var npc in npcData.Values) { DestroyImmediate(npc); } } foreach (var npc in _selfNpcObjectDic) { DestroyImmediate(npc.Value); } } 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.starTarget.Any(modifier => modifier.conditionType == conditionType)) { return true; } if (currentLevelData.scoreTarget.Any(modifier => modifier.conditionType == conditionType)) { return true; } return false; } #endregion #region MainLife private void Awake() { SceneView.duringSceneGui += OnSceneGUI; } protected override void OnEnable() { TerrainTypeConfig.Load(); _enemyNpcObjectWaveDic = new Dictionary>(); _midNpcObjectWaveDic = new Dictionary>(); _selfNpcObjectDic = new Dictionary(); _showNpcInfos = new List(); base.OnEnable(); } protected override void OnDisable() { currWindow = null; DestroyNpcPrefab(); //DestoryBuildPrefab(); _enemyNpcObjectWaveDic = null; _midNpcObjectWaveDic = null; _selfNpcObjectDic = null; base.OnDisable(); } protected override void OnDestroy() { 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 Draw private bool IsShowNpcID => _showNpcInfos != null && _showNpcInfos.Count > 0; private List _showNpcInfos; private void OnSceneGUI(SceneView sceneView) { DrawRoadPath(); DrawAreaPath(); DrawNpcID(); } 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 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 (_enemyNpcObjectWaveDic.TryGetValue(npc.npcInfo.waveIndex, out var enemyNpcObjectDic)) { var waveID = npc.npcInfo.waveIndex; if (enemyNpcObjectDic.TryGetValue(npc, out var npcObj)) { var content = $"{waveID},{npc.id}"; Handles.Label(npcObj.transform.position, content, labelStyle); } } else if (_midNpcObjectWaveDic.TryGetValue(npc.npcInfo.waveIndex, out var midNpcObjectDic)) { var waveID = npc.npcInfo.waveIndex; if (midNpcObjectDic.TryGetValue(npc, out var npcObj)) { var content = $"{waveID},{npc.id}"; Handles.Label(npcObj.transform.position, content, labelStyle); } } else if (_selfNpcObjectDic.TryGetValue(npc.npcInfo.position, out var selfNpcObj)) { Handles.Label(selfNpcObj.transform.position, npc.id.ToString(), labelStyleSelf); } } } #endregion }