using TGS; using System; using Gameplay; using Framework; using System.IO; using UnityEditor; using UnityEngine; using System.Linq; using Gameplay.Utils; using Gameplay.Level; using Sirenix.Utilities; using Sirenix.OdinInspector; using Sirenix.Utilities.Editor; using System.Collections.Generic; using cfg; using cfg.AICfg; using cfg.FightCfg; using cfg.GuideCfg; using cfg.LevelCfg; using cfg.MonsterCfg; using cfg.VehicleCfg; using Sirenix.OdinInspector.Editor; using Level = cfg.LevelCfg.Level; using ObjectFieldAlignment = Sirenix.OdinInspector.ObjectFieldAlignment; using static Gameplay.Level.LevelData; using Gameplay.Character; public class LevelEditorWindow : OdinEditorWindow { [MenuItem("Tools/*地图场景/关卡编辑器", false, (int)NLDMenuID.LevelEditor)] private static void OpenLevelEditorWindow() { var window = GetWindow(); window.position = GUIHelper.GetEditorWindowRect().AlignCenter(800, 600); window.titleContent = new GUIContent("关卡编辑器"); } public static void OpenWindow(LevelEditor levelEditor) { MapEditorSettings.Init(); currWindow = GetWindow(); currWindow.position = GUIHelper.GetEditorWindowRect().AlignCenter(800, 600); currWindow.currentLevelData = null; currWindow._levelEditor = levelEditor; currWindow.LoadLevel(levelEditor.LevelData); var level = Path.GetFileNameWithoutExtension(levelEditor.LevelDataJson.name); currWindow.levelID = level.Substring(level.Length - 6); //currWindow.GetLevelMonsterClass(); DebugUtil.Log("关卡数据:{0}", currWindow.levelID); } private static LevelEditorWindow currWindow; private MapManager _mapManager; private Map _curMap; private LevelEditor _levelEditor; #region 配置表 private Level LevelCfg => EditorTableManager.instance.tables.Level; private Monster MonsterCfg => EditorTableManager.instance.tables.Monster; private MonsterClass MonsterClassCfg => EditorTableManager.instance.tables.MonsterClass; private VehicleConfig VehicleCfg => EditorTableManager.instance.tables.VehicleConfig; private FightNPCConfig FightNpcCfg => EditorTableManager.instance.tables.FightNPCConfig; private BuildConfig FightBuildCfg => EditorTableManager.instance.tables.BuildConfig; #endregion #region 地块颜色 private static readonly Color RegionColor = MapUtils.GetRGBAColor("FF960072"); #endregion #region 关卡数据 [BoxGroup("当前关卡数据")] [LabelText("当前关卡数据"), Indent] public LevelData currentLevelData = null; #endregion #region 关卡进度 [BoxGroup("关卡引导")] [LabelText("关卡进度"), Indent] public List progressGuideDatas = new(); #endregion #region 引导立标 [Serializable] public class SetFlagGuideEditorInfo { public SetFlagGuideEditorInfo(LevelData.SetFlagGuideData flagGuideData) { this.flagGuideData = flagGuideData; guideTagID = flagGuideData.guideTagID; if (GuideTagConfig.DataMap.TryGetValue(guideTagID, out var guideData)) { flagGuideData.guideTagPath = guideData.ImagePath; guideTagName = guideData.Name; var sprite = AssetDatabase.LoadAssetAtPath(guideData.ImagePath); if (sprite != null) guideTagSprite = sprite; } } private GuideTagConfig GuideTagConfig => EditorTableManager.instance.tables.GuideTagConfig; [HorizontalGroup("1")] [LabelText("立标配置ID")] [ValueDropdown("GetGuideTagIDs")] [OnValueChanged("ChangeTagID")] public int guideTagID; [HorizontalGroup("1")] [LabelText("立标名字")] [ReadOnly] public string guideTagName; [HorizontalGroup("2")] [LabelText("预览图")] [PreviewField(150, ObjectFieldAlignment.Center), Indent] public Sprite guideTagSprite; [HorizontalGroup("3")] [LabelText("具体数据")] public LevelData.SetFlagGuideData flagGuideData; private IEnumerable GetGuideTagIDs() { return GuideTagConfig.DataMap.Keys; } private void ChangeTagID() { if (GuideTagConfig.DataMap.TryGetValue(guideTagID, out var guideData)) { flagGuideData.guideTagPath = guideData.ImagePath; flagGuideData.guideTagID = guideTagID; guideTagName = guideData.Name; var sprite = AssetDatabase.LoadAssetAtPath(guideData.ImagePath); if (sprite != null) guideTagSprite = sprite; } } } [BoxGroup("关卡引导")] [LabelText("立标数据"), Indent] public List setFlagEditorInfos = new(); #endregion #region 关卡进度 [BoxGroup("关卡引导")] [LabelText("信息板数据"), Indent] public List infoPanelGuideDatas = new(); #endregion #region 关卡AI广播 [BoxGroup("关卡AI广播")] [LabelText("AI广播数据"), Indent] public List listAIBroadcastData = new(); #endregion #region 占领相关 [Serializable] public class CaptureEditorInfo { [LabelText("占领点位")] public Vector2Int capturePoint; [LabelText("占领时间")] public int captureProgress; } [BoxGroup("占领相关")] [VerticalGroup("占领相关/数据")] [LabelText("当前占领数据"), Indent] public List captureEditorInfo = new(); [BoxGroup("占领相关")] [VerticalGroup("占领相关/编辑")] [LabelText("编辑占领数据"), Indent] public bool isEditingCapture = false; #endregion #region 扛旗相关 [Serializable] public class FlagEditorInfo { [LabelText("扛旗起点")] public List flagStartPoints = new(); [LabelText("扛旗终点")] public List flagEndPoints = new(); [LabelText("添加起点")] [OnValueChanged("EditingStartPoints")] public bool isEditingStart; [LabelText("添加终点")] [OnValueChanged("EditingEndPoints")] public bool isEditingEnd; private void EditingStartPoints() { currWindow.isEditingFlagStart = isEditingStart; if (isEditingStart) { isEditingEnd = false; currWindow.isEditingFlagEnd = false; } } private void EditingEndPoints() { currWindow.isEditingFlagEnd = isEditingEnd; if (isEditingEnd) { isEditingStart = false; currWindow.isEditingFlagStart = false; } } } private bool isEditingFlagStart; private bool isEditingFlagEnd; [BoxGroup("扛棋相关")] [LabelText("当前扛旗数据"), Indent] public FlagEditorInfo flagEditorInfo = new(); #endregion #region 撤离相关 [Serializable] public class FallbackEditorInfo { [LabelText("单次撤离点")] public List fallbackSinglePoints = new(); [LabelText("复用撤离点")] public List fallbackReusablePoints = new(); [LabelText("添加单次撤离点")] [OnValueChanged("EditingFallbackPoints")] public bool isEditingSinglePoints; [LabelText("添加复用撤离点")] [OnValueChanged("EditingMultiplexPoint")] public bool isEditingReusablePoints; private void EditingFallbackPoints() { currWindow.isEditingFallbackSingle = isEditingSinglePoints; if (isEditingSinglePoints) { isEditingReusablePoints = false; currWindow.isEditingFallbackReusable = false; } } private void EditingMultiplexPoint() { currWindow.isEditingFallbackReusable = isEditingReusablePoints; if (isEditingReusablePoints) { isEditingSinglePoints = false; currWindow.isEditingFallbackSingle = false; } } } private bool isEditingFallbackSingle; private bool isEditingFallbackReusable; [BoxGroup("撤离相关")] [LabelText("当前撤离数据"), Indent] public FallbackEditorInfo fallbackEditorInfo = new(); #endregion #region 地区相关 [Serializable] public class RegionData { [LabelText("出战区")] [OnValueChanged("OnRegionChanged")] public int preparingRegionId; [LabelText("出战区朝向")] public LevelData.CharacterToward preparingRegionToward; [LabelText("撤退区")] public int fallbackRegionId; [LabelText("敌方撤退区")] public int enemyFallbackRegionId; [LabelText("PVP防守区")] public int pvpDefendRegionId; [LabelText("PVP防守方朝向")] public LevelData.CharacterToward pvpDefendToward; private void OnRegionChanged() { var regionsList = currWindow._curMap.MapData.regions; for (int i = 0; i < regionsList.Count; i++) { currWindow._curMap.SetRegionColor(i, false, RegionColor); } currWindow._curMap.SetRegionColor(preparingRegionId, true, RegionColor); } } [BoxGroup("地区相关")] [LabelText("当前地区数据"), Indent] public RegionData curRegionData = null; #endregion #region NPC刷新点位相关 [HideInInspector] [BoxGroup("刷新点位")] [LabelText("编辑刷新点"), Indent, OnValueChanged("OnIsEditingRefreshIDChanged")] public bool isEditingRefreshID; [HideInInspector] [BoxGroup("刷新点位")] [LabelText("刷新点位"), Indent] [ShowIf("isEditingRefreshID")] public List refreshPoints = new List(); private void OnIsEditingRefreshIDChanged() { if (!isEditingRefreshID) { foreach (var point in refreshPoints) { _curMap.TerrainGridSystem.CellHideRegionSurface(point); } } else { foreach (var point in refreshPoints) { _curMap.TerrainGridSystem.CellToggleRegionSurface(point, true, RegionColor); } } } #endregion #region 指定路线 [Serializable] public class PrescribedRoadEditorInfo { [LabelText("路线ID")] public int roadID; [LabelText("路线点位")] public List roadPoints = new(); private bool isEditingRoadInfo; public PrescribedRoadEditorInfo() { roadID = currWindow.roadEditorInfos.Count + 1; } [Button("开始编辑路线"), Indent] [HideIf("isEditingRoadInfo")] private void StartEditRoad() { isEditingRoadInfo = true; currWindow.StartEditRoadInfo(this); } [Button("完成路线编辑"), Indent] [ShowIf("isEditingRoadInfo")] private void EndEditRoad() { isEditingRoadInfo = false; currWindow.FinishEditRoadInfo(); } } [BoxGroup("制定路线")] [LabelText("编辑指定路径"), Indent] public bool isEditingRoad; [BoxGroup("制定路线")] [LabelText("编辑指定路线"), Indent] [ShowIf("isEditingRoad")] public List roadEditorInfos = new(); private bool isEditingRoadInfo; private PrescribedRoadEditorInfo _currEditingRoadInfo; private void StartEditRoadInfo(PrescribedRoadEditorInfo roadInfo) { isEditingRoadInfo = true; _currEditingRoadInfo = roadInfo; } private void FinishEditRoadInfo() { isEditingRoadInfo = false; _currEditingRoadInfo = null; } #endregion #region 指定区域 [Serializable] public class PrescribedAreaEditorInfo { [LabelText("区域ID")] public int areaID; [LabelText("区域点位")] public List areaPoints = new(); private bool isEditingAreaInfo; public PrescribedAreaEditorInfo() { areaID = currWindow.areaEditorInfos.Count + 1; } [Button("开始编辑区域"), Indent] [HideIf("isEditingAreaInfo")] private void StartEditArea() { isEditingAreaInfo = true; currWindow.StartEditAreaInfo(this); } [Button("完成区域编辑"), Indent] [ShowIf("isEditingAreaInfo")] private void EndEditArea() { isEditingAreaInfo = false; currWindow.FinishEditAreaInfo(); } } [BoxGroup("制定区域")] [LabelText("编辑指定区域"), Indent] public bool isEditingArea; [BoxGroup("制定区域")] [LabelText("编辑指定区域"), Indent] [ShowIf("isEditingArea")] public List areaEditorInfos = new(); private bool isEditingAreaInfo; private PrescribedAreaEditorInfo _currEditingAreaInfo; private void StartEditAreaInfo(PrescribedAreaEditorInfo roadInfo) { isEditingAreaInfo = true; _currEditingAreaInfo = roadInfo; } private void FinishEditAreaInfo() { isEditingAreaInfo = false; _currEditingAreaInfo = null; } #endregion #region 显示地图索引 [BoxGroup("显示地格索引")] [LabelText("显示类型"), Indent] [OnValueChanged("ShowCellIndex")] [LabelWidth(100)] [SerializeField] private CellDebugInfo cellDebugInfo = CellDebugInfo.Nothing; private void ShowCellIndex() { if (_curMap == null || _curMap.TerrainGridSystem == null) return; _curMap.TerrainGridSystem.displayCellDebugInfo = cellDebugInfo; var obj = FindObjectOfType().gameObject; Selection.activeGameObject = obj; } #endregion #region NPC编辑器类 [Serializable] public class NpcEditorInfo { private AITypeMonsterConfig AITypeCfg => EditorTableManager.instance.tables.AITypeMonsterConfig; [HideInInspector] public int npcTroopId; [ReadOnly] [LabelText("序号ID")] public int id; [FoldoutGroup("详细信息")] [LabelText("MonsterID")] [ValueDropdown("GetMonsterIDs")] [OnValueChanged("ChangeNpcID")] public int monsterId; [FoldoutGroup("详细信息")] [LabelText("等级")] [ReadOnly] public int level; private IEnumerable GetMonsterIDs() { return npcTroopId switch { ETroopsId.Neutral => currWindow.GetMidMonsterIDs(), // 中立 ETroopsId.Self => currWindow.GetSelfNpcIDs(), // 友方 ETroopsId.Enemy => currWindow.GetEnemyMonsterIDs(), // 敌人 _ => null }; } private void ChangeNpcID() { switch (npcTroopId) { case ETroopsId.Neutral: // 中立 currWindow.ChangeMidNpcID(this, monsterId); break; case ETroopsId.Self: // 友方 currWindow.ChangeSelfNpcID(npcInfo, monsterId); break; case ETroopsId.Enemy: // 敌人 currWindow.ChangeEnemyNpcID(this, monsterId); break; default: DebugUtil.LogError($"未处理类型:{npcTroopId}"); break; } } [FoldoutGroup("详细信息")] [LabelText("朝向")] [OnValueChanged("NpcTowardOnChange")] public CharacterToward npcToward; private void NpcTowardOnChange() { npcInfo.npcToward = npcToward; switch (npcTroopId) { case ETroopsId.Neutral: // 中立 currWindow.ChangeMidNpcToward(this, npcToward); break; case ETroopsId.Self: // 友方 currWindow.ChangeSelfNpcToward(npcInfo.position, npcToward); break; case ETroopsId.Enemy: // 敌人 currWindow.ChangeEnemyNpcToward(this, npcToward); break; default: DebugUtil.LogError($"未处理类型:{npcTroopId}"); break; } } [FoldoutGroup("详细信息")] [HideLabel] public NPCInfo npcInfo; [FoldoutGroup("详细信息")] [LabelText("AI预设")] [ReadOnly] public string aiType; public NpcEditorInfo(NPCInfo npcInfo, int index, int level, int aiType, int npcTroopId) { this.npcInfo = npcInfo; this.level = level; this.aiType = GetAITypeData(aiType); monsterId = npcInfo.id; id = index; this.npcTroopId = npcTroopId; npcToward = this.npcInfo.npcToward; } [FoldoutGroup("详细信息")] [LabelText("位置调整")] [OnValueChanged("ChangeNpcPos")] public bool isChangePos; private void ChangeNpcPos() { if (isChangePos) { if (currWindow._currEditingNpcInfo != null) DebugUtil.LogWarning("检查其他怪物是否处于位置调整或编辑巡逻信息状态"); } currWindow._currNpcEditorInfo = isChangePos ? this : null; currWindow.isEditingNpcPos = isChangePos; currWindow.npcTroopId = npcTroopId; currWindow._currEditingNpcInfo = isChangePos ? npcInfo : null; } [FoldoutGroup("详细信息")] [LabelText("显示该怪物序号")] [OnValueChanged("ShowNpcID")] public bool isShowNpcID; private void ShowNpcID() { if (isShowNpcID && !currWindow._showNpcInfos.Contains(this)) currWindow._showNpcInfos.Add(this); else if (!isShowNpcID) { currWindow._showNpcInfos.Remove(this); } } private string GetAITypeData(int aiTypeID) { if (AITypeCfg.DataMap.TryGetValue(aiTypeID, out var dataAIType)) { return dataAIType.Name; } return $"无效AI类型: {aiTypeID},请检查配置表"; } } private NPCInfo _currEditingNpcInfo; private NpcEditorInfo _currNpcEditorInfo; private bool isEditingNpcPos; private int npcTroopId; #endregion #region 敌方Npc private GameObject enemyObjRoot; /// /// 波次/怪物ID对应的NPC预制体 /// private Dictionary> _enemyNpcObjectWaveDic; [BoxGroup("敌方Npc")] [LabelText("编辑NPC"), Indent] [LabelWidth(100)] [SerializeField] [OnValueChanged("OnIsEditingEnemyNpcChanged")] public bool isEditingEnemyNpc; [BoxGroup("敌方Npc")] [LabelText("显示所有怪物序号ID"), Indent] [OnValueChanged("ShowAllEnemyNpcId")] [SerializeField] public bool isShowAllEnemyNpcID; [BoxGroup("敌方Npc")] [LabelText("筛选怪物ID"), Indent, LabelWidth(100)] [SerializeField] [ShowIf("isEditingEnemyNpc")] public string selectEnemyMonsterId; [BoxGroup("敌方Npc")] [LabelText("怪物ID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("EnemyMonsterIDChanged")] [ValueDropdown("GetEnemyMonsterIDs")] [ShowIf("isEditingEnemyNpc")] public int curEnemyMonsterID; [BoxGroup("敌方Npc")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("NPC预制体预览图"), ShowIf("isEditingEnemyNpc")] public GameObject enemyNpcObject; [BoxGroup("敌方Npc")] [LabelText("当前怪物列表"), Indent, ShowIf("isEditingEnemyNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true, ShowFoldout = true)] public List currentEnemyNpcInfo = null; /// /// 该关卡的敌方NPCID /// [HideInInspector] public List enemyMonsterIDs; private IEnumerable GetEnemyMonsterIDs() { enemyMonsterIDs ??= new List(); enemyMonsterIDs.Clear(); if (null == selectEnemyMonsterId) return enemyMonsterIDs; foreach (var monster in MonsterCfg.DataMap) { string strID = monster.Key.ToString(); if (!strID.StartsWith(selectEnemyMonsterId)) continue; switch (monster.Value.NpcType) { case EMonsterType.EnemyCharacter: case EMonsterType.Vehicle: case EMonsterType.FightBuild: { enemyMonsterIDs.Add(monster.Key); } break; case EMonsterType.SelfCharacter: break; default: DebugUtil.LogError($"未处理类型:{monster.Value.NpcType}"); break; } } return enemyMonsterIDs; } private void EnemyMonsterIDChanged() { var obj = LoadEnemyNpcPrefab(curEnemyMonsterID); if (obj != null) enemyNpcObject = obj; } private void OnIsEditingEnemyNpcChanged() { if (isEditingEnemyNpc && isEditingSelfNpc && isEditingMidNpc) { isEditingSelfNpc = false; isEditingMidNpc = false; } } private void ShowAllEnemyNpcId() { foreach (var npc in currentEnemyNpcInfo) { npc.isShowNpcID = isShowAllEnemyNpcID; if (isShowAllEnemyNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 己方Npc private const int SelfID = 200; private GameObject selfObjRoot; private Dictionary _selfNpcObjectDic; [BoxGroup("己方Npc")] [LabelText("编辑己方NPC"), Indent] [LabelWidth(100)] [OnValueChanged("OnIsEditingSelfNpcChanged")] [SerializeField] public bool isEditingSelfNpc; [BoxGroup("己方Npc")] [LabelText("显示所有己方序号ID"), Indent] [OnValueChanged("ShowAllSelfNpcId")] [SerializeField] public bool isShowAllSelfNpcID; [BoxGroup("己方Npc")] [LabelText("筛选怪物ID"), Indent, LabelWidth(100)] [SerializeField] [ShowIf("isEditingSelfNpc")] public string selectSelfMonsterId; [BoxGroup("己方Npc")] [LabelText("己方NpcID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("SelfNpcIDChanged")] [ValueDropdown("GetSelfNpcIDs")] [ShowIf("isEditingSelfNpc")] public int curSelfNpcID; [BoxGroup("己方Npc")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("己方NPC预制体预览图"), ShowIf("isEditingSelfNpc")] public GameObject selfNpcObject; [BoxGroup("己方Npc")] [LabelText("当前己方NPC列表"), Indent, ShowIf("isEditingSelfNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true)] public List currentSelfNpcInfo = null; /// /// 该关卡的己方NPCID /// [HideInInspector] public List selfMonsterIDs; private IEnumerable GetSelfNpcIDs() { selfMonsterIDs ??= new List(); selfMonsterIDs.Clear(); if (null == selectSelfMonsterId) return selfMonsterIDs; foreach (var monster in MonsterCfg.DataMap) { string strID = monster.Key.ToString(); if (!strID.StartsWith(selectSelfMonsterId)) continue; switch (monster.Value.NpcType) { case EMonsterType.SelfCharacter: case EMonsterType.Vehicle: case EMonsterType.FightBuild: { selfMonsterIDs.Add(monster.Key); } break; case EMonsterType.EnemyCharacter: break; default: DebugUtil.LogError($"未处理类型:{monster.Value.NpcType}"); break; } } return selfMonsterIDs; } private void SelfNpcIDChanged() { var obj = LoadSelfNpcPrefab(curSelfNpcID); if (obj != null) selfNpcObject = obj; } private void OnIsEditingSelfNpcChanged() { if (isEditingEnemyNpc && isEditingSelfNpc && isEditingMidNpc) { isEditingEnemyNpc = false; isEditingMidNpc = false; } } private void ShowAllSelfNpcId() { foreach (var npc in currentSelfNpcInfo) { npc.isShowNpcID = isShowAllSelfNpcID; if (isShowAllSelfNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 中立Npc private GameObject midObjRoot; /// /// 中立 波次/怪物ID对应的NPC预制体 /// private Dictionary> _midNpcObjectWaveDic; [BoxGroup("中立Npc")] [LabelText("编辑NPC"), Indent] [LabelWidth(100)] [SerializeField] [OnValueChanged("OnIsEditingMidNpcChanged")] public bool isEditingMidNpc; [BoxGroup("中立Npc")] [LabelText("显示所有怪物序号ID"), Indent] [OnValueChanged("ShowAllMidNpcId")] [SerializeField] public bool isShowAllMidNpcID; [BoxGroup("中立Npc")] [LabelText("筛选怪物ID"), Indent, LabelWidth(100)] [SerializeField] [ShowIf("isEditingMidNpc")] public string selectMidMonsterId; [BoxGroup("中立Npc")] [LabelText("怪物ID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("MidMonsterIDChanged")] [ValueDropdown("GetMidMonsterIDs")] [ShowIf("isEditingMidNpc")] public int curMidMonsterID; [BoxGroup("中立Npc")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("NPC预制体预览图"), ShowIf("isEditingMidNpc")] public GameObject midNpcObject; [BoxGroup("中立Npc")] [LabelText("当前怪物列表"), Indent, ShowIf("isEditingMidNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true, ShowFoldout = true)] public List currentMidNpcInfo = null; /// /// 该关卡的中立NPCID /// [HideInInspector] public List midMonsterIDs; private IEnumerable GetMidMonsterIDs() { midMonsterIDs ??= new List(); midMonsterIDs.Clear(); if (null == selectMidMonsterId) return midMonsterIDs; foreach (var monster in MonsterCfg.DataMap) { string strID = monster.Key.ToString(); if (!strID.StartsWith(selectMidMonsterId)) continue; switch (monster.Value.NpcType) { case EMonsterType.Vehicle: case EMonsterType.FightBuild: { midMonsterIDs.Add(monster.Key); } break; case EMonsterType.EnemyCharacter: case EMonsterType.SelfCharacter: break; default: DebugUtil.LogError($"未处理类型:{monster.Value.NpcType}"); break; } } return midMonsterIDs; } private void MidMonsterIDChanged() { var obj = LoadMidNpcPrefab(curMidMonsterID); if (obj != null) midNpcObject = obj; } private void OnIsEditingMidNpcChanged() { if (isEditingEnemyNpc && isEditingSelfNpc && isEditingMidNpc) { isEditingEnemyNpc = false; isEditingSelfNpc = false; } } private void ShowAllMidNpcId() { foreach (var npc in currentMidNpcInfo) { npc.isShowNpcID = isShowAllMidNpcID; if (isShowAllMidNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 阻挡球编辑 /// /// 球体根节点 /// private GameObject blockSphereRoot; /// /// 已修改阻挡球字典 /// private readonly Dictionary blockSphereInfo = new(); /// /// 被隐藏的物体列表,用于后续恢复显示 /// private readonly List hideObjs = new List(); /// /// 所有禁止攻击的格子 /// private readonly List banAttackCells = new(); /// /// 标准六边形内接圆直径 /// private static readonly float Calibre = 3f; /// /// 编辑时颜色 /// private static readonly Color EditingSphereColor = Color.red; /// /// 已编辑过的数据颜色 /// private static readonly Color ChangedSphereColor = Color.green; [Serializable] public class BlockSpereEditorInfo { [HideInInspector] public MapData.BlockSphereInfo blockSphereInfo; #region 可视字段 [HorizontalGroup("1", 0.1f)] [LabelText("地格")] [LabelWidth(30)] [ReadOnly] public int cellIndex; [HorizontalGroup("1", 0.12f)] [LabelText("缩放")] [LabelWidth(30)] [EnableIf("canEditor")] [ShowIf("@isChanged||canEditor")] [OnValueChanged("OnScaleValueChanged")] public float scale = 1f; [HorizontalGroup("1", 0.25f)] [LabelText("偏移")] [LabelWidth(30)] [EnableIf("canEditor")] [ShowIf("@isChanged||canEditor")] [OnValueChanged("OnOffestValueChanged")] public Vector2 offest; [HorizontalGroup("1", 0.25f)] [LabelText("备注")] [LabelWidth(30)] [EnableIf("canEditor")] // [ShowIf("@isChanged||canEditor")] [OnValueChanged("OnNoteValueChanged")] public string note; #endregion #region 字段 /// /// 可编辑 /// [HideInInspector] public bool canEditor; /// /// 数据已经变化 /// [HideInInspector] public bool isChanged; /// /// 阻挡球物体 /// [HideInInspector] public GameObject sphere; /// /// 原始共享材质 /// [HideInInspector] public Material originalSharedMaterial; /// /// 编辑时的临时材质 /// [HideInInspector] public Material editingMaterial; /// /// 地格中心位置 /// [HideInInspector] public Vector3 originalPos; #endregion #region 构造函数 public BlockSpereEditorInfo(int cellIndex, GameObject sphere) { var map = currWindow._curMap; this.cellIndex = cellIndex; blockSphereInfo = new MapData.BlockSphereInfo() { cellIndex = cellIndex, }; originalPos = map.TGSCellIndex2WorldPosition(cellIndex); if (sphere != null) { this.sphere = sphere; SetSphereColor(Color.white); } isChanged = false; } public BlockSpereEditorInfo(MapData.BlockSphereInfo blockInfo, GameObject sphere) { var map = currWindow._curMap; blockSphereInfo = new MapData.BlockSphereInfo { cellIndex = blockInfo.cellIndex, scale = blockInfo.scale, note = blockInfo.note, offestPos = blockInfo.offestPos, }; cellIndex = blockInfo.cellIndex; originalPos = map.TGSCellIndex2WorldPosition(blockInfo.cellIndex); offest = new Vector2(blockInfo.offestPos.x - originalPos.x, blockInfo.offestPos.z - originalPos.z); scale = blockInfo.scale / Calibre; if (sphere != null) { this.sphere = sphere; } UpdateChangedData(); } #endregion #region 按钮 [Button("恢复默认")] [HorizontalGroup("1")] [ShowIf("isChanged")] public void Revert() { if (!isChanged) return; isChanged = false; if (sphere != null) { scale = 1f; offest = new Vector2(0f, 0f); blockSphereInfo.offestPos = originalPos; blockSphereInfo.scale = Calibre; sphere.transform.localScale = Vector3.one * Calibre; sphere.transform.position = originalPos; // 如果正在监听这个球体,更新监听的位置和缩放 if (currWindow.currentEditingSphere == this) { currWindow.lastPosition = originalPos; currWindow.lastScale = Vector3.one * Calibre; } if (!canEditor) { SetSphereColor(Color.white); } } } [Button("编辑")] [HorizontalGroup("1")] [HideIf("canEditor")] private void EditorBlockAttack() { canEditor = true; if (sphere != null) { var renderer = sphere.GetComponent(); // 保存原始材质(可能已经是绿色的编辑过材质) originalSharedMaterial = renderer.sharedMaterial; // 创建编辑用的红色材质 editingMaterial = new Material(originalSharedMaterial); editingMaterial.color = EditingSphereColor; renderer.sharedMaterial = editingMaterial; if (currWindow.currentEditingSphere != null) { currWindow.currentEditingSphere.ExitEditorBlockAttack(); } Selection.activeGameObject = sphere; currWindow.StartMonitoringSphere(this); } } [Button("保存")] [HorizontalGroup("1")] [ShowIf("canEditor")] public void ExitEditorBlockAttack() { canEditor = false; if (sphere != null) { var renderer = sphere.GetComponent(); // 清理临时材质并恢复原始材质 if (editingMaterial != null) { DestroyImmediate(editingMaterial); editingMaterial = null; } // 恢复原始材质 if (originalSharedMaterial != null) { renderer.sharedMaterial = originalSharedMaterial; } blockSphereInfo.scale = Calibre * scale; blockSphereInfo.offestPos = sphere.transform.position; DebugUtil.Log( $"保存 :{blockSphereInfo.cellIndex} 上的阻挡攻击球体真实直径为: {blockSphereInfo.scale}, 具体位置为: {blockSphereInfo.offestPos}, " + $"原始的位置是:{originalPos}, 原始直径是:{Calibre}"); UpdateChangedData(); } // 停止监听Transform变化 currWindow.StopMonitoringSphere(); } #endregion #region 值变化 private void OnScaleValueChanged() { var realScale = Calibre * scale; if (sphere != null) { sphere.transform.localScale = Vector3.one * realScale; } UpdateChangedData(); } private void OnOffestValueChanged() { var realOffestX = offest.x + originalPos.x; var realOffestZ = offest.y + originalPos.z; var newPos = new Vector3(realOffestX, 0f, realOffestZ); if (sphere != null) { sphere.transform.position = newPos; } UpdateChangedData(); } private void OnNoteValueChanged() { blockSphereInfo.note = note; } public void UpdateChangedData() { isChanged = offest != Vector2.zero || !Mathf.Approximately(1f, scale); // 如果状态发生变化且不在编辑状态,更新球体颜色 if (!canEditor && sphere != null) { SetSphereColor(isChanged ? ChangedSphereColor : Color.white); } } /// /// 设置球体颜色 /// private void SetSphereColor(Color color) { if (sphere == null) return; var renderer = sphere.GetComponent(); if (renderer != null) { if (!canEditor) { if (renderer.sharedMaterial != null) { var material = new Material(renderer.sharedMaterial); material.color = color; renderer.sharedMaterial = material; } } } } #endregion } [BoxGroup("攻击阻挡球")] [HorizontalGroup("攻击阻挡球/Block")] [LabelText("编辑攻击阻挡球"), Indent] [OnValueChanged("OnCanEditorBlockSphereChanged")] [LabelWidth(100)] [SerializeField] private bool canEditorBlockSphere; private void OnCanEditorBlockSphereChanged() { if (canEditorBlockSphere) { showBuildings = false; HideObjectsExcept(); blockSphereEditorList.Clear(); UpdateBlockSphereInfo(); foreach (var cellIndex in banAttackCells) { if (blockSphereInfo.TryGetValue(cellIndex, out var sphereInfo)) { var sphere = CreateSphere(cellIndex, sphereInfo.offestPos, Vector3.one * sphereInfo.scale); var blockEditorInfo = new BlockSpereEditorInfo(sphereInfo, sphere); blockSphereEditorList.Add(blockEditorInfo); } else { var pos = _curMap.TGSCellIndex2WorldPosition(cellIndex); var sphere = CreateSphere(cellIndex, pos, Vector3.one * Calibre); var blockEditorInfo = new BlockSpereEditorInfo(cellIndex, sphere); blockSphereEditorList.Add(blockEditorInfo); } } editingBlockSphereList.Clear(); foreach (var editorInfo in blockSphereEditorList) { if (editorInfo.isChanged) { editingBlockSphereList.Add(editorInfo); } } } else { // 清理所有临时材质 foreach (var editorInfo in blockSphereEditorList) { if (editorInfo.editingMaterial != null) { if (editorInfo.sphere != null) { var renderer = editorInfo.sphere.GetComponent(); if (renderer != null) { renderer.sharedMaterial = editorInfo.originalSharedMaterial; } } DestroyImmediate(editorInfo.editingMaterial); } } showBuildings = true; ShowHiddenObjects(); DestroyBlockSphere(); } } /// /// 更新阻挡球信息 /// private void UpdateBlockSphereInfo() { blockSphereInfo.Clear(); GetAllBanAttackCell(); foreach (var blockInfo in currentLevelData.BlockSphereInfos) { blockSphereInfo[blockInfo.cellIndex] = blockInfo; } } /// /// 获取所有禁止攻击地格 /// private void GetAllBanAttackCell() { banAttackCells.Clear(); GetAllBanAttackBuildings(); } /// /// 获取所有禁止攻击建筑地格 /// private void GetAllBanAttackBuildings() { AddBanAttackBuildings(currentEnemyNpcInfo); AddBanAttackBuildings(currentMidNpcInfo); AddBanAttackBuildings(currentSelfNpcInfo); } /// /// 获取禁止攻击建筑地格 /// private void AddBanAttackBuildings(List npcList) { foreach (var npcEditorInfo in npcList) { if (!MonsterCfg.DataMap.TryGetValue(npcEditorInfo.monsterId, out var dataMonster)) continue; if (dataMonster.NpcType != EMonsterType.FightBuild) continue; if (!FightBuildCfg.DataMap.TryGetValue(dataMonster.MonsterClassID, out var dataFight)) continue; if (!dataFight.IsReplaceBlockAttack || !dataFight.BlockAttack) continue; var cellIndex = _curMap.BlockPosition2TGSCellIndex(npcEditorInfo.npcInfo.position); if (!banAttackCells.Contains(cellIndex)) banAttackCells.Add(cellIndex); DebugUtil.Log($"能阻挡攻击的建筑{dataMonster.MonsterClassID}所在地格是{cellIndex}"); } } [LabelText("展示建筑")] [HorizontalGroup("攻击阻挡球/BlockBuildings"), Indent] [ShowIf("canEditorBlockSphere")] [OnValueChanged("OnShowBuildingsChanged")] public bool showBuildings; private void OnShowBuildingsChanged() { if (showBuildings) { ShowHiddenObjects(); } else { HideObjectsExcept(); } } [LabelText("添加地格")] [HorizontalGroup("攻击阻挡球/新数据"), Indent] [ShowIf("canEditorBlockSphere")] public int addCellIndex; [Button("添加编辑")] [HorizontalGroup("攻击阻挡球/新数据/Add"), Indent] [ShowIf("canEditorBlockSphere")] public void AddBlockSphere() { if (!banAttackCells.Contains(addCellIndex)) { EditorUtility.DisplayDialog("错误", $"地格 {addCellIndex} 不是攻击阻挡地格!", "确定"); return; } foreach (var blockSpereEditor in editingBlockSphereList) { if (blockSpereEditor.cellIndex == addCellIndex) { return; } } foreach (var blockSpereEditor in blockSphereEditorList) { if (blockSpereEditor.cellIndex == addCellIndex) { editingBlockSphereList.Add(blockSpereEditor); break; } } } [LabelText("阻挡球列表")] [HorizontalGroup("攻击阻挡球/EditList"), Indent] [ShowIf("canEditorBlockSphere")] [ListDrawerSettings(HideAddButton = true, CustomRemoveIndexFunction = "CustomRemoveBlockSphereIndex")] public List editingBlockSphereList = new(); [HideInInspector] public List blockSphereEditorList = new(); private void CustomRemoveBlockSphereIndex(int index) { if (index < editingBlockSphereList.Count) { editingBlockSphereList[index].Revert(); DebugUtil.Log($"删除并还原{editingBlockSphereList[index].cellIndex}的阻挡球编辑数据"); editingBlockSphereList.RemoveAt(index); } } /// /// 创建球体 /// private GameObject CreateSphere(int cellIndex, Vector3 pos, Vector3 scale) { var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere); sphere.transform.position = pos; sphere.transform.localScale = scale; if (blockSphereRoot == null) blockSphereRoot = new GameObject("blockSphereRoot"); sphere.transform.SetParent(blockSphereRoot.transform); sphere.name = "sphere" + cellIndex; return sphere; } private void DestroyBlockSphere() { DestroyImmediate(blockSphereRoot); } /// /// 保存数据 /// private void SaveBlockSphereInfo() { currentLevelData.BlockSphereInfos.Clear(); foreach (var blockSpereEditor in editingBlockSphereList) { currentLevelData.BlockSphereInfos.Add(blockSpereEditor.blockSphereInfo); } } /// /// 加载数据 /// private void LoadBlockSphereInfo() { blockSphereInfo.Clear(); foreach (var blockInfo in currentLevelData.BlockSphereInfos) { blockSphereInfo[blockInfo.cellIndex] = blockInfo; } } #region Sphere物体编辑器监听 /// /// 当前正在编辑的球体信息 /// [HideInInspector] public BlockSpereEditorInfo currentEditingSphere; /// /// 上次记录的位置 /// [HideInInspector] public Vector3 lastPosition; /// /// 上次记录的缩放 /// [HideInInspector] public Vector3 lastScale; /// /// 开始监听球体Transform变化 /// private void StartMonitoringSphere(BlockSpereEditorInfo editorInfo) { currentEditingSphere = editorInfo; if (editorInfo.sphere != null) { lastPosition = editorInfo.sphere.transform.position; lastScale = editorInfo.sphere.transform.localScale; // 注册编辑器更新回调 EditorApplication.update += OnEditorUpdate; } } /// /// 停止监听球体Transform变化 /// private void StopMonitoringSphere() { currentEditingSphere = null; EditorApplication.update -= OnEditorUpdate; } /// /// 编辑器更新回调 /// private void OnEditorUpdate() { if (currentEditingSphere?.sphere == null || !currentEditingSphere.canEditor) { StopMonitoringSphere(); return; } var sphere = currentEditingSphere.sphere; var transform = sphere.transform; // 检查位置变化 if (Vector3.Distance(transform.position, lastPosition) > 0.001f) { // 锁定Y轴 var newPos = transform.position; newPos.y = 0f; transform.position = newPos; // 更新偏移值 var offsetX = newPos.x - currentEditingSphere.originalPos.x; var offsetZ = newPos.z - currentEditingSphere.originalPos.z; currentEditingSphere.offest = new Vector2(offsetX, offsetZ); lastPosition = newPos; // 标记为已修改 currentEditingSphere.UpdateChangedData(); } // 检查缩放变化 if (Vector3.Distance(transform.localScale, lastScale) > 0.001f) { var newScale = transform.localScale.x; transform.localScale = Vector3.one * newScale; currentEditingSphere.scale = newScale / Calibre; lastScale = transform.localScale; currentEditingSphere.UpdateChangedData(); } } #endregion #region 物体隐藏/显示管理 /// /// 需要隐藏的物体节点名字 /// private readonly List hideObjsRoot = new() { "Art", "BackGround", "Buildings", "enemyObjRoot", "midObjRoot", "selfObjRoot" }; /// /// 不需要隐藏的物体节点名字 /// private readonly List noHideObjsRoot = new() { "Ground", "Ground2", "Lights", "Fog", "Global Volume", "Effects" }; /// /// 隐藏指定节点下的物体(排除白名单中的物体) /// public void HideObjectsExcept() { if (hideObjsRoot == null || hideObjsRoot.Count == 0) { return; } // 清空之前的隐藏物体列表 hideObjs.Clear(); foreach (string nodeName in hideObjsRoot) { GameObject node = GameObject.Find(nodeName); if (node == null) { continue; } HideChildrenRecursively(node.transform, noHideObjsRoot); } } /// /// 递归隐藏子物体 /// /// 父级Transform /// 排除的物体名列表 private void HideChildrenRecursively(Transform parent, List excludeNames) { for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); GameObject childObj = child.gameObject; // 检查是否在排除列表中(完全匹配) bool shouldExclude = false; if (excludeNames != null) { foreach (string excludeName in excludeNames) { if (childObj.name.Equals(excludeName, System.StringComparison.OrdinalIgnoreCase)) { shouldExclude = true; break; } } } if (shouldExclude) { continue; } else { // 不在排除列表中,隐藏此物体 if (childObj.activeSelf) { childObj.SetActive(false); hideObjs.Add(childObj); } // 继续递归处理子物体 HideChildrenRecursively(child, excludeNames); } } } /// /// 显示之前隐藏的所有物体 /// private void ShowHiddenObjects() { if (hideObjs == null || hideObjs.Count == 0) { return; } for (int i = hideObjs.Count - 1; i >= 0; i--) { GameObject obj = hideObjs[i]; // 检查物体是否仍然存在(可能已被删除) if (obj != null) { obj.SetActive(true); } hideObjs.RemoveAt(i); } } #endregion #endregion #region 保存 [Button("保存当前关卡"), Indent] private void SaveCurrentLevel() { CheckCorrectLevel(); // 地区 currentLevelData.preparingRegionId = curRegionData.preparingRegionId; currentLevelData.fallbackRegionId = curRegionData.fallbackRegionId; currentLevelData.enemyFallbackRegionId = curRegionData.enemyFallbackRegionId; currentLevelData.preparingRegionToward = curRegionData.preparingRegionToward; currentLevelData.pvpDefendRegionId = curRegionData.pvpDefendRegionId; currentLevelData.pvpDefendToward = curRegionData.pvpDefendToward; #region 敌方NPC foreach (var npcEditor in currentEnemyNpcInfo) { npcEditor.npcInfo.index = npcEditor.id; } currentLevelData.npcInfos = currentEnemyNpcInfo.Select(editorNpcInfo => editorNpcInfo.npcInfo).ToList(); #endregion #region 己方NPC foreach (var npcFightEditorInfo in currentSelfNpcInfo) { npcFightEditorInfo.npcInfo.index = npcFightEditorInfo.id; } currentLevelData.selfNpcInfos = currentSelfNpcInfo.Select(editorFightNpcInfo => editorFightNpcInfo.npcInfo).ToList(); #endregion #region 中立NPC foreach (var npcEditor in currentMidNpcInfo) { npcEditor.npcInfo.index = npcEditor.id; } currentLevelData.midNpcInfos = currentMidNpcInfo.Select(editorNpcInfo => editorNpcInfo.npcInfo).ToList(); #endregion // 占领数据 currentLevelData.CapturePoints.Clear(); foreach (var captureInfo in captureEditorInfo) { var capture = new LevelData.CaptureInfo { CapturePoint = _curMap.BlockPosition2TGSCellIndex(captureInfo.capturePoint), CaptureProgress = captureInfo.captureProgress, }; currentLevelData.CapturePoints.Add(capture); } // 扛旗数据 currentLevelData.flagStartPoints.Clear(); currentLevelData.flagEndPoints.Clear(); foreach (var flag in flagEditorInfo.flagStartPoints) { var cellIndex = _curMap.BlockPosition2TGSCellIndex(flag); currentLevelData.flagStartPoints.Add(cellIndex); } foreach (var flag in flagEditorInfo.flagEndPoints) { var cellIndex = _curMap.BlockPosition2TGSCellIndex(flag); currentLevelData.flagEndPoints.Add(cellIndex); } // 撤离数据 currentLevelData.fallbackSinglePoints.Clear(); currentLevelData.fallbackReusablePoints.Clear(); foreach (var fallback in fallbackEditorInfo.fallbackSinglePoints) { var cellIndex = _curMap.BlockPosition2TGSCellIndex(fallback); currentLevelData.fallbackSinglePoints.Add(cellIndex); } foreach (var fallback in fallbackEditorInfo.fallbackReusablePoints) { var cellIndex = _curMap.BlockPosition2TGSCellIndex(fallback); currentLevelData.fallbackReusablePoints.Add(cellIndex); } // 刷新点位 currentLevelData.refreshPoints.Clear(); currentLevelData.refreshPoints.AddRange(refreshPoints); // 指定路径 currentLevelData.patrolRoadData = roadEditorInfos.Select(editorInfo => new PatrolData { id = editorInfo.roadID, patrolInfos = editorInfo.roadPoints, }).ToList(); // 指定区域 currentLevelData.patrolRegionData = areaEditorInfos.Select(editorInfo => new PatrolData { id = editorInfo.areaID, patrolInfos = editorInfo.areaPoints, }).ToList(); // 关卡进度 currentLevelData.progressGuideDatas = progressGuideDatas; // 立标数据 currentLevelData.setFlagGuideDatas = setFlagEditorInfos.Select(x => x.flagGuideData).ToList(); // 信息板数据 currentLevelData.infoPanelGuideDatas = infoPanelGuideDatas; currentLevelData.listAIBroadcastData = listAIBroadcastData; // 处理条件(击杀对象列表) ProcessCondition(); // 阻挡球 SaveBlockSphereInfo(); _levelEditor.SaveLevelData(); _mapManager.SaveMapData(); } private void ProcessCondition() { foreach (var condition in currentLevelData.setFlagGuideDatas) { ProcessCondition(condition.triggerCondition); ProcessCondition(condition.finishCondition); } foreach (var condition in currentLevelData.progressGuideDatas) { ProcessCondition(condition.triggerCondition); ProcessCondition(condition.finishCondition); } foreach (var condition in currentLevelData.infoPanelGuideDatas) { ProcessCondition(condition.triggerCondition); ProcessCondition(condition.finishCondition); } foreach (var condition in currentLevelData.listAIBroadcastData) { ProcessCondition(condition.triggerCondition); } ProcessConditionForList(currentLevelData.starTarget); ProcessConditionForList(currentLevelData.scoreTarget); } private void ProcessConditionForList(IEnumerable conditionModifiers) { foreach (var conditionModifier in conditionModifiers) { ProcessCondition(conditionModifier); } } private void ProcessCondition(ConditionModifier conditionModifier) { // 击杀条件特殊处理 if (conditionModifier.conditionType == ConditionType.CombatKillEnemy) { conditionModifier.conditionArgs.Clear(); foreach (var enemy in conditionModifier.killEnemyIDList) { conditionModifier.conditionArgs.Add(new Param("", enemy, "")); } } else if (conditionModifier.conditionType == ConditionType.CombatInTargetGridByJob) { conditionModifier.conditionArgs.Clear(); conditionModifier.conditionArgs.Add(new Param("职业", conditionModifier.job, typeof(cfg.CharacterCfg.Ejob).FullName)); conditionModifier.conditionArgs.Add(new Param("是否路过", conditionModifier.isPass, "")); foreach (var unitID in conditionModifier.unitIDList) { conditionModifier.conditionArgs.Add(new Param("地格位置", unitID, "")); } } else if (conditionModifier.conditionType == ConditionType.CombatInTargetGridByCharacterId) { conditionModifier.conditionArgs.Clear(); conditionModifier.conditionArgs.Add(new Param("角色ID", conditionModifier.roleID, "")); conditionModifier.conditionArgs.Add(new Param("是否路过", conditionModifier.isPass, "")); foreach (var unitID in conditionModifier.unitIDList) { conditionModifier.conditionArgs.Add(new Param("地格位置", unitID, "")); } } // 任意角色在目标位置特殊处理 else if (conditionModifier.conditionType == ConditionType.CombatInTargetGrid) { conditionModifier.conditionArgs.Clear(); conditionModifier.conditionArgs.Add(new Param("是否路过", conditionModifier.isPass, "")); foreach (var unitID in conditionModifier.unitIDList) { conditionModifier.conditionArgs.Add(new Param("地格位置", unitID, "")); } } // 撤退数量特殊处理 else if (conditionModifier.conditionType == ConditionType.CombatFallBack && conditionModifier.conditionArgs.Count > 0) { currentLevelData.fallBackCount = (int)conditionModifier.conditionArgs[0].Arg; } } #endregion #region 关卡ID相关 /// /// 关卡ID 通过关卡数据 json文件名 获得 /// [LabelText("手动指定关卡ID"), Indent, LabelWidth(100), ShowIf("needManualInputLevelID")] [OnValueChanged("GetLevelMonsterClass")] [InfoBox("无法正确获得关卡ID,请手动指定")] [SerializeField] public string levelID; /// /// 需要手动输入关卡ID /// [HideInInspector] public bool needManualInputLevelID; /* /// /// 获取关卡怪物列表 /// public void GetLevelMonsterClass() { if (!int.TryParse(levelID, out int levelIndex) || !LevelCfg.DataMap.TryGetValue(levelIndex, out _)) { needManualInputLevelID = true; DebugUtil.LogError("无对应的关卡ID请手动输入关卡ID"); curEnemyMonsterID = 0; enemyNpcObject = null; return; } enemyMonsterIDs = new List(); selfMonsterIDs = new List(); midMonsterIDs = new List(); foreach (var monster in MonsterCfg.DataMap) { string strID = monster.Key.ToString(); string tempID = strID[..6]; // 关卡ID相同 if (!tempID.Equals(levelID)) continue; switch (monster.Value.NpcType) { case EMonsterType.EnemyCharacter: { enemyMonsterIDs.Add(monster.Key); } break; case EMonsterType.SelfCharacter: { selfMonsterIDs.Add(monster.Key); } break; case EMonsterType.Vehicle: case EMonsterType.FightBuild: { midMonsterIDs.Add(monster.Key); enemyMonsterIDs.Add(monster.Key); selfMonsterIDs.Add(monster.Key); } break; default: DebugUtil.LogError($"未处理类型:{monster.Value.NpcType}"); break; } } if (enemyMonsterIDs.Count <= 0 && midMonsterIDs.Count <= 0) DebugUtil.LogError($"请检查Monster表,没有{levelID}关卡的怪物数据"); } */ #endregion #region 加载 private void LoadLevel(LevelData levelData) { DestroyNpcPrefab(); _enemyNpcObjectWaveDic.Clear(); _midNpcObjectWaveDic.Clear(); _showNpcInfos.Clear(); _selfNpcObjectDic.Clear(); currentLevelData = levelData; //加载地区信息 curRegionData = new RegionData { preparingRegionId = currentLevelData.preparingRegionId, fallbackRegionId = currentLevelData.fallbackRegionId, enemyFallbackRegionId = currentLevelData.enemyFallbackRegionId, preparingRegionToward = currentLevelData.preparingRegionToward, pvpDefendRegionId = currentLevelData.pvpDefendRegionId, pvpDefendToward = currentLevelData.pvpDefendToward }; _mapManager = FindObjectOfType(); _curMap = _mapManager.Map; _curMap.TerrainGridSystem.OnMouseClickOnGrid += OnCellClick; // 加载占领数据 captureEditorInfo = new List(); foreach (var captureInfo in currentLevelData.CapturePoints) { var captureEditor = new CaptureEditorInfo() { capturePoint = _curMap.TGSCellIndex2BlockPosition(captureInfo.CapturePoint), captureProgress = captureInfo.CaptureProgress, }; captureEditorInfo.Add(captureEditor); } // 加载扛旗数据 flagEditorInfo = new FlagEditorInfo(); foreach (var flagStartPoint in currentLevelData.flagStartPoints) { var pos = _curMap.TGSCellIndex2BlockPosition(flagStartPoint); flagEditorInfo.flagStartPoints.Add(pos); } foreach (var flagEndPoint in currentLevelData.flagEndPoints) { var pos = _curMap.TGSCellIndex2BlockPosition(flagEndPoint); flagEditorInfo.flagEndPoints.Add(pos); } // 加载撤离数据 fallbackEditorInfo = new FallbackEditorInfo(); foreach (var fallback in currentLevelData.fallbackSinglePoints) { var pos = _curMap.TGSCellIndex2BlockPosition(fallback); fallbackEditorInfo.fallbackSinglePoints.Add(pos); } foreach (var fallback in currentLevelData.fallbackReusablePoints) { var pos = _curMap.TGSCellIndex2BlockPosition(fallback); fallbackEditorInfo.fallbackReusablePoints.Add(pos); } // 加载刷新点位 refreshPoints = new List(); refreshPoints.AddRange(currentLevelData.refreshPoints); // 加载指定路线 roadEditorInfos = currentLevelData.patrolRoadData.Select(roadInfo => new PrescribedRoadEditorInfo { roadID = roadInfo.id, roadPoints = roadInfo.patrolInfos, }).ToList(); // 加载指定区域 areaEditorInfos = currentLevelData.patrolRegionData.Select(areaInfo => new PrescribedAreaEditorInfo { areaID = areaInfo.id, areaPoints = areaInfo.patrolInfos, }).ToList(); // 加载关卡进度数据 progressGuideDatas = currentLevelData.progressGuideDatas; // 加载关卡引导立标数据 setFlagEditorInfos = currentLevelData.setFlagGuideDatas.Select(x => new SetFlagGuideEditorInfo(x)).ToList(); // 加载信息板数据 infoPanelGuideDatas = currentLevelData.infoPanelGuideDatas; listAIBroadcastData = currentLevelData.listAIBroadcastData; // 加载敌方NPC到场景中 LoadEnemyNpcInfo(); // 加载中立NPC到场景中 LoadMidNpcInfo(); // 加载己方NPC到场景中 LoadSelfNpcInfo(); // 加载阻挡球 LoadBlockSphereInfo(); } 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); 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++; } } private void LoadMidNpcInfo() { var monsterConfigDic = MonsterCfg.DataMap; currentMidNpcInfo = new(); var index = 1; foreach (var npcInfo in currentLevelData.midNpcInfos) { if (!monsterConfigDic.TryGetValue(npcInfo.id, out var monsterClassData)) { DebugUtil.LogError("请检查配置,没有 {0} 的怪物类ID", npcInfo.id); continue; } var npcEditor = new NpcEditorInfo(npcInfo, index, monsterClassData.MonsterLevel, monsterClassData.AIType, ETroopsId.Neutral); currentMidNpcInfo.Add(npcEditor); var npcObj = LoadMidNpcPrefab(npcInfo.id); if (npcObj != null) { var setPos = _curMap.BlockPosition2WorldPosition(npcInfo.position); var yaw = LevelUtils.SetCharaterToward(npcInfo.npcToward); 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); 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 bool ResponseClickOnCell => isEditingEnemyNpc || isEditingSelfNpc || isEditingMidNpc || isEditingNpcPos || isEditingFlagStart || isEditingFlagEnd || isEditingCapture || isEditingFallbackReusable || isEditingFallbackSingle || isEditingRefreshID || isEditingRoadInfo || isEditingAreaInfo; private void OnCellClick(TerrainGridSystem tgs, int cellindex, int buttonindex) { if (!ResponseClickOnCell) return; var clickPosition = _curMap.TGSCellIndex2BlockPosition(cellindex); int leftButton = 0; int rightButton = 1; if (buttonindex != leftButton && buttonindex != rightButton) return; bool opTrue = buttonindex == leftButton; if (isEditingFlagStart) { EditingFlagStart(clickPosition, true, opTrue); } else if (isEditingFlagEnd) { EditingFlagStart(clickPosition, false, opTrue); } else if (isEditingFallbackSingle) { EditingFallBack(clickPosition, true, opTrue); } else if (isEditingFallbackReusable) { EditingFallBack(clickPosition, false, opTrue); } else if (isEditingCapture) { EditingCapture(clickPosition, opTrue); } else if (isEditingNpcPos) { if (opTrue) ChangeNpcPos(clickPosition, npcTroopId); } else if (isEditingEnemyNpc) { EditEnemyNpc(clickPosition, opTrue); } else if (isEditingSelfNpc) { EditSelfNpc(clickPosition, opTrue); } else if (isEditingMidNpc) { EditMidNpc(clickPosition, opTrue); } else if (isEditingRefreshID) { EditRefreshID(cellindex, opTrue); } else if (isEditingRoadInfo && isEditingRoad) { EditRoad(cellindex, opTrue); } else if (isEditingAreaInfo && isEditingArea) { EditArea(cellindex, opTrue); } } private void EditingFallBack(Vector2Int clickPosition, bool isEditingSingle, bool isAdd) { if (isEditingSingle) { if (isAdd && !fallbackEditorInfo.fallbackSinglePoints.Contains(clickPosition)) { DebugUtil.Log("单次撤离点添加: {0}", clickPosition); fallbackEditorInfo.fallbackSinglePoints.Add(clickPosition); } else if (!isAdd && fallbackEditorInfo.fallbackSinglePoints.Contains(clickPosition)) { DebugUtil.Log("单次撤离点移除: {0}", clickPosition); fallbackEditorInfo.fallbackSinglePoints.Remove(clickPosition); } } else { if (isAdd && !fallbackEditorInfo.fallbackReusablePoints.Contains(clickPosition)) { DebugUtil.Log("复用撤离点添加: {0}", clickPosition); fallbackEditorInfo.fallbackReusablePoints.Add(clickPosition); } else if (!isAdd && fallbackEditorInfo.fallbackReusablePoints.Contains(clickPosition)) { DebugUtil.Log("复用撤离点移除: {0}", clickPosition); fallbackEditorInfo.fallbackReusablePoints.Remove(clickPosition); } } } private void EditingFlagStart(Vector2Int clickPosition, bool isEditingStart, bool isAdd) { if (isEditingStart) { if (isAdd && !flagEditorInfo.flagStartPoints.Contains(clickPosition)) { DebugUtil.Log("扛旗起点添加: {0}", clickPosition); flagEditorInfo.flagStartPoints.Add(clickPosition); } else if (!isAdd && flagEditorInfo.flagStartPoints.Contains(clickPosition)) { DebugUtil.Log("扛旗起点移除: {0}", clickPosition); flagEditorInfo.flagStartPoints.Remove(clickPosition); } } else { if (isAdd && !flagEditorInfo.flagEndPoints.Contains(clickPosition)) { DebugUtil.Log("扛旗终点添加: {0}", clickPosition); flagEditorInfo.flagEndPoints.Add(clickPosition); } else if (!isAdd && flagEditorInfo.flagEndPoints.Contains(clickPosition)) { DebugUtil.Log("扛旗终点移除: {0}", clickPosition); flagEditorInfo.flagEndPoints.Remove(clickPosition); } } } private void EditingCapture(Vector2Int clickPosition, bool isAdd) { var isExist = false; CaptureEditorInfo existCapture = null; foreach (var capture in captureEditorInfo) { if (capture.capturePoint == clickPosition) { isExist = true; existCapture = capture; } } if (isAdd) { if (!isExist) { DebugUtil.Log("占领点添加: {0}", clickPosition); captureEditorInfo.Add(new CaptureEditorInfo() { capturePoint = clickPosition }); } } else { if (existCapture != null) { DebugUtil.Log("占领点移除: {0}", clickPosition); captureEditorInfo.Remove(existCapture); } } } private void ChangeNpcPos(Vector2Int clickPosition, int npcTroopId) { if (null == _currNpcEditorInfo) return; var oldPos = _currEditingNpcInfo.position; if (oldPos == clickPosition) return; var newWorldPos = _curMap.BlockPosition2WorldPosition(clickPosition); if (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 { RemoveEnemyNpc(clickPosition); } } private void AddEnemyNpc(Vector2Int clickPosition) { if (enemyNpcObject == null || curEnemyMonsterID == 0) { DebugUtil.LogError("请选择正确的NPC!!"); return; } _enemyNpcObjectWaveDic ??= new Dictionary>(); NPCInfo npcInfo = new() { id = curEnemyMonsterID, position = clickPosition, index = currentEnemyNpcInfo.Count + 1, troopId = ETroopsId.Enemy, }; var level = 0; var aiType = 0; var monsterConfigDic = MonsterCfg.DataMap; if (monsterConfigDic.TryGetValue(curEnemyMonsterID, out var monster)) { level = monster.MonsterLevel; aiType = monster.AIType; } else { DebugUtil.LogError("获取怪物[{0}]配置数据错误", curEnemyMonsterID); } var npcEditor = new NpcEditorInfo(npcInfo, currentEnemyNpcInfo.Count + 1, level, aiType, ETroopsId.Enemy); 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); npcObjectDic.Add(npcEditor, obj); } else { var obj = Instantiate(enemyNpcObject, pos, enemyNpcObject.transform.rotation); npcObjectDic = new Dictionary { { npcEditor, obj } }; _enemyNpcObjectWaveDic.Add(npcInfo.waveIndex, npcObjectDic); } DebugUtil.Log( $"添加了第[ {npcInfo.waveIndex} ]波的[ {npcInfo.index} ]怪物到 [ {npcInfo.position} ]位置上, 其预制体为: {enemyNpcObject.name}"); } private void RemoveEnemyNpc(Vector2Int clickPosition) { var npc = 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(); } #endregion #region 已放NPC编辑方法 private void EditSelfNpc(Vector2Int clickPosition, bool isAdd) { if (isAdd) { AddSelfNpc(clickPosition); } else { RemoveSelfNpc(clickPosition); } } private void AddSelfNpc(Vector2Int clickPosition) { if (selfNpcObject == null || curSelfNpcID == 0) { DebugUtil.LogError("请选择正确的己方NPC!!"); return; } _selfNpcObjectDic ??= new Dictionary(); if (_selfNpcObjectDic.ContainsKey(clickPosition)) { DebugUtil.LogError("该位置已有NPC存在,请重新选!!!"); return; } NPCInfo npcInfo = new() { id = curSelfNpcID, position = clickPosition, troopId = ETroopsId.Self, }; var level = 0; var aiType = 0; var monsterConfigDic = MonsterCfg.DataMap; if (monsterConfigDic.TryGetValue(curSelfNpcID, out var monster)) { level = monster.MonsterLevel; aiType = monster.AIType; } else { DebugUtil.LogError("获取己方Npc[{0}]配置数据错误", curSelfNpcID); } currentSelfNpcInfo.Add(new NpcEditorInfo(npcInfo, SelfID + currentSelfNpcInfo.Count + 1, level, aiType, ETroopsId.Self)); var pos = _curMap.BlockPosition2WorldPosition(npcInfo.position); _selfNpcObjectDic.Add(npcInfo.position, Instantiate(selfNpcObject, pos, selfNpcObject.transform.rotation)); DebugUtil.Log("添加了己方Npc: [ {0} ] 到 [ {1} ]位置上, 其预制体为: {2}", npcInfo.id, npcInfo.position, selfNpcObject.name); } private void RemoveSelfNpc(Vector2Int clickPosition) { if (_selfNpcObjectDic.TryGetValue(clickPosition, out var npcObj)) { var npc = currentSelfNpcInfo.FirstOrDefault(npcFight => npcFight.npcInfo.position == clickPosition); if (npc != null) { currentSelfNpcInfo.Remove(npc); DebugUtil.Log("从[ {0} ]位置上移除了[{1}] 己方NPC", clickPosition, npcObj.name); DestroyImmediate(npcObj); _selfNpcObjectDic.Remove(clickPosition); } } ReorderSelfNpcID(); } #endregion #region 中立NPC编辑方法 private void EditMidNpc(Vector2Int clickPosition, bool isAdd) { if (isAdd) AddMidNpc(clickPosition); else RemoveMidNpc(clickPosition); } private void AddMidNpc(Vector2Int clickPosition) { if (midNpcObject == null || curMidMonsterID == 0) { DebugUtil.LogError("请选择正确的NPC!!"); return; } _midNpcObjectWaveDic ??= new Dictionary>(); NPCInfo npcInfo = new() { id = curMidMonsterID, position = clickPosition, index = currentMidNpcInfo.Count + 1, troopId = ETroopsId.Neutral, isOpenInvestigate = false, }; var level = 0; var aiType = 0; var monsterConfigDic = MonsterCfg.DataMap; if (monsterConfigDic.TryGetValue(curMidMonsterID, out var monster)) { level = monster.MonsterLevel; aiType = monster.AIType; } else { DebugUtil.LogError("获取怪物[{0}]配置数据错误", curMidMonsterID); } var npcEditor = new NpcEditorInfo(npcInfo, currentMidNpcInfo.Count + 1, level, aiType, ETroopsId.Neutral); currentMidNpcInfo.Add(npcEditor); var pos = _curMap.BlockPosition2WorldPosition(npcInfo.position); if (_midNpcObjectWaveDic.TryGetValue(npcInfo.waveIndex, out var npcObjectDic)) { var obj = Instantiate(midNpcObject, pos, midNpcObject.transform.rotation); npcObjectDic.Add(npcEditor, obj); } else { var obj = Instantiate(midNpcObject, pos, midNpcObject.transform.rotation); npcObjectDic = new Dictionary { { npcEditor, obj } }; _midNpcObjectWaveDic.Add(npcInfo.waveIndex, npcObjectDic); } DebugUtil.Log( $"添加了第[ {npcInfo.waveIndex} ]波的[ {npcInfo.index} ]怪物到 [ {npcInfo.position} ]位置上, 其预制体为: {midNpcObject.name}"); } private void RemoveMidNpc(Vector2Int clickPosition) { var npc = currentMidNpcInfo.LastOrDefault(npc => npc.npcInfo.position == clickPosition); if (npc != null) { if (_midNpcObjectWaveDic.TryGetValue(npc.npcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(npc, out var obj)) { DebugUtil.Log( $"从[ {clickPosition} ]位置上移除了[ {npc.npcInfo.waveIndex} ]波的[{npc.id}]NPC, 其预制体名字为:{obj.name}"); DestroyImmediate(obj); npcObjectDic.Remove(npc); } else { DebugUtil.LogError($"波次{npc.npcInfo.waveIndex}中没有怪物{npc.id}"); } } else { DebugUtil.LogError("没有波次{0}的怪物信息", npc.npcInfo.waveIndex); } currentMidNpcInfo.Remove(npc); } ReorderMidNpcID(); } #endregion private void EditRefreshID(int clickPosition, bool isAdd) { if (isAdd && !refreshPoints.Contains(clickPosition)) { refreshPoints.Add(clickPosition); _curMap.TerrainGridSystem.CellToggleRegionSurface(clickPosition, true, RegionColor); } else if (!isAdd && refreshPoints.Contains(clickPosition)) { refreshPoints.Remove(clickPosition); _curMap.TerrainGridSystem.CellHideRegionSurface(clickPosition); } } /// /// 编辑路线 /// /// /// private void EditRoad(int clickPosition, bool isAdd) { if (_currEditingRoadInfo == null) return; if (isAdd && !_currEditingRoadInfo.roadPoints.Any(x => x.cellIndex == clickPosition)) { _currEditingRoadInfo.roadPoints.Add(new PatrolInfo() { cellIndex = clickPosition, waitTime = 0f, }); } else if (!isAdd && _currEditingRoadInfo.roadPoints.Any(x => x.cellIndex == clickPosition)) { _currEditingRoadInfo.roadPoints = _currEditingRoadInfo.roadPoints.Where(x => x.cellIndex != clickPosition).ToList(); } } /// /// 编辑区域 /// /// /// private void EditArea(int clickPosition, bool isAdd) { if (null == _currEditingAreaInfo) return; if (isAdd && !_currEditingAreaInfo.areaPoints.Any(x => x.cellIndex == clickPosition)) { _currEditingAreaInfo.areaPoints.Add(new PatrolInfo() { cellIndex = clickPosition, waitTime = 0f, }); } else if (!isAdd && _currEditingAreaInfo.areaPoints.Any(x => x.cellIndex == clickPosition)) { _currEditingAreaInfo.areaPoints = _currEditingAreaInfo.areaPoints.Where(x => x.cellIndex != clickPosition).ToList(); } } #endregion #region 敌方Npc某些值改变时响应事件 private void ReorderEnemyNpcID() { for (var i = 0; i < currentEnemyNpcInfo.Count; i++) { currentEnemyNpcInfo[i].id = i + 1; currentEnemyNpcInfo[i].npcInfo.index = currentEnemyNpcInfo[i].id; } } private void ReorderMidNpcID() { for (var i = 0; i < currentMidNpcInfo.Count; i++) { currentMidNpcInfo[i].id = i + 1; currentMidNpcInfo[i].npcInfo.index = currentMidNpcInfo[i].id; } } private void ReorderSelfNpcID() { for (var i = 0; i < currentSelfNpcInfo.Count; i++) { currentSelfNpcInfo[i].id = SelfID + i + 1; } } private void ChangeEnemyNpcID(NpcEditorInfo npcEditorInfo, int newMonsterID) { if (_enemyNpcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj)) { var newObj = LoadEnemyNpcPrefab(newMonsterID); if (newObj != null) { var obj = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); if (enemyObjRoot == null) enemyObjRoot = new GameObject("enemyObjRoot"); if (obj != null) obj.transform.SetParent(enemyObjRoot.transform); npcEditorInfo.npcInfo.id = newMonsterID; npcEditorInfo.level = MonsterCfg.DataMap[newMonsterID].MonsterLevel; npcObjectDic[npcEditorInfo] = obj; DestroyImmediate(npcObj); DebugUtil.Log( $"从[ {npcEditorInfo.npcInfo.position} ]的怪物ID改为:[ {npcEditorInfo.npcInfo.id} ]"); } } } } private void ChangeMidNpcID(NpcEditorInfo npcEditorInfo, int newMonsterID) { if (_midNpcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj)) { var newObj = LoadMidNpcPrefab(newMonsterID); if (newObj != null) { var obj = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); if (midObjRoot == null) midObjRoot = new GameObject("midObjRoot"); if (obj != null) obj.transform.SetParent(midObjRoot.transform); npcEditorInfo.npcInfo.id = newMonsterID; npcEditorInfo.level = MonsterCfg.DataMap[newMonsterID].MonsterLevel; npcObjectDic[npcEditorInfo] = obj; DestroyImmediate(npcObj); DebugUtil.Log( $"从[ {npcEditorInfo.npcInfo.position} ]的怪物ID改为:[ {npcEditorInfo.npcInfo.id} ]"); } } } } private void ChangeSelfNpcID(NPCInfo npcInfoInfo, int newMonsterID) { if (_selfNpcObjectDic.TryGetValue(npcInfoInfo.position, out var npcObj)) { var newObj = LoadSelfNpcPrefab(newMonsterID); if (newObj != null) { var obj = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); if (selfObjRoot == null) selfObjRoot = new GameObject("selfObjRoot"); if (obj != null) obj.transform.SetParent(selfObjRoot.transform); npcInfoInfo.id = newMonsterID; _selfNpcObjectDic[npcInfoInfo.position] = obj; DestroyImmediate(npcObj); DebugUtil.Log("位置上[ {0} ]的fight怪物ID改为: [ {1} ] ", npcInfoInfo.position, npcInfoInfo.id); } } } #endregion #region 检查关卡 private const string PreparingRegionErrorMessage = "关卡出战区设置错误,该地图没有:<{0}>号地区。\n"; private const string FallbackRegionErrorMessage = "关卡撤退区设置错误,该地图没有:<{0}>号地区。\n"; private const string EnemyFallbackRegionErrorMessage = "关卡敌方撤退区设置错误,该地图没有:<{0}>号地区。\n"; private const string FixedMessage = "已将错误区域设置为默认<0>号地区。"; private const string CaptureErrorMessage = "占领条件参数大于配置的占领区域。"; private void CheckCorrectLevel() { if (_curMap == null) { return; } var errorMessage = string.Empty; if (!_curMap.MapData.IsRegionExist(curRegionData.preparingRegionId)) { errorMessage += string.Format(PreparingRegionErrorMessage, curRegionData.preparingRegionId); curRegionData.preparingRegionId = 0; } if (!_curMap.MapData.IsRegionExist(curRegionData.fallbackRegionId)) { errorMessage += string.Format(FallbackRegionErrorMessage, curRegionData.fallbackRegionId); curRegionData.fallbackRegionId = 0; } if (!_curMap.MapData.IsRegionExist(curRegionData.enemyFallbackRegionId)) { errorMessage += string.Format(EnemyFallbackRegionErrorMessage, curRegionData.enemyFallbackRegionId); curRegionData.enemyFallbackRegionId = 0; } // 检查占领条件相关 /* if (!CheckLevelCondition()) { CustomPopUpWindow.PopUp(CaptureErrorMessage); } */ if (!string.IsNullOrEmpty(errorMessage)) { CustomPopUpWindow.PopUp(errorMessage + FixedMessage); } } /*private bool CheckLevelCondition() { if (currentLevelData.stages.Any(stage => stage.successCondition.conditionType == ConditionType.CombatLeastOccupy && stage.successCondition.conditionArgs[0].Arg > captureEditorInfo.Count)) { return false; } if (currentLevelData.starTarget.Any(modifier => modifier.conditionType == ConditionType.CombatLeastOccupy && modifier.conditionArgs[0].Arg > captureEditorInfo.Count)) { return false; } if (currentLevelData.scoreTarget.Any(modifier => modifier.conditionType == ConditionType.CombatLeastOccupy && modifier.conditionArgs[0].Arg > captureEditorInfo.Count)) { return false; } return true; }*/ #endregion #region 内部方法 /// /// 关卡加载时 加载敌方预制体 /// /// Monster表ID /// private GameObject LoadEnemyNpcPrefab(int monsterID) { GameObject obj = null; var curMonsterClassID = 0; if (MonsterCfg.DataMap.TryGetValue(monsterID, out var monster)) { curMonsterClassID = monster.MonsterClassID; } else { return null; } switch (monster.NpcType) { case EMonsterType.Vehicle: { if (VehicleCfg.DataMap.TryGetValue(curMonsterClassID, out var vehicle)) { var prefabPath = PathEx.GetVehicleGameModelPath(vehicle.ID); obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (obj == null) { DebugUtil.LogError("请检查敌方载具类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath); } } else { DebugUtil.LogError("请检查敌方载具类: {0}的配置是否存在", curMonsterClassID); } return obj; } case EMonsterType.EnemyCharacter: { if (MonsterClassCfg.DataMap.TryGetValue(curMonsterClassID, out var monsterClass)) { var prefabPath = PathEx.GetCharacterFightModelPath(monsterClass.NameId, monsterClass.WeaponId); obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (obj == null) { DebugUtil.LogError("请检查敌方怪物类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath); } } else { DebugUtil.LogError("请检查敌方怪物类: {0}的配置是否存在", curMonsterClassID); } return obj; } case EMonsterType.FightBuild: { if (FightBuildCfg.DataMap.TryGetValue(curMonsterClassID, out var buildData)) { var prefabPath = PathEx.GetFightBuildingPath(buildData.ModelPath); obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (obj == null) { DebugUtil.LogError("请检查敌方建筑类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath); } } else { DebugUtil.LogError("请检查敌方建筑类: {0}的配置是否存在", curMonsterClassID); } return obj; } default: return null; } } /// /// 关卡加载时 加载中立预制体 /// /// Monster表ID /// private GameObject LoadMidNpcPrefab(int monsterID) { GameObject obj = null; var curMonsterClassID = 0; if (MonsterCfg.DataMap.TryGetValue(monsterID, out var monster)) { curMonsterClassID = monster.MonsterClassID; } else { return null; } switch (monster.NpcType) { case EMonsterType.Vehicle: { if (VehicleCfg.DataMap.TryGetValue(curMonsterClassID, out var vehicle)) { var prefabPath = PathEx.GetVehicleGameModelPath(vehicle.ID); obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (obj == null) { DebugUtil.LogError("请检查敌方载具类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath); } } else { DebugUtil.LogError("请检查敌方载具类: {0}的配置是否存在", curMonsterClassID); } return obj; } case EMonsterType.EnemyCharacter: { if (MonsterClassCfg.DataMap.TryGetValue(curMonsterClassID, out var monsterClass)) { var prefabPath = PathEx.GetCharacterFightModelPath(monsterClass.NameId, monsterClass.WeaponId); obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (obj == null) { DebugUtil.LogError("请检查敌方怪物类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath); } } else { DebugUtil.LogError("请检查敌方怪物类: {0}的配置是否存在", curMonsterClassID); } return obj; } case EMonsterType.FightBuild: { if (FightBuildCfg.DataMap.TryGetValue(curMonsterClassID, out var buildData)) { var prefabPath = PathEx.GetFightBuildingPath(buildData.ModelPath); obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (obj == null) { DebugUtil.LogError("请检查敌方建筑类: {0}的预制体路径是否存在,对应的完整路径: {1}", curMonsterClassID, prefabPath); } } else { DebugUtil.LogError("请检查敌方建筑类: {0}的配置是否存在", curMonsterClassID); } return obj; } default: return null; } } /// /// 关卡加载时 加载己方预制体 /// /// Monster表ID /// private GameObject LoadSelfNpcPrefab(int monsterID) { GameObject obj = null; var curFightNpcID = 0; if (MonsterCfg.DataMap.TryGetValue(monsterID, out var monster)) { curFightNpcID = monster.MonsterClassID; } else { return null; } switch (monster.NpcType) { case EMonsterType.Vehicle: { if (VehicleCfg.DataMap.TryGetValue(curFightNpcID, out var vehicle)) { var prefabPath = PathEx.GetVehicleGameModelPath(vehicle.ID); obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (obj == null) { DebugUtil.LogError("请检查己方载具类: {0}的预制体路径是否存在,对应的完整路径: {1}", curFightNpcID, prefabPath); } } else { DebugUtil.LogError("请检查己方载具类: {0}的配置是否存在", curFightNpcID); } return obj; } case EMonsterType.SelfCharacter: { if (FightNpcCfg.DataMap.TryGetValue(curFightNpcID, out var fightNpc)) { var prefabPath = PathEx.GetCharacterFightModelPath(fightNpc.NameId, fightNpc.WeaponId); obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (obj == null) { DebugUtil.LogError("请检查己方NPC类: {0}的预制体路径是否存在,对应的完整路径: {1}", curFightNpcID, prefabPath); } } else { DebugUtil.LogError("请检查己方NPC类: {0}的配置是否存在", curFightNpcID); } return obj; } case EMonsterType.FightBuild: { if (FightBuildCfg.DataMap.TryGetValue(curFightNpcID, out var buildData)) { var prefabPath = PathEx.GetFightBuildingPath(buildData.ModelPath); obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (obj == null) { DebugUtil.LogError("请检查己方建筑类: {0}的预制体路径是否存在,对应的完整路径: {1}", curFightNpcID, prefabPath); } } else { DebugUtil.LogError("请检查己方建筑类: {0}的配置是否存在", curFightNpcID); } return obj; } default: return null; } } private void DestroyNpcPrefab() { if (selfObjRoot != null) DestroyImmediate(selfObjRoot); if (midObjRoot != null) DestroyImmediate(midObjRoot); if (enemyObjRoot != null) DestroyImmediate(enemyObjRoot); /*foreach (var npcData in _enemyNpcObjectWaveDic.Values) { foreach (var npc in npcData.Values) { DestroyImmediate(npc); } } foreach (var npcData in _midNpcObjectWaveDic.Values) { foreach (var npc in npcData.Values) { DestroyImmediate(npc); } } foreach (var npc in _selfNpcObjectDic) { DestroyImmediate(npc.Value); }*/ } private void ChangeEnemyNpcToward(NpcEditorInfo npcEditorInfo, CharacterToward npcToward) { if (_enemyNpcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj)) { var yaw = LevelUtils.SetCharaterToward(npcToward); npcObj.transform.eulerAngles = new Vector3(0, yaw, 0); } } } private void ChangeMidNpcToward(NpcEditorInfo npcEditorInfo, CharacterToward npcToward) { if (_midNpcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj)) { var yaw = LevelUtils.SetCharaterToward(npcToward); npcObj.transform.eulerAngles = new Vector3(0, yaw, 0); } } } private void ChangeSelfNpcToward(Vector2Int pos, CharacterToward npcToward) { if (_selfNpcObjectDic.TryGetValue(pos, out var npcObj)) { var yaw = LevelUtils.SetCharaterToward(npcToward); npcObj.transform.eulerAngles = new Vector3(0, yaw, 0); } } private bool CheckConditionSame(ConditionType conditionType) { if (currentLevelData.starTarget.Any(modifier => modifier.conditionType == conditionType)) { return true; } if (currentLevelData.scoreTarget.Any(modifier => modifier.conditionType == conditionType)) { return true; } return false; } #endregion #region MainLife private void Awake() { SceneView.duringSceneGui += OnSceneGUI; } protected override void OnEnable() { TerrainTypeConfig.Load(); _enemyNpcObjectWaveDic = new Dictionary>(); _midNpcObjectWaveDic = new Dictionary>(); _selfNpcObjectDic = new Dictionary(); _showNpcInfos = new List(); base.OnEnable(); } protected override void OnDisable() { StopMonitoringSphere(); ShowHiddenObjects(); SaveBlockSphereInfo(); DestroyBlockSphere(); currWindow = null; DestroyNpcPrefab(); _enemyNpcObjectWaveDic = null; _midNpcObjectWaveDic = null; _selfNpcObjectDic = null; base.OnDisable(); } protected override void OnDestroy() { StopMonitoringSphere(); ShowHiddenObjects(); SaveBlockSphereInfo(); DestroyBlockSphere(); base.OnDestroy(); if (_curMap != null) _curMap.TerrainGridSystem.OnMouseClickOnGrid -= OnCellClick; SaveCurrentLevel(); SceneView.duringSceneGui -= OnSceneGUI; } protected override void OnGUI() { base.OnGUI(); if (_levelEditor == null || _mapManager == null || _mapManager.Map != _curMap || _levelEditor.LevelData != currentLevelData) { Debug.LogError("数据已变更,请重新打开关卡编辑器"); Close(); } } #endregion #region Draw private bool IsShowNpcID => _showNpcInfos != null && _showNpcInfos.Count > 0; private List _showNpcInfos; private void OnSceneGUI(SceneView sceneView) { DrawRoadPath(); DrawAreaPath(); DrawNpcID(); 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 content = $"{waveID},{npc.id}"; 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 content = $"{waveID},{npc.id}"; 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 }