using TGS; using System; using Gameplay; using Framework; using System.IO; using UnityEditor; using UnityEngine; using System.Linq; using cfg.FightCfg; using Gameplay.Utils; using Gameplay.Level; using Sirenix.Utilities; using Gameplay.Level.Data; using Sirenix.OdinInspector; using Sirenix.Utilities.Editor; using UnityEngine.Serialization; using System.Collections.Generic; using Sirenix.OdinInspector.Editor; using Constants = Framework.Constants; using ObjectFieldAlignment = Sirenix.OdinInspector.ObjectFieldAlignment; 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; [LabelText("扛旗终点区")] [ShowIf("needSetFlagRegion")] public int flagTargetRegionId; [LabelText("占领进度")] [ShowIf("needSetCaptureProgress")] public int captureProgress; [HideInInspector] public bool needSetFlagRegion; [HideInInspector] public bool needSetCaptureProgress; 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 { [ReadOnly] [LabelText("序号ID")] public int id; [LabelText("MonsterID")] [ValueDropdown("GetMonsterIDs")] [OnValueChanged("ChangeNpcID")] public int monsterId; private IEnumerable GetMonsterIDs() { return currWindow.GetMonsterIDs(); } private void ChangeNpcID() { currWindow.ChangeNpcID(npcInfo, monsterId); } [LabelText("朝向")] [OnValueChanged("NpcTowardOnChange")] public LevelData.CharacterToward npcToward; private void NpcTowardOnChange() { npcInfo.npcToward = npcToward; currWindow.ChangeNpcToward(npcInfo.position, npcToward); } [HideLabel] public LevelData.NPCInfo npcInfo; public NPCEditorInfo(LevelData.NPCInfo npcInfo, int index) { this.npcInfo = npcInfo; monsterId = npcInfo.id; id = index; } [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 ProtectEditorInfo { [HorizontalGroup("Protect")] [HideLabel] public LevelData.ProtectUnit protectUnitInfo; [HorizontalGroup("Protect")] [TableColumnWidth(50, Resizable = false)] [PreviewField(150, ObjectFieldAlignment.Center)] [LabelText("护送对象预览图")] [AssetList(Path = Constants.LEVEL_PROTECT_UNIT_PATH)] [OnValueChanged("OnProtectUnitChanged")] public GameObject protectUnitPrefab; public ProtectEditorInfo(LevelData.ProtectUnit protectUnitInfo) { this.protectUnitInfo = protectUnitInfo; InitData(); } private void InitData() { prefabPathDic = new Dictionary(); var protectCfg = EditorTableManager.instance.tables.ProtectUnitConfig; dataProtectUnits = protectCfg.DataList; foreach (var data in dataProtectUnits) { prefabPathDic.Add(data.PrefabPath, data.ID); } if (protectCfg.DataMap.TryGetValue(protectUnitInfo.id, out var dataConfig)) { protectUnitPrefab = AssetDatabase.LoadAssetAtPath(dataConfig.PrefabPath); if (protectUnitPrefab == null) DebugUtil.LogError("检查 ProtectUnitConfig 表,该ID: {0} 没有对应模型配置", protectUnitInfo.id); } } private void OnProtectUnitChanged() { if (prefabPathDic == null) { InitData(); } var path = AssetDatabase.GetAssetPath(protectUnitPrefab); if (!prefabPathDic.TryGetValue(path, out var id)) { DebugUtil.LogError("检查 ProtectUnitConfig 表,该模型没有对应配置: {0}", path); } else { protectUnitInfo.id = id; } } private Dictionary prefabPathDic; private List dataProtectUnits; } private static LevelEditorWindow currWindow; private MapManager _mapManager; private Dictionary _npcObjectDic; private Map _curMap; private LevelEditor _levelEditor; private LevelData.NPCInfo _currEditingNPCInfo; private bool isEditingPatrolInfo; private bool isEditingNpcPos; private bool isShowNpcID => _showNpcInfos != null && _showNpcInfos.Count > 0; private List _showNpcInfos; private bool ResponseClickOnCell => isEditingNPC || isEditingPatrolInfo; [LabelText("当前关卡数据"), Indent] [OnValueChanged("OnCurrentLevelDataChanged")] public LevelData currentLevelData = null; [LabelText("当前地区数据"), Indent] [OnValueChanged("OnCurrentLevelDataChanged")] public RegionData curRegionData = null; [LabelText("编辑NPC"), Indent] [HorizontalGroup("NPC")] [LabelWidth(100)] [SerializeField] public bool isEditingNPC; [LabelText("显示索引地图"), Indent] [HorizontalGroup("NPC/1")] [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文件名 获得 /// [FormerlySerializedAs("LevelID")] [LabelText("手动指定关卡ID"), Indent] [LabelWidth(100)] [SerializeField] [ShowIf("NeedManualInputLevelID")] [OnValueChanged("GetLevelMonsterClass")] [InfoBox("无法正确获得关卡ID,请手动指定")] public string levelID; [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; //当前怪物类ID private int _curMonsterClassID; private IEnumerable GetMonsterIDs() { return monsterIDs; } private void MonsterIDChanged() { var obj = LoadNpcPrefab(curMonsterID); if (obj != null) npcObject = obj; } [LabelText("显示所有怪物序号ID"), Indent] [OnValueChanged("ShowAllNpcId")] [SerializeField] public bool isShowAllNpcID; private void ShowAllNpcId() { foreach (var npc in currentNpcInfo) { npc.isShowNpcID = isShowAllNpcID; if (isShowAllNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } [LabelText("当前怪物列表"), Indent] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true)] public List currentNpcInfo = null; [LabelText("编辑护送对象"), Indent] public bool isEditingProtectUnit; [LabelText("护送对象列表"), ShowIf("isEditingProtectUnit"), Indent] public List currentProtectUnits = null; [Button("保存当前关卡"), Indent] 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(); //占领进度 抗旗终点 currentLevelData.captureProgress = curRegionData.captureProgress; currentLevelData.flagTargetRegionId = curRegionData.flagTargetRegionId; //护送对象 currentLevelData.protectUnitInfos = currentProtectUnits.Select(editorProtectInfo => editorProtectInfo.protectUnitInfo).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); var level = Path.GetFileNameWithoutExtension(levelEditor.LevelDataJson.name); currWindow.levelID = level.Substring(level.Length - 6); currWindow.GetLevelMonsterClass(); currWindow.CheckFlagAndCapture(); DebugUtil.Log("关卡数据:{0}", currWindow.levelID); } /// /// 该关卡的怪物ID /// [HideInInspector] public List monsterIDs; /// /// 需要手动输入关卡ID /// [HideInInspector] public bool NeedManualInputLevelID; public void GetLevelMonsterClass() { var levelCfg = EditorTableManager.instance.tables.Level; 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(); var monsterCfg = EditorTableManager.instance.tables.Monster; foreach (var monster in monsterCfg.DataMap) { var strID = monster.Key.ToString(); var tempID = strID.Substring(0, 6); //关卡ID相同 if (tempID.Equals(levelID)) { monsterIDs.Add(monster.Key); } } if (monsterIDs.Count <= 0) { DebugUtil.LogError("请检查Monster表,没有{0}关卡的怪物数据", levelID); } } private void CheckFlagAndCapture() { curRegionData.needSetFlagRegion = _curMap.MapData.flagStartPoints.Count > 0; curRegionData.needSetCaptureProgress = _curMap.MapData.capturePoints.Count > 0; } private const string PreparingRegionErrorMessage = "关卡出战区设置错误,该地图没有:<{0}>号地区。\n"; private const string FallbackRegionErrorMessage = "关卡撤退区设置错误,该地图没有:<{0}>号地区。\n"; private const string EnemyFallbackRegionErrorMessage = "关卡敌方撤退区设置错误,该地图没有:<{0}>号地区。\n"; private const string FlagTargetRegionErrorMessage = "抗旗终点区设置错误,该地图没有:<{0}>号地区。\n"; private const string ProtectUnitInfoErrorMessage = "护送 {0} 的起点和终点相同或出错,请检查。\n"; private const string FixedMessage = "已将错误区域设置为默认<0>号地区。"; private void CheckCorrectLevel() { if (_curMap == null) { return; } if (curRegionData.needSetCaptureProgress && curRegionData.captureProgress == 0) DebugUtil.LogError("该地图有占领地格,无占领进度"); if (curRegionData.needSetFlagRegion && curRegionData.flagTargetRegionId <= 0) DebugUtil.LogError("该地图有抗旗地格,抗旗终点区似乎无修改,默认为0号区域"); 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 (!_curMap.MapData.IsRegionExist(curRegionData.flagTargetRegionId)) { errorMessage += string.Format(FlagTargetRegionErrorMessage, curRegionData.flagTargetRegionId); curRegionData.flagTargetRegionId = 0; } foreach (var protectEditor in currentProtectUnits) { if (protectEditor.protectUnitInfo.protectStartPoint == protectEditor.protectUnitInfo.protectTargetPoint || protectEditor.protectUnitInfo.protectTargetPoint < 0 || protectEditor.protectUnitInfo.protectStartPoint < 0) { errorMessage += string.Format(ProtectUnitInfoErrorMessage, protectEditor.protectUnitInfo.id); break; } } if (!string.IsNullOrEmpty(errorMessage)) { CustomPopUpWindow.PopUp(errorMessage + FixedMessage); } } private void Awake() { SceneView.duringSceneGui += OnSceneGUI; } protected override void OnEnable() { TerrainTypeConfig.Load(); _npcObjectDic = new Dictionary(); _showNpcInfos = new List(); base.OnEnable(); } protected override void OnDisable() { currWindow = null; //MapEditorUtils.UnLoadMap(EditorMap.Instance); //DestroyImmediate(GameObject.Find("Main Camera")); DestroyNpcPrefab(); _npcObjectDic = 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(); _npcObjectDic.Clear(); _showNpcInfos.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; //加载抗旗和占领 curRegionData.flagTargetRegionId = currentLevelData.flagTargetRegionId; curRegionData.captureProgress = currentLevelData.captureProgress; _mapManager = FindObjectOfType(); _curMap = _mapManager.Map; _curMap.TerrainGridSystem.OnMouseClickOnGrid += OnCellClick; var monsterConfigDic = EditorTableManager.instance.tables.Monster.DataMap; //加载NPC到场景中 currentNpcInfo = new(); var index = 1; foreach (var npcInfo in currentLevelData.npcInfos) { var npcEditor = new NPCEditorInfo(npcInfo, index); currentNpcInfo.Add(npcEditor); if (!monsterConfigDic.TryGetValue(npcInfo.id, out var monsterClassData)) { DebugUtil.LogError("请检查配置,没有 {0} 的怪物类ID", npcInfo.id); continue; } 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++; } //加载护送对象 currentProtectUnits = new List(); foreach (var protectUnit in currentLevelData.protectUnitInfos) { currentProtectUnits.Add(new ProtectEditorInfo(protectUnit)); } } private void OnCellClick(TerrainGridSystem tgs, int cellindex, int buttonindex) { if (isEditingProtectUnit) DebugUtil.LogWarning("当前点击索引: {0}", cellindex); 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 (isEditingNpcPos) { if (opTrue) ChangeNpcPos(clickPosition); } else if (isEditingNPC) { if (opTrue) { AddNPC(clickPosition); } else { RemoveNPC(clickPosition); } } } 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); } } 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 = EditorTableManager.instance.tables.Monster.DataMap; if (monsterConfigDic.TryGetValue(curMonsterID, out var monster)) { npcInfo.level = monster.MonsterLevel; } else { DebugUtil.LogError("获取怪物[{0}]配置数据错误", curMonsterID); } currentNpcInfo.Add(new(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 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); } 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 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 GameObject LoadNpcPrefab(int monsterID) { GameObject obj = null; var monsterCfg = EditorTableManager.instance.tables.Monster; if (monsterCfg.DataMap.TryGetValue(monsterID, out var monster)) { _curMonsterClassID = monster.MonsterClassID; } else { return null; } switch (monster.NpcType) { case ENpcType.ENEMY_VEHICLE: { var vehicleConfig = EditorTableManager.instance.tables.EnemyVehicleConfig; if (vehicleConfig.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 ENpcType.ENEMY_CHARACTER: { var monsterClassCfg = EditorTableManager.instance.tables.MonsterClass; if (monsterClassCfg.DataMap.TryGetValue(_curMonsterClassID, out var monsterClass)) { var prefabPath = PathEx.GetCharacterFightModelPath(monsterClass.NameId); obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (obj == null) { DebugUtil.LogError("请检查怪物类: {0}的预制体路径是否存在,对应的完整路径: {1}", _curMonsterClassID, prefabPath); } } else { DebugUtil.LogError("请检查怪物类: {0}的配置是否存在", _curMonsterClassID); } return obj; } default: return null; } } private void DestroyNpcPrefab() { foreach (var npc in _npcObjectDic) { 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); } } 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(); 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 void DrawNpcID() { if (!isShowNpcID) return; if (labelStyle == null) { labelStyle = new GUIStyle { fontSize = 38, fontStyle = FontStyle.Bold, normal = { textColor = Color.red } }; } foreach (var npc in _showNpcInfos) { if (_npcObjectDic.TryGetValue(npc.npcInfo.position, out var npcObj)) { Handles.Label(npcObj.transform.position, npc.id.ToString(), labelStyle); } } } }