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.FightCfg; using cfg.MonsterCfg; using cfg.VehicleCfg; using Sirenix.OdinInspector.Editor; using Level = cfg.LevelCfg.Level; using ObjectFieldAlignment = Sirenix.OdinInspector.ObjectFieldAlignment; public class LevelEditorWindow : OdinEditorWindow { 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); } #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); } } [Serializable] public class NpcEditorInfo { [HideInInspector] public bool selfNpc; [ReadOnly] [LabelText("序号ID")] public int id; [LabelText("MonsterID")] [ValueDropdown("GetMonsterIDs")] [OnValueChanged("ChangeNpcID")] public int monsterId; private IEnumerable GetMonsterIDs() { if (!selfNpc) return currWindow.GetMonsterIDs(); return currWindow.GetSelfNpcIDs(); } private void ChangeNpcID() { if (!selfNpc) currWindow.ChangeNpcID(npcInfo, monsterId); else currWindow.ChangeFightNpcID(npcInfo, monsterId); } [LabelText("朝向")] [OnValueChanged("NpcTowardOnChange")] public LevelData.CharacterToward npcToward; private void NpcTowardOnChange() { npcInfo.npcToward = npcToward; if (!selfNpc) currWindow.ChangeNpcToward(npcInfo.position, npcToward); else currWindow.ChangeSelfNpcToward(npcInfo.position, npcToward); } [HideLabel] public LevelData.NPCInfo npcInfo; public NpcEditorInfo(LevelData.NPCInfo npcInfo, int index, bool isSelfNpc = false) { this.npcInfo = npcInfo; monsterId = npcInfo.id; id = index; selfNpc = isSelfNpc; npcToward = this.npcInfo.npcToward; } [LabelText("位置调整")] [OnValueChanged("ChangeNpcPos")] public bool isChangePos; private void ChangeNpcPos() { if (isChangePos) { currWindow.isEditingPatrolInfo = false; if (currWindow._currEditingNpcInfo != null) DebugUtil.LogWarning("检查其他怪物是否处于位置调整或编辑巡逻信息状态"); } currWindow.isEditingNpcPos = isChangePos; currWindow._currEditingNpcInfo = isChangePos ? npcInfo : null; } [LabelText("显示该怪物序号")] [OnValueChanged("ShowNpcID")] public bool isShowNpcID; private void ShowNpcID() { if (isShowNpcID && !currWindow._showNpcInfos.Contains(this)) currWindow._showNpcInfos.Add(this); else { currWindow._showNpcInfos.Remove(this); } } private bool _isEditing; [Button("开始编辑巡逻信息")] [HideIf("_isEditing")] private void StartEditNpc() { currWindow.StartEditPatrolInfo(npcInfo); _isEditing = true; } [Button("完成编辑巡逻信息")] [ShowIf("_isEditing")] private void FinishEditNpc() { currWindow.FinishEditPatrolInfo(); _isEditing = false; } } [Serializable] public class CaptureEditorInfo { [LabelText("占领点位")] public Vector2Int capturePoint; [LabelText("占领时间")] public int captureProgress; } [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; } } } [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; } } } #endregion #region 配置表 private Level LevelCfg => EditorTableManager.instance.tables.Level; private Monster MonsterCfg => EditorTableManager.instance.tables.Monster; private VehicleConfig VehicleCfg => EditorTableManager.instance.tables.VehicleConfig; private FightNPCConfig FightNpcCfg => EditorTableManager.instance.tables.FightNPCConfig; #endregion private const int SelfID = 200; private static readonly Color RegionColor = MapUtils.GetRGBAColor("FF960072"); private static LevelEditorWindow currWindow; private MapManager _mapManager; private Dictionary _npcObjectDic; private Dictionary _selfNpcObjectDic; private Map _curMap; private LevelEditor _levelEditor; private LevelData.NPCInfo _currEditingNpcInfo; private bool isEditingPatrolInfo; private bool isEditingNpcPos; private bool isEditingFlagStart; private bool isEditingFlagEnd; private bool isEditingFallbackSingle; private bool isEditingFallbackReusable; private bool IsShowNpcID => _showNpcInfos != null && _showNpcInfos.Count > 0; private List _showNpcInfos; private bool ResponseClickOnCell => isEditingNpc || isEditingSelfNpc || isEditingNpcPos || isEditingPatrolInfo || isEditingFlagStart || isEditingFlagEnd || isEditingCapture || isEditingFallbackReusable || isEditingFallbackSingle; [LabelText("当前关卡数据"), Indent] [OnValueChanged("OnCurrentLevelDataChanged")] public LevelData currentLevelData = null; [LabelText("当前占领数据"), Indent] [ShowIf("EditorCaptureInfo")] public List captureEditorInfo = new(); [LabelText("编辑占领数据"), Indent] [ShowIf("EditorCaptureInfo")] public bool isEditingCapture = false; private bool EditorCaptureInfo() { return CheckConditionSame(ConditionType.CombatLeastOccupy); } [LabelText("当前扛旗数据"), Indent] [ShowIf("EditorFlagInfo")] public FlagEditorInfo flagEditorInfo = new(); private bool EditorFlagInfo() { return CheckConditionSame(ConditionType.CombatSubmitFlag); } [LabelText("当前撤离数据"), Indent] [ShowIf("EditorFallbackInfo")] public FallbackEditorInfo fallbackEditorInfo = new(); private bool EditorFallbackInfo() { return CheckConditionSame(ConditionType.CombatFallBack); } [LabelText("当前地区数据"), Indent] [OnValueChanged("OnCurrentLevelDataChanged")] public RegionData curRegionData = null; [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; } /// /// 关卡ID 通过关卡数据 json文件名 获得 /// [LabelText("手动指定关卡ID"), Indent, LabelWidth(100), ShowIf("needManualInputLevelID")] [OnValueChanged("GetLevelMonsterClass")] [InfoBox("无法正确获得关卡ID,请手动指定")] [SerializeField] public string levelID; #region 敌方Npc [LabelText("编辑NPC"), Indent] [LabelWidth(100)] [SerializeField] [OnValueChanged("OnIsEditingNpcChanged")] public bool isEditingNpc; [LabelText("显示所有怪物序号ID"), Indent] [OnValueChanged("ShowAllNpcId")] [SerializeField] public bool isShowAllNpcID; [LabelText("怪物ID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("MonsterIDChanged")] [ValueDropdown("GetMonsterIDs")] [ShowIf("isEditingNpc")] public int curMonsterID; [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("NPC预制体预览图"), ShowIf("isEditingNpc")] public GameObject npcObject; [LabelText("当前怪物列表"), Indent, ShowIf("isEditingNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true)] public List currentNpcInfo = null; private IEnumerable GetMonsterIDs() { return monsterIDs; } private void MonsterIDChanged() { var obj = LoadNpcPrefab(curMonsterID); if (obj != null) npcObject = obj; } private void OnIsEditingNpcChanged() { if (isEditingNpc && isEditingSelfNpc) isEditingSelfNpc = false; } private void ShowAllNpcId() { foreach (var npc in currentNpcInfo) { npc.isShowNpcID = isShowAllNpcID; if (isShowAllNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 己方Npc [LabelText("编辑己方NPC"), Indent] [LabelWidth(100)] [OnValueChanged("OnIsEditingSelfNpcChanged")] [SerializeField] public bool isEditingSelfNpc; [LabelText("显示所有己方序号ID"), Indent] [OnValueChanged("ShowAllSelfNpcId")] [SerializeField] public bool isShowAllSelfNpcID; [LabelText("己方NpcID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("SelfNpcIDChanged")] [ValueDropdown("GetSelfNpcIDs")] [ShowIf("isEditingSelfNpc")] public int curSelfNpcID; [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("己方NPC预制体预览图"), ShowIf("isEditingSelfNpc")] public GameObject selfNpcObject; [LabelText("当前己方NPC列表"), Indent, ShowIf("isEditingSelfNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true)] public List currentSelfNpcInfo = null; private IEnumerable GetSelfNpcIDs() { return selfMonsterIDs; } private void SelfNpcIDChanged() { var obj = LoadSelfNpcPrefab(curSelfNpcID); if (obj != null) selfNpcObject = obj; } private void OnIsEditingSelfNpcChanged() { if (isEditingNpc && isEditingSelfNpc) isEditingNpc = 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 保存 [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; // NPC foreach (var npcEditor in currentNpcInfo) { npcEditor.npcInfo.index = npcEditor.id; } currentLevelData.npcInfos = currentNpcInfo.Select(editorNpcInfo => editorNpcInfo.npcInfo).ToList(); // 己方NPC foreach (var npcFightEditorInfo in currentSelfNpcInfo) { npcFightEditorInfo.npcInfo.index = npcFightEditorInfo.id; } currentLevelData.selfNpcInfos = currentSelfNpcInfo.Select(editorFightNpcInfo => editorFightNpcInfo.npcInfo).ToList(); // 条件 ProcessCondition(); // 占领数据 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); } _levelEditor.SaveLevelData(); } private void ProcessCondition() { ProcessConditionForList(currentLevelData.stages.Select(stage => stage.successCondition)); ProcessConditionForList(currentLevelData.starTarget); ProcessConditionForList(currentLevelData.scoreTarget); } private void ProcessConditionForList(IEnumerable conditionModifiers) { foreach (var conditionModifier in conditionModifiers) { if (conditionModifier.conditionType == ConditionType.CombatKillEnemy) { conditionModifier.conditionArgs.Clear(); foreach (var enemy in conditionModifier.killEnemyIDList) { conditionModifier.conditionArgs.Add(new Param("", enemy, "")); } } } foreach (var conditionModifier in conditionModifiers) { if (conditionModifier.conditionType == ConditionType.CombatFallBack && conditionModifier.conditionArgs.Count > 0) { currentLevelData.fallBackCount = (int)conditionModifier.conditionArgs[0].Arg; } } } #endregion /// /// 该关卡的怪物ID /// [HideInInspector] public List monsterIDs; /// /// 该关卡的怪物ID /// [HideInInspector] public List selfMonsterIDs; /// /// 该关卡的剧怪物ID /// [HideInInspector] public List fightMonsterIDs; /// /// 需要手动输入关卡ID /// [HideInInspector] public bool needManualInputLevelID; public void GetLevelMonsterClass() { if (!int.TryParse(levelID, out var levelIndex) || !LevelCfg.DataMap.TryGetValue(levelIndex, out var level)) { needManualInputLevelID = true; DebugUtil.LogError("无对应的关卡ID请手动输入关卡ID"); curMonsterID = 0; npcObject = null; return; } monsterIDs = new List(); selfMonsterIDs = new List(); foreach (var monster in MonsterCfg.DataMap) { var strID = monster.Key.ToString(); var tempID = strID.Substring(0, 6); //关卡ID相同 if (tempID.Equals(levelID)) { switch (monster.Value.NpcType) { case 2: selfMonsterIDs.Add(monster.Key); break; case 3: selfMonsterIDs.Add(monster.Key); break; default: monsterIDs.Add(monster.Key); break; } } } if (monsterIDs.Count <= 0) { DebugUtil.LogError("请检查Monster表,没有{0}关卡的怪物数据", levelID); } } #region 加载 private void LoadLevel(LevelData levelData) { DestroyNpcPrefab(); _npcObjectDic.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); } // 加载NPC到场景中 LoadNpcInfo(); // 加载己方NPC LoadSelfNpcInfo(); } private void LoadNpcInfo() { var monsterConfigDic = MonsterCfg.DataMap; currentNpcInfo = 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); currentNpcInfo.Add(npcEditor); npcInfo.level = monsterClassData.MonsterLevel; var npcObj = LoadNpcPrefab(npcInfo.id); if (npcObj != null) { var setPos = _curMap.BlockPosition2WorldPosition(npcInfo.position); var yaw = LevelUtils.SetCharaterToward(npcInfo.npcToward); _npcObjectDic.Add(npcInfo.position, Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0))); } 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, true); currentSelfNpcInfo.Add(npcEditor); selfNpcInfo.level = monsterClassData.MonsterLevel; 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 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 (isEditingPatrolInfo) { if (opTrue) AddPatrolInfo(clickPosition); else RemovePatrolInfo(clickPosition); } else if (isEditingNpcPos) { if (opTrue) ChangeNpcPos(clickPosition); } else if (isEditingNpc) { EditNpc(clickPosition, opTrue); } else if (isEditingSelfNpc) { EditSelfNpc(clickPosition, 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) { var oldPos = _currEditingNpcInfo.position; if (oldPos == clickPosition) return; var newWorldPos = _curMap.BlockPosition2WorldPosition(clickPosition); if (_npcObjectDic.TryGetValue(oldPos, out var npcObj)) { npcObj.transform.position = newWorldPos; _currEditingNpcInfo.position = clickPosition; _npcObjectDic.Remove(oldPos); _npcObjectDic.Add(clickPosition, npcObj); DebugUtil.Log("怪物: [ {0} ] 移动到 [ {1} ]位置上", _currEditingNpcInfo.id, clickPosition); } else if (_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); } } private void EditNpc(Vector2Int clickPosition, bool isAdd) { if (isAdd) { AddNpc(clickPosition); } else { RemoveNpc(clickPosition); } } private void AddNpc(Vector2Int clickPosition) { if (npcObject == null || curMonsterID == 0) { DebugUtil.LogError("请选择正确的NPC!!"); return; } _npcObjectDic ??= new Dictionary(); if (_npcObjectDic.ContainsKey(clickPosition)) { DebugUtil.LogError("该位置已有NPC存在,请重新选!!!"); return; } LevelData.NPCInfo npcInfo = new LevelData.NPCInfo { id = curMonsterID, position = clickPosition }; var monsterConfigDic = MonsterCfg.DataMap; if (monsterConfigDic.TryGetValue(curMonsterID, out var monster)) { npcInfo.level = monster.MonsterLevel; } else { DebugUtil.LogError("获取怪物[{0}]配置数据错误", curMonsterID); } currentNpcInfo.Add(new NpcEditorInfo(npcInfo, currentNpcInfo.Count + 1)); var pos = _curMap.BlockPosition2WorldPosition(npcInfo.position); _npcObjectDic.Add(npcInfo.position, Instantiate(npcObject, pos, npcObject.transform.rotation)); DebugUtil.Log("添加了怪物: [ {0} ] 到 [ {1} ]位置上, 其预制体为: {2}", npcInfo.id, npcInfo.position, npcObject.name); } private void RemoveNpc(Vector2Int clickPosition) { if (_npcObjectDic.TryGetValue(clickPosition, out var npcObj)) { var npc = currentNpcInfo.FirstOrDefault(npc => npc.npcInfo.position == clickPosition); if (npc != null) { currentNpcInfo.Remove(npc); DebugUtil.Log("从[ {0} ]位置上移除了[{1}]NPC", clickPosition, npcObj.name); DestroyImmediate(npcObj); _npcObjectDic.Remove(clickPosition); } } ReorderNpcID(); } 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; } LevelData.NPCInfo npcInfo = new LevelData.NPCInfo { id = curSelfNpcID, position = clickPosition }; currentSelfNpcInfo.Add(new NpcEditorInfo(npcInfo, SelfID + currentSelfNpcInfo.Count + 1)); 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(); } private void AddPatrolInfo(Vector2Int clickPosition) { _currEditingNpcInfo.patrolInfos.Add(new LevelData.PatrolInfo() { patrolPoint = clickPosition, }); } private void RemovePatrolInfo(Vector2Int clickPosition) { var index = _currEditingNpcInfo.patrolInfos.FindLastIndex(pInfo => pInfo.patrolPoint == clickPosition); if (index >= 0) _currEditingNpcInfo.patrolInfos.RemoveAt(index); } #endregion #region 值改变时响应时间事件 private void OnCurrentLevelDataChanged() { Debug.Log($"{GetType()}.OnCurrentLevelDataChanged"); } private void StartEditPatrolInfo(LevelData.NPCInfo npcInfo) { isEditingPatrolInfo = true; _currEditingNpcInfo = npcInfo; } private void FinishEditPatrolInfo() { isEditingPatrolInfo = false; _currEditingNpcInfo = null; } private void ReorderNpcID() { for (var i = 0; i < currentNpcInfo.Count; i++) { currentNpcInfo[i].id = i + 1; } } private void ReorderSelfNpcID() { for (var i = 0; i < currentSelfNpcInfo.Count; i++) { currentSelfNpcInfo[i].id = SelfID + i + 1; } } private void ChangeNpcID(LevelData.NPCInfo npcInfo, int newMonsterID) { if (_npcObjectDic.TryGetValue(npcInfo.position, out var npcObj)) { var newObj = LoadNpcPrefab(newMonsterID); if (newObj != null) { npcInfo.id = newMonsterID; _npcObjectDic[npcInfo.position] = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); DestroyImmediate(npcObj); DebugUtil.Log("位置上[ {0} ]的怪物ID改为: [ {1} ] ", npcInfo.position, npcInfo.id); } } } private void ChangeFightNpcID(LevelData.NPCInfo npcInfoInfo, int newMonsterID) { if (_selfNpcObjectDic.TryGetValue(npcInfoInfo.position, out var npcObj)) { var newObj = LoadSelfNpcPrefab(newMonsterID); if (newObj != null) { npcInfoInfo.id = newMonsterID; _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 内部方法 private GameObject LoadNpcPrefab(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 1: { 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 0: { var monsterClassCfg = EditorTableManager.instance.tables.MonsterClass; 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; } default: return null; } } 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 3: { 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 2: { 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("请检查FightNpc类: {0}的预制体路径是否存在,对应的完整路径: {1}", curFightNpcID, prefabPath); } } else { DebugUtil.LogError("请检查FightNpc类: {0}的配置是否存在", curFightNpcID); } return obj; } default: return null; } } private void DestroyNpcPrefab() { foreach (var npc in _npcObjectDic) { DestroyImmediate(npc.Value); } foreach (var npc in _selfNpcObjectDic) { DestroyImmediate(npc.Value); } } private void ChangeNpcToward(Vector2Int pos, LevelData.CharacterToward npcToward) { if (_npcObjectDic.TryGetValue(pos, out var npcObj)) { var yaw = LevelUtils.SetCharaterToward(npcToward); npcObj.transform.eulerAngles = new Vector3(0, yaw, 0); } } private void ChangeSelfNpcToward(Vector2Int pos, LevelData.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.stages.Any(stage => stage.successCondition.conditionType == conditionType)) { return true; } 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(); _npcObjectDic = new Dictionary(); _showNpcInfos = new List(); _selfNpcObjectDic = new Dictionary(); base.OnEnable(); } protected override void OnDisable() { currWindow = null; DestroyNpcPrefab(); _npcObjectDic = 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 void OnSceneGUI(SceneView sceneView) { DrawPatrolPath(); DrawNpcID(); } private void DrawPatrolPath() { if (!isEditingPatrolInfo) return; Vector3 lastWorldPos = Vector3.zero; for (int i = 0; i < _currEditingNpcInfo.patrolInfos.Count; i++) { var gridPos = _currEditingNpcInfo.patrolInfos[i].patrolPoint; var worldPos = _curMap.BlockPosition2WorldPosition(gridPos); 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 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 (_npcObjectDic.TryGetValue(npc.npcInfo.position, out var npcObj)) { Handles.Label(npcObj.transform.position, npc.id.ToString(), labelStyle); } else if (_selfNpcObjectDic.TryGetValue(npc.npcInfo.position, out var selfNpcObj)) { Handles.Label(selfNpcObj.transform.position, npc.id.ToString(), labelStyleSelf); } } } #endregion }