using cfg.LevelCfg; using Framework; using Gameplay; using Gameplay.Level; using Sirenix.OdinInspector; using Sirenix.OdinInspector.Editor; using Sirenix.Utilities; using Sirenix.Utilities.Editor; using System; using System.Collections.Generic; using System.Linq; using Gameplay.Utils; using TGS; using UnityEditor; using UnityEngine; using UnityEngine.Serialization; using Constants = Framework.Constants; using ObjectFieldAlignment = Sirenix.OdinInspector.ObjectFieldAlignment; //todo 序列化bug public class LevelEditorWindow : OdinEditorWindow { private static readonly Color regionColor = MapUtils.GetRGBAColor("FF960072"); [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 { [HideLabel] public LevelData.NPCInfo npcInfo; private bool _isEditing; public NPCEditorInfo(LevelData.NPCInfo npcInfo) { this.npcInfo = npcInfo; } [Button("开始编辑巡逻信息")] [HideIf("_isEditing")] private void StartEditNPC() { LevelEditorWindow.currWindow.StartEditPatrolInfo(npcInfo); _isEditing = true; } [Button("完成编辑巡逻信息")] [ShowIf("_isEditing")] private void FinishEditNPC() { LevelEditorWindow.currWindow.FinishEditPatrolInfo(); _isEditing = false; } } // private static LevelPresentation currentLevelPresentation; private MapManager _mapManager; private Dictionary _npcDic; private Map _curMap; public static LevelEditorWindow currWindow; private LevelEditor _levelEditor; private LevelData.NPCInfo _currEditingNPCInfo; public bool IsEditingPatrolInfo => _currEditingNPCInfo != null; private bool ResponseClickOnCell => isEditingNPC || IsEditingPatrolInfo; // [LabelText("关卡名称"), Indent] // public string levelName; [LabelText("当前关卡数据"), Indent] [OnValueChanged("OnCurrentLevelDataChanged")] public LevelData currentLevelData = null; [LabelText("当前地区数据"), Indent] [OnValueChanged("OnCurrentLevelDataChanged")] public RegionData curRegionData = null; [LabelText("添加NPC")] [LabelWidth(100)] [SerializeField] public bool isEditingNPC; [PreviewField(150, ObjectFieldAlignment.Center)] [AssetList(Path = (Constants.LEVEL_NPC_PATH_WITHOUT_ASSET), CustomFilterMethod = "IsNPCPrefabValid")] [ShowIf("isEditingNPC")] [LabelText("选择NPC")] public GameObject npcObject; private bool IsNPCPrefabValid(GameObject prefab) { var monsterClasses = EditorTableManager.instance.tables.MonsterClass; var prefabPath = AssetDatabase.GetAssetPath(prefab); foreach (var monsterClassCfg in monsterClasses.DataList) { var combinePath = PathEx.GetEnemySrcModelPath(monsterClassCfg.NameId); if (combinePath == prefabPath) return true; } return false; } public List CurrentNpcInfo = null; [Indent] [Button("保存当前关卡")] private void SaveCurrentLevel() { CheckCorrectLevel(); //var configFilePath = string.Format(Constants.LEVEL_CONFIG_FORMAT_PATH, currentLevelData.id); 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列表 currentLevelData.npcInfos = CurrentNpcInfo.Select(editorNPCInfo => editorNPCInfo.npcInfo).ToList(); _levelEditor.SaveLevelData(); } 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); } public const string preparingRegionErrorMessage = "关卡出战区设置错误,<{0}>地图没有:<{1}>号地区。\n"; public const string fallbackRegionErrorMessage = "关卡撤退区设置错误,<{0}>地图没有:<{1}>号地区。\n"; public const string enemyFallbackRegionErrorMessage = "关卡敌方撤退区设置错误,<{0}>地图没有:<{1}>号地区。\n"; public const string fixedMessage = "已将错误区域设置为<0>号地区。"; private void CheckCorrectLevel() { if (_curMap == null) { return; } var regionList = _curMap.MapData.regions; var errorMessage = string.Empty; if (!_curMap.MapData.IsRegionExist(currentLevelData.preparingRegionId)) { errorMessage += string.Format(preparingRegionErrorMessage, null, currentLevelData.preparingRegionId); currentLevelData.preparingRegionId = 0; } if (!_curMap.MapData.IsRegionExist(currentLevelData.fallbackRegionId)) { errorMessage += string.Format(fallbackRegionErrorMessage, null, currentLevelData.fallbackRegionId); currentLevelData.fallbackRegionId = 0; } if (!_curMap.MapData.IsRegionExist(currentLevelData.enemyFallbackRegionId)) { errorMessage += string.Format(enemyFallbackRegionErrorMessage, null, currentLevelData.enemyFallbackRegionId); currentLevelData.enemyFallbackRegionId = 0; } if (!string.IsNullOrEmpty(errorMessage)) { CustomPopUpWindow.PopUp(errorMessage + fixedMessage); } } private void Awake() { SceneView.duringSceneGui += OnSceneGUI; } protected override void OnEnable() { TerrainTypeConfig.Load(); _npcDic = new Dictionary(); base.OnEnable(); } protected override void OnDisable() { currWindow = null; //MapEditorUtils.UnLoadMap(EditorMap.Instance); //DestroyImmediate(GameObject.Find("Main Camera")); DestroyNpcPrefab(); _npcDic = null; base.OnDisable(); } protected override void OnDestroy() { base.OnDestroy(); if (_curMap != null) _curMap.TerrainGridSystem.OnMouseClickOnGrid -= OnCellClick; SaveCurrentLevel(); SceneView.duringSceneGui -= OnSceneGUI; } private bool banSaveLevelList = false; private void OnCurrentLevelDataChanged() { Debug.Log($"{GetType()}.OnCurrentLevelDataChanged"); } private const string descKey = "LevelDesc"; private const string nameKey = "LevelName"; private void LoadLevel(LevelData levelData) { DestroyNpcPrefab(); _npcDic.Clear(); currentLevelData = levelData; //加载地区信息 curRegionData = new RegionData(); curRegionData.preparingRegionId = currentLevelData.preparingRegionId; curRegionData.fallbackRegionId = currentLevelData.fallbackRegionId; curRegionData.enemyFallbackRegionId = currentLevelData.enemyFallbackRegionId; curRegionData.preparingRegionToward = currentLevelData.preparingRegionToward; curRegionData.pvpDefendRegionId = currentLevelData.pvpDefendRegionId; curRegionData.pvpDefendToward = currentLevelData.pvpDefendToward; _mapManager = FindObjectOfType(); _curMap = _mapManager.Map; _curMap.TerrainGridSystem.OnMouseClickOnGrid += OnCellClick; //加载NPC到场景中 CurrentNpcInfo = new(); foreach (var npcInfo in currentLevelData.npcInfos) { CurrentNpcInfo.Add(new(npcInfo)); string npcId = string.Format("P_E{0}", npcInfo.id.ToString().Substring(1)); var npc = AssetDatabase.LoadAssetAtPath(string.Format(Constants.LEVEL_NPC_PATH, npcId)); var position = _curMap.BlockPosition2WorldPosition(npcInfo.position); _npcDic.Add(npcInfo.position, Instantiate(npc, position, npc.transform.rotation)); } } 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 (IsEditingPatrolInfo) { if (opTrue) AddPatrolInfo(clickPosition); else RemovePatrolInfo(clickPosition); } else if (isEditingNPC) { if (opTrue) { AddNPC(clickPosition); } else { RemoveNPC(clickPosition); } } } private void AddNPC(Vector2Int clickPosition) { if (npcObject == null) { DebugUtil.LogError("请选择正确的NPC!!"); return; } if (_npcDic.ContainsKey(clickPosition)) { DebugUtil.LogError("该位置已有NPC存在,请重新选!!!"); return; } LevelData.NPCInfo npcInfo = new LevelData.NPCInfo(); string npcId = npcObject.name.Replace("P_E", "1"); npcInfo.id = int.Parse(npcId); npcInfo.position = clickPosition; //默认等级为1 npcInfo.level = 1; CurrentNpcInfo.Add(new(npcInfo)); var position = _curMap.BlockPosition2WorldPosition(npcInfo.position); _npcDic.Add(npcInfo.position, Instantiate(npcObject, position, npcObject.transform.rotation)); DebugUtil.Log("添加了[{0}]NPC到[ {1} ]位置上", npcObject.name, npcInfo.position); } private void RemoveNPC(Vector2Int clickPosition) { if (_npcDic.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); _npcDic.Remove(clickPosition); } } } 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); } public void StartEditPatrolInfo(LevelData.NPCInfo npcInfo) { _currEditingNPCInfo = npcInfo; } public void FinishEditPatrolInfo() { _currEditingNPCInfo = null; } private void DestroyNpcPrefab() { foreach (var npc in _npcDic) { DestroyImmediate(npc.Value); } } protected override void OnGUI() { base.OnGUI(); if (_levelEditor == null || _mapManager == null || _mapManager.Map != _curMap || _levelEditor.LevelData != currentLevelData) { Debug.LogError("数据已变更,请重新打开关卡编辑器"); Close(); } } private void OnSceneGUI(SceneView sceneView) { DrawPatrolPath(); } 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; } } }