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 static LevelEditorWindow; using NPOI.SS.Formula.Functions; 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 占领相关 [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] [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; } #endregion #region NPC编辑器类 [Serializable] public class NpcEditorInfo { private AITypeConfig AITypeCfg => EditorTableManager.instance.tables.AITypeConfig; [HideInInspector] public bool selfNpc; [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() { if (!selfNpc) return currWindow.GetMonsterIDs(); return currWindow.GetSelfNpcIDs(); } private void ChangeNpcID() { if (!selfNpc) currWindow.ChangeNpcID(this, monsterId); else currWindow.ChangeFightNpcID(npcInfo, monsterId); } [FoldoutGroup("详细信息")] [LabelText("朝向")] [OnValueChanged("NpcTowardOnChange")] public LevelData.CharacterToward npcToward; private void NpcTowardOnChange() { npcInfo.npcToward = npcToward; if (!selfNpc) currWindow.ChangeNpcToward(this, npcToward); else currWindow.ChangeSelfNpcToward(npcInfo.position, npcToward); } [FoldoutGroup("详细信息")] [HideLabel] public LevelData.NPCInfo npcInfo; [FoldoutGroup("详细信息")] [LabelText("行为树")] [ValueDropdown(nameof(GetFilesFromFolder))] public string aiConfigName; [FoldoutGroup("详细信息")] [LabelText("AI预设")] [ReadOnly] public string aiType; private IEnumerable GetFilesFromFolder() { string rootPath = "Assets/Config/AI/"; return new string[] { "" }.Concat(Directory.GetFiles(rootPath, "*.asset", SearchOption.AllDirectories) .Select(Path.GetFileNameWithoutExtension)); } public NpcEditorInfo(LevelData.NPCInfo npcInfo, int index, int level, int aiType, bool isSelfNpc = false) { this.npcInfo = npcInfo; this.level = level; this.npcInfo.aiTypeID = aiType; this.aiType = GetAITypeData(aiType); monsterId = npcInfo.id; id = index; selfNpc = isSelfNpc; npcToward = this.npcInfo.npcToward; aiConfigName = npcInfo.configName; } [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._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 LevelData.NPCInfo _currEditingNpcInfo; private NpcEditorInfo _currNpcEditorInfo; private bool isEditingNpcPos; #endregion #region 敌方Npc /// /// 波次/怪物ID对应的NPC预制体 /// private Dictionary> _npcObjectWaveDic; // private Dictionary _npcObjectDic; [BoxGroup("敌方Npc")] [LabelText("编辑NPC"), Indent] [LabelWidth(100)] [SerializeField] [OnValueChanged("OnIsEditingNpcChanged")] public bool isEditingNpc; [BoxGroup("敌方Npc")] [LabelText("显示所有怪物序号ID"), Indent] [OnValueChanged("ShowAllNpcId")] [SerializeField] public bool isShowAllNpcID; [BoxGroup("敌方Npc")] [LabelText("怪物ID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("MonsterIDChanged")] [ValueDropdown("GetMonsterIDs")] [ShowIf("isEditingNpc")] public int curMonsterID; [BoxGroup("敌方Npc")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("NPC预制体预览图"), ShowIf("isEditingNpc")] public GameObject npcObject; [BoxGroup("敌方Npc")] [LabelText("当前怪物列表"), Indent, ShowIf("isEditingNpc")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true, ShowFoldout = true)] public List currentNpcInfo = null; /// /// 该关卡的敌方NPCID /// [HideInInspector] public List monsterIDs; private IEnumerable GetMonsterIDs() { return monsterIDs; } private void MonsterIDChanged() { var obj = LoadNpcPrefab(curMonsterID); if (obj != null) npcObject = obj; } private void OnIsEditingNpcChanged() { if (isEditingNpc && isEditingSelfNpc) isEditingSelfNpc = false; } private void ShowAllNpcId() { foreach (var npc in currentNpcInfo) { npc.isShowNpcID = isShowAllNpcID; if (isShowAllNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 己方Npc private const int SelfID = 200; 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("己方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() { return selfMonsterIDs; } private void SelfNpcIDChanged() { var obj = LoadSelfNpcPrefab(curSelfNpcID); if (obj != null) selfNpcObject = obj; } private void OnIsEditingSelfNpcChanged() { if (isEditingNpc && isEditingSelfNpc) isEditingNpc = false; } private void ShowAllSelfNpcId() { foreach (var npc in currentSelfNpcInfo) { npc.isShowNpcID = isShowAllSelfNpcID; if (isShowAllSelfNpcID) { if (!_showNpcInfos.Contains(npc)) { _showNpcInfos.Add(npc); } } else { _showNpcInfos.Remove(npc); } } } #endregion #region 建筑编辑器类 [Serializable] public class BuildEditorInfo { private AITypeConfig AITypeCfg => EditorTableManager.instance.tables.AITypeConfig; [HideInInspector] public int camp; [ReadOnly][LabelText("序号ID")] public int id; [FoldoutGroup("详细信息")] [LabelText("BuildID")] [ValueDropdown("GetBuildIDs")] [OnValueChanged("ChangeBuildID")] public int buildId; [FoldoutGroup("详细信息")] [LabelText("等级")] [ReadOnly] public int level; private IEnumerable GetBuildIDs() { return currWindow.GetBuildIDs(); } private void ChangeBuildID() { currWindow.ChangeBuildID(this, buildId); } [FoldoutGroup("详细信息")] [LabelText("朝向")] [OnValueChanged("BuildTowardOnChange")] public CharacterToward buildToward; private void BuildTowardOnChange() { buildInfo.buildToward = buildToward; currWindow.ChangeBuildToward(this, buildToward); } [FoldoutGroup("详细信息")][HideLabel] public BuildInfo buildInfo; [FoldoutGroup("详细信息")] [LabelText("行为树")] [ValueDropdown(nameof(GetFilesFromFolder))] public string aiConfigName; [FoldoutGroup("详细信息")] [LabelText("AI预设")] [ReadOnly] public string aiType; private IEnumerable GetFilesFromFolder() { string rootPath = "Assets/Config/AI/"; return new string[] { "" }.Concat(Directory.GetFiles(rootPath, "*.asset", SearchOption.AllDirectories) .Select(Path.GetFileNameWithoutExtension)); } public BuildEditorInfo(BuildInfo buildInfo, int index, int level, int aiType, int camp = 0) { this.buildInfo = buildInfo; this.level = level; this.buildInfo.aiTypeID = aiType; this.aiType = GetAITypeData(aiType); buildId = buildInfo.id; id = index; this.camp = camp; buildToward = this.buildInfo.buildToward; aiConfigName = buildInfo.configName; } [FoldoutGroup("详细信息")] [LabelText("位置调整")] [OnValueChanged("ChangeBuildPos")] public bool isChangePos; private void ChangeBuildPos() { if (isChangePos) { if (currWindow._currEditingBuildInfo != null) DebugUtil.LogWarning("检查其他建筑是否处于位置调整状态"); } currWindow._currBuildEditorInfo = isChangePos ? this : null; currWindow.isEditingBuildPos = isChangePos; currWindow._currEditingBuildInfo = isChangePos ? buildInfo : null; } [FoldoutGroup("详细信息")] [LabelText("显示该建筑序号")] [OnValueChanged("ShowBuildID")] public bool isShowBuildID; private void ShowBuildID() { if (isShowBuildID && !currWindow._showBuildInfos.Contains(this)) currWindow._showBuildInfos.Add(this); else if (!isShowBuildID) { currWindow._showBuildInfos.Remove(this); } } private string GetAITypeData(int aiTypeID) { if (AITypeCfg.DataMap.TryGetValue(aiTypeID, out var dataAIType)) { return dataAIType.Name; } return $"无效AI类型: {aiTypeID},请检查配置表"; } } private BuildInfo _currEditingBuildInfo; private BuildEditorInfo _currBuildEditorInfo; private bool isEditingBuildPos; #endregion #region 建筑 /// /// 建筑ID对应的建筑预制体 /// private Dictionary _buildObjectDic; [BoxGroup("建筑")] [LabelText("编辑建筑"), Indent] [LabelWidth(100)] [SerializeField] [OnValueChanged("OnIsEditingBuildChanged")] public bool isEditingBuild; [BoxGroup("建筑")] [LabelText("显示所有建筑序号ID"), Indent] [OnValueChanged("ShowAllBuildId")] [SerializeField] public bool isShowAllBuildID; [BoxGroup("建筑")] [LabelText("建筑ID"), Indent, LabelWidth(100)] [SerializeField] [OnValueChanged("BuildIDChanged")] [ValueDropdown("GetBuildIDs")] [ShowIf("isEditingBuild")] public int curBuildID; [BoxGroup("建筑")] [PreviewField(150, ObjectFieldAlignment.Center), Indent, ReadOnly] [LabelText("建筑预制体预览图"), ShowIf("isEditingBuild")] public GameObject buildObject; [BoxGroup("建筑")] [LabelText("当前建筑列表"), Indent, ShowIf("isEditingBuild")] [ListDrawerSettings(HideAddButton = true, HideRemoveButton = true, ShowFoldout = true)] public List currentBuildInfo = null; /// /// 该关卡的建筑ID /// [HideInInspector] public List buildIDs; private IEnumerable GetBuildIDs() { return buildIDs; } private void BuildIDChanged() { GameObject obj = LoadBuildPrefab(curBuildID); if (null != obj) npcObject = obj; } private void OnIsEditingBuildChanged() { if (isEditingNpc && isEditingSelfNpc && isEditingBuild) isEditingSelfNpc = false; } private void ShowAllBuildId() { foreach (var build in currentBuildInfo) { build.isShowBuildID = isShowAllBuildID; if (isShowAllBuildID) { if (!_showBuildInfos.Contains(build)) _showBuildInfos.Add(build); } else _showBuildInfos.Remove(build); } } #endregion #region 保存 [Button("保存当前关卡"), Indent] private void SaveCurrentLevel() { CheckCorrectLevel(); // 地区 currentLevelData.preparingRegionId = curRegionData.preparingRegionId; currentLevelData.fallbackRegionId = curRegionData.fallbackRegionId; currentLevelData.enemyFallbackRegionId = curRegionData.enemyFallbackRegionId; currentLevelData.preparingRegionToward = curRegionData.preparingRegionToward; currentLevelData.pvpDefendRegionId = curRegionData.pvpDefendRegionId; currentLevelData.pvpDefendToward = curRegionData.pvpDefendToward; // NPC foreach (var npcEditor in currentNpcInfo) { npcEditor.npcInfo.index = npcEditor.id; npcEditor.npcInfo.configName = npcEditor.aiConfigName; } currentLevelData.npcInfos = currentNpcInfo.Select(editorNpcInfo => editorNpcInfo.npcInfo).ToList(); // 己方NPC foreach (var npcFightEditorInfo in currentSelfNpcInfo) { npcFightEditorInfo.npcInfo.index = npcFightEditorInfo.id; npcFightEditorInfo.npcInfo.configName = npcFightEditorInfo.aiConfigName; } currentLevelData.selfNpcInfos = currentSelfNpcInfo.Select(editorFightNpcInfo => editorFightNpcInfo.npcInfo).ToList(); // 建筑 /*foreach (var buildEditor in currentBuildInfo) { buildEditor.buildInfo.index = buildEditor.id; buildEditor.buildInfo.configName = buildEditor.aiConfigName; } currentLevelData.buildInfos = currentBuildInfo.Select(editorFightBuildInfo => editorFightBuildInfo.buildInfo).ToList();*/ // 占领数据 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; // 处理条件(击杀对象列表) ProcessCondition(); _levelEditor.SaveLevelData(); } 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); } 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 var levelIndex) || !LevelCfg.DataMap.TryGetValue(levelIndex, out var level)) { needManualInputLevelID = true; DebugUtil.LogError("无对应的关卡ID请手动输入关卡ID"); curMonsterID = 0; npcObject = null; return; } monsterIDs = new List(); selfMonsterIDs = new List(); foreach (var monster in MonsterCfg.DataMap) { var strID = monster.Key.ToString(); var tempID = strID.Substring(0, 6); //关卡ID相同 if (tempID.Equals(levelID)) { switch (monster.Value.NpcType) { case EMonsterType.SelfCharacter: case EMonsterType.Vehicle: case EMonsterType.FightBuild: selfMonsterIDs.Add(monster.Key); break; default: monsterIDs.Add(monster.Key); break; } } } if (monsterIDs.Count <= 0) { DebugUtil.LogError("请检查Monster表,没有{0}关卡的怪物数据", levelID); } } #endregion #region 加载 private void LoadLevel(LevelData levelData) { DestroyNpcPrefab(); //DestoryBuildPrefab(); // _npcObjectDic.Clear(); _npcObjectWaveDic.Clear(); _showNpcInfos.Clear(); _selfNpcObjectDic.Clear(); //_buildObjectDic.Clear(); //_showBuildInfos.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; // 加载NPC到场景中 LoadNpcInfo(); // 加载己方NPC LoadSelfNpcInfo(); } private void LoadNpcInfo() { var monsterConfigDic = MonsterCfg.DataMap; currentNpcInfo = new(); var index = 1; foreach (var npcInfo in currentLevelData.npcInfos) { if (!monsterConfigDic.TryGetValue(npcInfo.id, out var monsterClassData)) { DebugUtil.LogError("请检查配置,没有 {0} 的怪物类ID", npcInfo.id); continue; } var npcEditor = new NpcEditorInfo(npcInfo, index, monsterClassData.MonsterLevel, monsterClassData.AIType); currentNpcInfo.Add(npcEditor); var npcObj = LoadNpcPrefab(npcInfo.id); if (npcObj != null) { var setPos = _curMap.BlockPosition2WorldPosition(npcInfo.position); var yaw = LevelUtils.SetCharaterToward(npcInfo.npcToward); if (_npcObjectWaveDic.TryGetValue(npcInfo.waveIndex, out var npcObjectDic)) { npcObjectDic.Add(npcEditor, Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0))); } else { npcObjectDic = new Dictionary { { npcEditor, Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0)) } }; _npcObjectWaveDic.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, true); currentSelfNpcInfo.Add(npcEditor); var npcObj = LoadSelfNpcPrefab(selfNpcInfo.id); if (npcObj != null) { var setPos = _curMap.BlockPosition2WorldPosition(selfNpcInfo.position); var yaw = LevelUtils.SetCharaterToward(selfNpcInfo.npcToward); _selfNpcObjectDic.Add(selfNpcInfo.position, Instantiate(npcObj, setPos, Quaternion.Euler(0, yaw, 0))); } index++; } } #endregion #region 点击添加事件 private bool ResponseClickOnCell => isEditingNpc || isEditingSelfNpc || isEditingBuild || isEditingNpcPos || isEditingBuildPos || 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); } else if (isEditingNpc) { EditNpc(clickPosition, opTrue); } else if (isEditingSelfNpc) { EditSelfNpc(clickPosition, opTrue); } else if (isEditingBuildPos) { if (opTrue) ChangeBuildPos(clickPosition); } else if (isEditingBuild) { EditBuild(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) { if (_currNpcEditorInfo == null) return; var oldPos = _currEditingNpcInfo.position; if (oldPos == clickPosition) return; var index = _currEditingNpcInfo.index; var newWorldPos = _curMap.BlockPosition2WorldPosition(clickPosition); if (_npcObjectWaveDic.TryGetValue(_currEditingNpcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(_currNpcEditorInfo, out var npcObj)) { npcObj.transform.position = newWorldPos; _currEditingNpcInfo.position = clickPosition; /*npcObjectDic.Remove(oldPos); npcObjectDic.Add(clickPosition, npcObj);*/ DebugUtil.Log("怪物: [ {0} ] 移动到 [ {1} ]位置上", _currEditingNpcInfo.id, clickPosition); } } else if (_selfNpcObjectDic.TryGetValue(oldPos, out var selfNpcObj)) { selfNpcObj.transform.position = newWorldPos; _currEditingNpcInfo.position = clickPosition; _selfNpcObjectDic.Remove(oldPos); _selfNpcObjectDic.Add(clickPosition, selfNpcObj); DebugUtil.Log("怪物: [ {0} ] 移动到 [ {1} ]位置上", _currEditingNpcInfo.id, clickPosition); } else { DebugUtil.LogError("需要更改的旧位置上没有怪物:{0}", oldPos); } } private void EditNpc(Vector2Int clickPosition, bool isAdd) { if (isAdd) { AddNpc(clickPosition); } else { RemoveNpc(clickPosition); } } private void AddNpc(Vector2Int clickPosition) { if (npcObject == null || curMonsterID == 0) { DebugUtil.LogError("请选择正确的NPC!!"); return; } _npcObjectWaveDic ??= new Dictionary>(); /*_npcObjectDic ??= new Dictionary(); if (_npcObjectDic.ContainsKey(clickPosition)) { DebugUtil.LogError("该位置已有NPC存在,请重新选!!!"); return; }*/ LevelData.NPCInfo npcInfo = new LevelData.NPCInfo { id = curMonsterID, position = clickPosition, index = currentNpcInfo.Count + 1 }; var level = 0; var aiType = 0; var monsterConfigDic = MonsterCfg.DataMap; if (monsterConfigDic.TryGetValue(curMonsterID, out var monster)) { level = monster.MonsterLevel; aiType = monster.AIType; } else { DebugUtil.LogError("获取怪物[{0}]配置数据错误", curMonsterID); } var npcEditor = new NpcEditorInfo(npcInfo, currentNpcInfo.Count + 1, level, aiType); currentNpcInfo.Add(npcEditor); var pos = _curMap.BlockPosition2WorldPosition(npcInfo.position); if (_npcObjectWaveDic.TryGetValue(npcInfo.waveIndex, out var npcObjectDic)) { var obj = Instantiate(npcObject, pos, npcObject.transform.rotation); npcObjectDic.Add(npcEditor, obj); } else { var obj = Instantiate(npcObject, pos, npcObject.transform.rotation); npcObjectDic = new Dictionary { { npcEditor, obj } }; _npcObjectWaveDic.Add(npcInfo.waveIndex, npcObjectDic); } DebugUtil.Log( $"添加了第[ {npcInfo.waveIndex} ]波的[ {npcInfo.index} ]怪物到 [ {npcInfo.position} ]位置上, 其预制体为: {npcObject.name}"); } private void RemoveNpc(Vector2Int clickPosition) { var npc = currentNpcInfo.LastOrDefault(npc => npc.npcInfo.position == clickPosition); if (npc != null) { if (_npcObjectWaveDic.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); } currentNpcInfo.Remove(npc); } ReorderNpcID(); } private void EditSelfNpc(Vector2Int clickPosition, bool isAdd) { if (isAdd) { AddSelfNpc(clickPosition); } else { RemoveSelfNpc(clickPosition); } } /// /// 编辑建筑 /// /// /// private void EditBuild(Vector2Int clickPosition, bool isAdd) { if (isAdd) AddBuild(clickPosition); else RemoveBuild(clickPosition); } private void ChangeBuildPos(Vector2Int clickPosition) { if (null == _currBuildEditorInfo) return; Vector2Int oldPos = _currEditingBuildInfo.position; if (oldPos == clickPosition) return; Vector3 newWorldPos = _curMap.BlockPosition2WorldPosition(clickPosition); if (!_buildObjectDic.TryGetValue(_currBuildEditorInfo, out GameObject buildObject)) { DebugUtil.LogError($"需要更改的旧位置上没有建筑:{oldPos}"); return; } buildObject.transform.position = newWorldPos; _currEditingBuildInfo.position = clickPosition; DebugUtil.Log("建筑: [ {0} ] 移动到 [ {1} ]位置上", _currEditingBuildInfo.id, clickPosition); } /// /// 添加建筑 /// /// private void AddBuild(Vector2Int clickPosition) { if (null == buildObject || 0 == curBuildID) { DebugUtil.LogError("请选择正确的建筑!!"); return; } _buildObjectDic ??= new Dictionary(); BuildInfo buildInfo = new() { id = curBuildID, position = clickPosition, index = currentBuildInfo.Count + 1 }; int level = 0; int aiType = 0; var buildConfigDic = FightBuildCfg.DataMap; if (buildConfigDic.TryGetValue(curBuildID, out DataBuild build)) level = build.FightLevel; else DebugUtil.LogError("获取建筑[{0}]配置数据错误", curBuildID); BuildEditorInfo buildEditor = new(buildInfo, currentBuildInfo.Count + 1, level, aiType); currentBuildInfo.Add(buildEditor); Vector3 pos = _curMap.BlockPosition2WorldPosition(buildInfo.position); GameObject obj = Instantiate(buildObject, pos, buildObject.transform.rotation); _buildObjectDic.Add(buildEditor, obj); DebugUtil.Log($"添加了{buildInfo.index} ]建筑到 [ {buildInfo.position} ]位置上, 其预制体为: {buildObject.name}"); } /// /// 移除建筑 /// /// private void RemoveBuild(Vector2Int clickPosition) { var build = currentBuildInfo.LastOrDefault(build => build.buildInfo.position == clickPosition); if (null == build) { ReorderBuildID(); return; } if (_buildObjectDic.TryGetValue(build, out GameObject buildObject)) { DebugUtil.Log($"从[ {clickPosition} ]位置上移除了[{build.id}]建筑, 其预制体名字为:{buildObject.name}"); DestroyImmediate(buildObject); currentBuildInfo.Remove(build); } else { DebugUtil.LogError($"没有建筑{build.id}"); } ReorderBuildID(); } 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(); } } private void AddSelfNpc(Vector2Int clickPosition) { if (selfNpcObject == null || curSelfNpcID == 0) { DebugUtil.LogError("请选择正确的己方NPC!!"); return; } _selfNpcObjectDic ??= new Dictionary(); if (_selfNpcObjectDic.ContainsKey(clickPosition)) { DebugUtil.LogError("该位置已有NPC存在,请重新选!!!"); return; } LevelData.NPCInfo npcInfo = new LevelData.NPCInfo { id = curSelfNpcID, position = clickPosition }; 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, true)); 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 ReorderNpcID() { for (var i = 0; i < currentNpcInfo.Count; i++) { currentNpcInfo[i].id = i + 1; currentNpcInfo[i].npcInfo.index = currentNpcInfo[i].id; } } private void ReorderSelfNpcID() { for (var i = 0; i < currentSelfNpcInfo.Count; i++) { currentSelfNpcInfo[i].id = SelfID + i + 1; } } private void ChangeNpcID(NpcEditorInfo npcEditorInfo, int newMonsterID) { if (_npcObjectWaveDic.TryGetValue(npcEditorInfo.npcInfo.waveIndex, out var npcObjectDic)) { if (npcObjectDic.TryGetValue(npcEditorInfo, out var npcObj)) { var newObj = LoadNpcPrefab(newMonsterID); if (newObj != null) { npcEditorInfo.npcInfo.id = newMonsterID; npcEditorInfo.npcInfo.aiTypeID = MonsterCfg.DataMap[newMonsterID].AIType; npcEditorInfo.level = MonsterCfg.DataMap[newMonsterID].MonsterLevel; npcObjectDic[npcEditorInfo] = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); DestroyImmediate(npcObj); DebugUtil.Log( $"从[ {npcEditorInfo.npcInfo.position} ]的怪物ID改为:[ {npcEditorInfo.npcInfo.id} ]"); } } } } private void ChangeFightNpcID(LevelData.NPCInfo npcInfoInfo, int newMonsterID) { if (_selfNpcObjectDic.TryGetValue(npcInfoInfo.position, out var npcObj)) { var newObj = LoadSelfNpcPrefab(newMonsterID); if (newObj != null) { npcInfoInfo.id = newMonsterID; npcInfoInfo.aiTypeID = MonsterCfg.DataMap[newMonsterID].AIType; _selfNpcObjectDic[npcInfoInfo.position] = Instantiate(newObj, npcObj.transform.position, npcObj.transform.rotation); DestroyImmediate(npcObj); DebugUtil.Log("位置上[ {0} ]的fight怪物ID改为: [ {1} ] ", npcInfoInfo.position, npcInfoInfo.id); } } } #endregion #region 建筑某些值改变时响应事件 private void ReorderBuildID() { for (int i = 0; i < currentBuildInfo.Count; ++i) { currentBuildInfo[i].id = i + 1; currentBuildInfo[i].buildInfo.index = currentBuildInfo[i].id; } } private void ChangeBuildID(BuildEditorInfo buildEditorInfo, int newBuildID) { if (!_buildObjectDic.TryGetValue(buildEditorInfo, out GameObject buildObj)) return; GameObject newObj = LoadBuildPrefab(newBuildID); if (null == newObj) return; buildEditorInfo.buildInfo.id = newBuildID; buildEditorInfo.level = FightBuildCfg.DataMap[newBuildID].FightLevel; _buildObjectDic[buildEditorInfo] = Instantiate(newObj, buildObj.transform.position, buildObj.transform.rotation); DestroyImmediate(buildObj); DebugUtil.Log($"从[ {buildEditorInfo.buildInfo.position} ]的建筑ID改为:[ {buildEditorInfo.buildInfo.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 LoadNpcPrefab(int monsterID) { GameObject obj = null; var curMonsterClassID = 0; if (MonsterCfg.DataMap.TryGetValue(monsterID, out var monster)) { curMonsterClassID = monster.MonsterClassID; } else { return null; } switch (monster.NpcType) { case 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 GameObject LoadBuildPrefab(int buildID) { if (!FightBuildCfg.DataMap.TryGetValue(buildID, out DataBuild buildData)) { DebugUtil.LogError($"请检查建筑类: {buildID}的配置是否存在"); return null; } string prefabPath = PathEx.GetFightBuildingPath(buildData.ModelPath); GameObject obj = AssetDatabase.LoadAssetAtPath(prefabPath); if (null == obj) DebugUtil.LogError($"请检查敌方建筑类: {buildID}的预制体路径是否存在,对应的完整路径: {prefabPath}"); return obj; } private void DestroyNpcPrefab() { foreach (var npcData in _npcObjectWaveDic.Values) { foreach (var npc in npcData.Values) { DestroyImmediate(npc); } } foreach (var npc in _selfNpcObjectDic) { DestroyImmediate(npc.Value); } } private void DestoryBuildPrefab() { foreach (var build in _buildObjectDic.Values) { DestroyImmediate(build); } } private void ChangeNpcToward(NpcEditorInfo npcEditorInfo, LevelData.CharacterToward npcToward) { if (_npcObjectWaveDic.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, LevelData.CharacterToward npcToward) { if (_selfNpcObjectDic.TryGetValue(pos, out var npcObj)) { var yaw = LevelUtils.SetCharaterToward(npcToward); npcObj.transform.eulerAngles = new Vector3(0, yaw, 0); } } private void ChangeBuildToward(BuildEditorInfo buildEditorInfo, CharacterToward buildToward) { if (!_buildObjectDic.TryGetValue(buildEditorInfo, out GameObject buildObject)) return; float yaw = LevelUtils.SetCharaterToward(buildToward); buildObject.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(); _npcObjectWaveDic = new Dictionary>(); _showNpcInfos = new List(); _selfNpcObjectDic = new Dictionary(); base.OnEnable(); } protected override void OnDisable() { currWindow = null; DestroyNpcPrefab(); //DestoryBuildPrefab(); _npcObjectWaveDic = null; _selfNpcObjectDic = null; _buildObjectDic = null; base.OnDisable(); } protected override void OnDestroy() { base.OnDestroy(); if (_curMap != null) _curMap.TerrainGridSystem.OnMouseClickOnGrid -= OnCellClick; SaveCurrentLevel(); SceneView.duringSceneGui -= OnSceneGUI; } protected override void OnGUI() { base.OnGUI(); if (_levelEditor == null || _mapManager == null || _mapManager.Map != _curMap || _levelEditor.LevelData != currentLevelData) { Debug.LogError("数据已变更,请重新打开关卡编辑器"); Close(); } } #endregion #region Draw private bool IsShowNpcID => _showNpcInfos != null && _showNpcInfos.Count > 0; private bool IsShowBuildID => null != _showBuildInfos && _showBuildInfos.Count > 0; private List _showNpcInfos; private List _showBuildInfos; private void OnSceneGUI(SceneView sceneView) { DrawRoadPath(); DrawAreaPath(); DrawNpcID(); DrawBuildID(); } 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 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 (_npcObjectWaveDic.TryGetValue(npc.npcInfo.waveIndex, out var npcObjectDic)) { var waveID = npc.npcInfo.waveIndex; if (npcObjectDic.TryGetValue(npc, out var npcObj)) { var content = $"{waveID},{npc.id}"; Handles.Label(npcObj.transform.position, content, labelStyle); } } else if (_selfNpcObjectDic.TryGetValue(npc.npcInfo.position, out var selfNpcObj)) { Handles.Label(selfNpcObj.transform.position, npc.id.ToString(), labelStyleSelf); } } } private void DrawBuildID() { if (!IsShowBuildID) 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 build in _showBuildInfos) { if (_buildObjectDic.TryGetValue(build, out GameObject buildObject)) Handles.Label(buildObject.transform.position, build.id.ToString(), labelStyle); } } #endregion }