【关卡编辑器】可视化编辑NPC
parent
4294319b28
commit
15f231cef6
|
|
@ -1,4 +1,6 @@
|
|||
namespace Framework
|
||||
using NPOI.SS.Formula.Functions;
|
||||
|
||||
namespace Framework
|
||||
{
|
||||
public partial class Constants
|
||||
{
|
||||
|
|
@ -14,11 +16,13 @@
|
|||
public const string ANIMATOR_CONFIG_FORMAT_PATH = "Assets/Config/Skills/AnimatorInfos/animator_{0}.json";
|
||||
public const string OUTLINE_POSTPROCESS_PATH = "Assets/Level/Prefabs/OutlineManager.prefab";
|
||||
public const string MAP_STRUCTURE_PATH_WITHOUT_ASSET = "Art/Character/Prefabs/Structure";
|
||||
|
||||
public const string LEVEL_NPC_PATH_WITHOUT_ASSET = "Art/Character/Prefabs/Enemy";
|
||||
|
||||
public const string CHARACTER_SELECT_GROUND_GFX = "Assets/Art/Gfx/Prefabs/GfxSelectedGround.prefab";
|
||||
public const string CHARACTER_TARGET_GFX = "Assets/_Test/Prefab/EndNotice.prefab";
|
||||
public const string TIREMARK_UNIT_PATH = "Assets/Art/Scene/TireMark/tiremark_panel.prefab";
|
||||
public const string TIREMARK_MAT_PATH = "Assets/Art/Scene/TireMark/mat_tiremark_draw.mat";
|
||||
public const string LEVEL_NPC_PATH = "Assets/Art/Character/Prefabs/Enemy/{0}.prefab";
|
||||
|
||||
public const string EMOJI_HEAD_MAT = "Assets/Art/Character/Models/Emoji/Mat/M_emoji_head.mat";
|
||||
|
||||
|
|
|
|||
|
|
@ -68,8 +68,10 @@ namespace Gameplay.Level
|
|||
[Serializable]
|
||||
public class NPCInfo
|
||||
{
|
||||
[Sirenix.OdinInspector.ReadOnly]
|
||||
public int id;
|
||||
public int level;
|
||||
[Sirenix.OdinInspector.ReadOnly]
|
||||
public Vector2Int position;
|
||||
[LabelText("AI类型")]
|
||||
public AIType aIType;
|
||||
|
|
@ -133,15 +135,19 @@ namespace Gameplay.Level
|
|||
public string mapName;
|
||||
|
||||
[LabelText("出战区")]
|
||||
[HideInInspector]
|
||||
public int preparingRegionId;
|
||||
|
||||
[LabelText("出战区朝向")]
|
||||
[HideInInspector]
|
||||
public CharacterToward preparingRegionToward;
|
||||
|
||||
[LabelText("撤退区")]
|
||||
[HideInInspector]
|
||||
public int fallbackRegionId;
|
||||
|
||||
[LabelText("敌方撤退区")]
|
||||
[HideInInspector]
|
||||
public int enemyFallbackRegionId;
|
||||
|
||||
[LabelText("天气")]
|
||||
|
|
@ -157,8 +163,9 @@ namespace Gameplay.Level
|
|||
[ListDrawerSettings(HideAddButton = true,HideRemoveButton = true)]
|
||||
public List<ConditionModifier> starTarget = new List<ConditionModifier>(){new ConditionModifier(ConditionUtil.LevelStarConditionFilter),new ConditionModifier(ConditionUtil.LevelStarConditionFilter),new ConditionModifier(ConditionUtil.LevelStarConditionFilter)};
|
||||
|
||||
[InfoBox("敌人默认朝向为六边形TopLeft,以游戏视角的六边形为模板方向")]
|
||||
[LabelText("敌人")]
|
||||
/*[InfoBox("敌人默认朝向为六边形TopLeft,以游戏视角的六边形为模板方向")]
|
||||
[LabelText("敌人")] */
|
||||
[HideInInspector]
|
||||
public List<NPCInfo> npcInfos = new List<NPCInfo>(16);
|
||||
|
||||
[OnDeserialized]
|
||||
|
|
@ -169,5 +176,7 @@ namespace Gameplay.Level
|
|||
starCondition.SetFilterTypes(ConditionUtil.LevelStarConditionFilter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -8,20 +8,24 @@ using Sirenix.Utilities;
|
|||
using Sirenix.Utilities.Editor;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TGS;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using Constants = Framework.Constants;
|
||||
using ObjectFieldAlignment = Sirenix.OdinInspector.ObjectFieldAlignment;
|
||||
|
||||
|
||||
public class LevelEditorWindow : OdinEditorWindow
|
||||
{
|
||||
private static readonly Color regionColor = MapUtils.GetRGBAColor("FF960072");
|
||||
|
||||
[Serializable]
|
||||
public class LevelPresentation
|
||||
{
|
||||
[ReadOnly] public string levelId = "temp";
|
||||
|
||||
[ReadOnly]
|
||||
public string levelId = "temp";
|
||||
|
||||
|
||||
[Button("编辑"), VerticalGroup("")]
|
||||
public void Open()
|
||||
|
|
@ -34,7 +38,7 @@ public class LevelEditorWindow : OdinEditorWindow
|
|||
thisWindow.SaveCurrentLevel();
|
||||
thisWindow.LoadLevel(levelId);
|
||||
currentLevelPresentation = this;
|
||||
} ,
|
||||
},
|
||||
() =>
|
||||
{
|
||||
thisWindow.LoadLevel(levelId);
|
||||
|
|
@ -50,34 +54,87 @@ public class LevelEditorWindow : OdinEditorWindow
|
|||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RegionData
|
||||
{
|
||||
[LabelText("出战区")]
|
||||
[OnValueChanged("OnRegionChanged")]
|
||||
public int preparingRegionId;
|
||||
|
||||
[LabelText("出战区朝向")]
|
||||
public LevelData.CharacterToward preparingRegionToward;
|
||||
|
||||
[LabelText("撤退区")]
|
||||
public int fallbackRegionId;
|
||||
|
||||
[LabelText("敌方撤退区")]
|
||||
public int enemyFallbackRegionId;
|
||||
|
||||
private void OnRegionChanged()
|
||||
{
|
||||
var regionslist = thisWindow._curMap.MapData.GetAllRegions();
|
||||
for(int i = 0; i < regionslist.Count; i++)
|
||||
{
|
||||
thisWindow._curMap.SetRegionColor(i, false, regionColor);
|
||||
}
|
||||
thisWindow._curMap.SetRegionColor(preparingRegionId, true, regionColor);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static LevelPresentation currentLevelPresentation;
|
||||
|
||||
private Dictionary<Vector2Int, GameObject> _npcDic;
|
||||
|
||||
private Map _curMap;
|
||||
|
||||
private static LevelEditorWindow thisWindow;
|
||||
|
||||
[TitleGroup("关卡编辑器")]
|
||||
|
||||
|
||||
[HorizontalGroup("关卡编辑器/Split", Width = 0.4f)]
|
||||
[TableList(ShowIndexLabels = true, AlwaysExpanded = true, DrawScrollView = true, ScrollViewHeight = 500), LabelText("所有关卡")]
|
||||
[TableList(ShowIndexLabels = true, AlwaysExpanded = true, DrawScrollView = true, ScrollViewHeight = 500),
|
||||
LabelText("所有关卡")]
|
||||
[OnValueChanged("OnLevelListChanged")]
|
||||
public List<LevelPresentation> LevelList = null;
|
||||
|
||||
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[LabelText("关卡名称"), Indent]
|
||||
public string levelName;
|
||||
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[TextArea, LabelText("关卡描述"),Indent]
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[TextArea, LabelText("关卡描述"), Indent]
|
||||
public string levelDesc;
|
||||
|
||||
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[Indent]
|
||||
[LabelText("当前关卡数据")]
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[LabelText("当前关卡数据"),Indent]
|
||||
[OnValueChanged("OnCurrentLevelDataChanged")]
|
||||
public LevelData currentLevelData = null;
|
||||
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[LabelText("当前地区数据"),Indent]
|
||||
[OnValueChanged("OnCurrentLevelDataChanged")]
|
||||
public RegionData curRegionData = null;
|
||||
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[LabelText("添加NPC")]
|
||||
[LabelWidth(100)]
|
||||
[SerializeField]
|
||||
public bool canAddNpc;
|
||||
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[PreviewField(150,ObjectFieldAlignment.Center)]
|
||||
[AssetList(Path = (Constants.LEVEL_NPC_PATH_WITHOUT_ASSET))]
|
||||
[ShowIf("canAddNpc")]
|
||||
[LabelText("选择NPC")]
|
||||
public GameObject npcObject;
|
||||
|
||||
|
||||
[VerticalGroup("关卡编辑器/Split/Right")] [LabelText("当前NPC数据")]
|
||||
public List<LevelData.NPCInfo> CurrentNpcInfo = null;
|
||||
|
||||
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[Indent]
|
||||
[EnableIf("@this.mapPrefab != null")]
|
||||
|
|
@ -86,15 +143,16 @@ public class LevelEditorWindow : OdinEditorWindow
|
|||
{
|
||||
currentLevelData.mapName = mapPrefab.name;
|
||||
EditorMap.Instance.Current = await MapEditorUtils.LoadMap(currentLevelData.mapName);
|
||||
_curMap = EditorMap.Instance.Current;
|
||||
}
|
||||
|
||||
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[Indent]
|
||||
[AssetList(Path = (Constants.MAP_ASSET_PATH_WITHOUT_ASSET))]
|
||||
[PreviewField(200, ObjectFieldAlignment.Center)]
|
||||
public GameObject mapPrefab = null;
|
||||
|
||||
|
||||
|
||||
|
||||
[VerticalGroup("关卡编辑器/Split/Right")]
|
||||
[Indent]
|
||||
[Button("保存当前关卡")]
|
||||
|
|
@ -102,12 +160,19 @@ public class LevelEditorWindow : OdinEditorWindow
|
|||
{
|
||||
CheckCorrectLevel();
|
||||
var configFilePath = string.Format(Constants.LEVEL_CONFIG_FORMAT_PATH, currentLevelData.id);
|
||||
// VersionControlSystem.Checkout(configFilePath);
|
||||
currentLevelData.preparingRegionId = curRegionData.preparingRegionId;
|
||||
currentLevelData.fallbackRegionId = curRegionData.fallbackRegionId;
|
||||
currentLevelData.enemyFallbackRegionId = curRegionData.enemyFallbackRegionId;
|
||||
currentLevelData.preparingRegionToward = curRegionData.preparingRegionToward;
|
||||
|
||||
//NPC列表
|
||||
currentLevelData.npcInfos = CurrentNpcInfo;
|
||||
|
||||
JsonHelper.SaveJson(configFilePath, currentLevelData);
|
||||
UpdateLevelList(currentLevelData);
|
||||
CustomPopUpWindow.PopUp($"保存关卡数据成功:{configFilePath}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
[MenuItem("Tools/LevelEditor")]
|
||||
private static void OpenWindow()
|
||||
|
|
@ -126,41 +191,46 @@ public class LevelEditorWindow : OdinEditorWindow
|
|||
|
||||
private void CheckCorrectLevel()
|
||||
{
|
||||
var map = EditorMap.Instance.Current;
|
||||
if (map == null)
|
||||
if (_curMap == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var regionList = map.MapData.GetAllRegions();
|
||||
|
||||
var regionList = _curMap.MapData.GetAllRegions();
|
||||
|
||||
var errorMessage = string.Empty;
|
||||
|
||||
if (!regionList.ContainsKey(currentLevelData.preparingRegionId))
|
||||
{
|
||||
errorMessage += string.Format(preparingRegionErrorMessage, currentLevelData.mapName, currentLevelData.preparingRegionId);
|
||||
errorMessage += string.Format(preparingRegionErrorMessage, currentLevelData.mapName,
|
||||
currentLevelData.preparingRegionId);
|
||||
currentLevelData.preparingRegionId = 0;
|
||||
}
|
||||
|
||||
if (!regionList.ContainsKey(currentLevelData.fallbackRegionId))
|
||||
{
|
||||
errorMessage += string.Format(fallbackRegionErrorMessage, currentLevelData.mapName, currentLevelData.fallbackRegionId);
|
||||
errorMessage += string.Format(fallbackRegionErrorMessage, currentLevelData.mapName,
|
||||
currentLevelData.fallbackRegionId);
|
||||
currentLevelData.fallbackRegionId = 0;
|
||||
}
|
||||
|
||||
if (!regionList.ContainsKey(currentLevelData.enemyFallbackRegionId))
|
||||
{
|
||||
errorMessage += string.Format(enemyFallbackRegionErrorMessage, currentLevelData.mapName, currentLevelData.enemyFallbackRegionId);
|
||||
errorMessage += string.Format(enemyFallbackRegionErrorMessage, currentLevelData.mapName,
|
||||
currentLevelData.enemyFallbackRegionId);
|
||||
currentLevelData.enemyFallbackRegionId = 0;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrEmpty(errorMessage))
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
CustomPopUpWindow.PopUp(errorMessage + fixedMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
TerrainTypeConfig.Load();
|
||||
_npcDic = new Dictionary<Vector2Int, GameObject>();
|
||||
base.OnEnable();
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +239,8 @@ public class LevelEditorWindow : OdinEditorWindow
|
|||
thisWindow = null;
|
||||
MapEditorUtils.UnLoadMap(EditorMap.Instance);
|
||||
DestroyImmediate(GameObject.Find("Main Camera"));
|
||||
DestroyNpcPrefab();
|
||||
_npcDic = null;
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
|
|
@ -187,6 +259,7 @@ public class LevelEditorWindow : OdinEditorWindow
|
|||
}
|
||||
|
||||
private bool banSaveLevelList = false;
|
||||
|
||||
private void OnLevelListChanged()
|
||||
{
|
||||
if (!banSaveLevelList)
|
||||
|
|
@ -199,21 +272,23 @@ public class LevelEditorWindow : OdinEditorWindow
|
|||
Debug.Log($"{GetType()}.New Level declared : {levelPresentation.levelId}");
|
||||
}
|
||||
}
|
||||
|
||||
SaveLevelList();
|
||||
Debug.Log($"{GetType()}.Save Level List");
|
||||
banSaveLevelList = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void OnCurrentLevelDataChanged()
|
||||
{
|
||||
Debug.Log($"{GetType()}.OnCurrentLevelDataChanged");
|
||||
}
|
||||
|
||||
|
||||
private void SaveLevelList()
|
||||
{
|
||||
var configFilePath = string.Format(Constants.LEVEL_LIST_PATH);
|
||||
// VersionControlSystem.Checkout(configFilePath);
|
||||
JsonHelper.SaveJson(configFilePath, LevelList);
|
||||
}
|
||||
|
||||
|
|
@ -222,21 +297,109 @@ public class LevelEditorWindow : OdinEditorWindow
|
|||
|
||||
private async void LoadLevel(string id)
|
||||
{
|
||||
DestroyNpcPrefab();
|
||||
_npcDic.Clear();
|
||||
|
||||
var configFilePath = string.Format(Constants.LEVEL_CONFIG_FORMAT_PATH, id);
|
||||
currentLevelData = JsonHelper.LoadJson<LevelData>(configFilePath);
|
||||
|
||||
//加载地区信息
|
||||
curRegionData = new RegionData();
|
||||
curRegionData.preparingRegionId = currentLevelData.preparingRegionId;
|
||||
curRegionData.fallbackRegionId = currentLevelData.fallbackRegionId;
|
||||
curRegionData.enemyFallbackRegionId = currentLevelData.enemyFallbackRegionId;
|
||||
curRegionData.preparingRegionToward = currentLevelData.preparingRegionToward;
|
||||
|
||||
if (!string.IsNullOrEmpty(currentLevelData.mapName))
|
||||
{
|
||||
EditorMap.Instance.Current = await MapEditorUtils.LoadMap(currentLevelData.mapName);
|
||||
|
||||
_curMap = EditorMap.Instance.Current;
|
||||
|
||||
// 显示在关卡编辑器中的地图资产
|
||||
mapPrefab = await MapUtils.LoadMapAsset(currentLevelData.mapName);
|
||||
|
||||
_curMap.TerrainGridSystem.OnMouseClickOnGrid += OnCellClick;
|
||||
}
|
||||
else
|
||||
{
|
||||
MapEditorUtils.UnLoadMap(EditorMap.Instance);
|
||||
mapPrefab = null;
|
||||
}
|
||||
|
||||
//加载NPC到场景中
|
||||
CurrentNpcInfo = new List<LevelData.NPCInfo>();
|
||||
foreach (var npcInfo in currentLevelData.npcInfos)
|
||||
{
|
||||
CurrentNpcInfo.Add(npcInfo);
|
||||
|
||||
string npcId = string.Format("P_E{0}", npcInfo.id.ToString().Substring(1));
|
||||
var npc = AssetDatabase.LoadAssetAtPath<GameObject>(string.Format(Constants.LEVEL_NPC_PATH, npcId));
|
||||
var position = _curMap.BlockPosition2WorldPosition(npcInfo.position);
|
||||
_npcDic.Add(npcInfo.position, Instantiate(npc, position, npc.transform.rotation));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCellClick(TerrainGridSystem tgs, int cellindex, int buttonindex)
|
||||
{
|
||||
if (!canAddNpc) return;
|
||||
|
||||
|
||||
var clickPosition = _curMap.TGSCellIndex2BlockPosition(cellindex);
|
||||
|
||||
int leftButton = 0;
|
||||
int rightButton = 1;
|
||||
|
||||
if (canAddNpc && buttonindex == leftButton)
|
||||
{
|
||||
if (npcObject == null)
|
||||
{
|
||||
DebugUtil.LogError("请选择正确的NPC!!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_npcDic.ContainsKey(clickPosition))
|
||||
{
|
||||
DebugUtil.LogError("该位置已有NPC存在,请重新选!!!");
|
||||
return;
|
||||
}
|
||||
|
||||
LevelData.NPCInfo npcInfo = new LevelData.NPCInfo();
|
||||
string npcId = npcObject.name.Replace("P_E", "1");
|
||||
|
||||
npcInfo.id = int.Parse(npcId);
|
||||
npcInfo.position = clickPosition;
|
||||
|
||||
//默认等级为1
|
||||
npcInfo.level = 1;
|
||||
CurrentNpcInfo.Add(npcInfo);
|
||||
var position = _curMap.BlockPosition2WorldPosition(npcInfo.position);
|
||||
_npcDic.Add(npcInfo.position, Instantiate(npcObject, position, npcObject.transform.rotation));
|
||||
DebugUtil.Log("添加了[{0}]NPC到[ {1} ]位置上", npcObject.name, npcInfo.position);
|
||||
}
|
||||
else if(canAddNpc && buttonindex == rightButton)
|
||||
{
|
||||
if (_npcDic.TryGetValue(clickPosition,out var npcObj))
|
||||
{
|
||||
var npc = CurrentNpcInfo.FirstOrDefault(npc => npc.position == clickPosition);
|
||||
if (npc != null)
|
||||
{
|
||||
CurrentNpcInfo.Remove(npc);
|
||||
DebugUtil.Log("从[ {0} ]位置上移除了[{1}]NPC", clickPosition, npcObj.name);
|
||||
DestroyImmediate(npcObj);
|
||||
_npcDic.Remove(clickPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void DestroyNpcPrefab()
|
||||
{
|
||||
foreach (var npc in _npcDic)
|
||||
{
|
||||
DestroyImmediate(npc.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue