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>(); _midNpcObjectWaveDic = new Dictionary>(); _selfNpcObjectDic = new Dictionary(); _showNpcInfos = new List(); _blockPrefabDic = new Dictionary(); 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(); ClearDefenseRegionColor(); 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 || isEditingFlagStart || isEditingFlagEnd || isEditingCaptureInfo || isEditingFallbackReusable || isEditingRetreat || isEditingRoadInfo || isEditingAreaInfo || canEditorBlockArea || canEditorDefenseArea || canEditorMinesArea; 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 (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); } } 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 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 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(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>(); 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 { { 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(); 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>(); 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 { { 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 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 加载 private void LoadLevel(LevelData levelData) { DestroyNpcPrefab(); _enemyNpcObjectWaveDic.Clear(); _midNpcObjectWaveDic.Clear(); _showNpcInfos.Clear(); _selfNpcObjectDic.Clear(); currentLevelData = levelData; _mapManager = FindObjectOfType(); _curMap = _mapManager.Map; _curMap.TerrainGridSystem.OnMouseClickOnGrid += OnCellClick; // 加载关卡任务 taskInfos = currentLevelData.taskInfos.Select(t => new TaskInfoEditor(t)).ToList(); // 加载胜利失败星级条件 /*successTaskID = currentLevelData.successTaskID; failTaskID = currentLevelData.failTaskID; starTaskIDs[0] = currentLevelData.starTaskIDs[0]; starTaskIDs[1] = currentLevelData.starTaskIDs[1]; starTaskIDs[2] = currentLevelData.starTaskIDs[2]; successTask = GetTaskName(currentLevelData.successTaskID); failTask = GetTaskName(currentLevelData.failTaskID); oneStarTask = GetTaskName(currentLevelData.starTaskIDs[0]); twoStarTask = GetTaskName(currentLevelData.starTaskIDs[1]); threeStarTask = GetTaskName(currentLevelData.starTaskIDs[2]);*/ // 加载评分条件 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); } // 加载扛旗数据 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); } DestroyAllBlockObj(); UpdateMinesRegion(); UpdateBlockRegion(); UpdateDefenseRegion(); // 加载撤离数据 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(); // 加载当前关卡的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 { { 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 { { 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); } } } /// /// 关卡加载时 加载敌方预制体 /// /// 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() { 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 _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 波次信息加载 /// /// 加载波次信息(从NPC数据中重建) /// private void LoadWaveInfo() { waveInfoEditors.Clear(); if (currentEnemyNpcInfo == null || currentEnemyNpcInfo.Count == 0) { DebugUtil.Log("没有敌方NPC,无需加载波次信息"); return; } // 从NPC数据中收集波次信息 // 结构: waveID -> List var waveGroupsMap = new Dictionary>(); 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(); } // 收集该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 }