功能要求
- 策划要在scene模式下编辑棋子摆放。
- 用handle.GUI绘制来解决了。
问题
- 在scene模式下编辑产生的数据,进入游戏模式后就全不见了。
- 改为executeAlways也没用。
- 我的解决办法是把编辑数据序列化保存到本地。
- 在OnEnable的时候再读取。
- 但是我忽然想到,直接把这些数据暴露在Inspector面板上,让unity帮我来序列化不就好了。这样就实现scene和play的数据通用,不会丢失数据了。
- 第二个事情
- 就是Scene模式下进行位置判断也很麻烦。因为他没有play模式下的那个相机。
- 所以我这边是用射线检测来做的。
- 第三个事情
- 按键检测也很麻烦。和play模式下不一样
- 第四事情
- 按键必须要在合适的时机进行Use,不然事件会传递给unity的工具本身。
- 下面是所有代码
using OfficeOpenXml.FormulaParsing.Excel.Functions.Math;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Pool;
using static PieceEditor;
using static UnityEngine.GridBrushBase;
[ExecuteInEditMode]
public class SpecialLevelEditor : MonoBehaviour
{
public List<Color> pieceColor;
public SpriteRenderer SpriteRenderer;
public Piece LayItemPrefab;
public Transform PoolObj;
Dictionary<Vector2Int, Piece> allPieces = new Dictionary<Vector2Int, Piece>();
int currWidth => currLevelData.currWidth;
int currHeight => currLevelData.currHeight;
#region Property
[HideInInspector] public int currLayMode;
[HideInInspector] public int currSelectRank;
[HideInInspector] public int mode;
[HideInInspector] public Vector2 scrollPos;
[HideInInspector] public string levelName;
[HideInInspector] public bool isNewFile;
#endregion
[HideInInspector] public SpecialLevelData currLevelData;
GUILayoutOption widthSetting => GUILayout.Width(120);
GUILayoutOption heightSetting => GUILayout.Height(30);
List<string> dataNames = new List<string>();
List<string> levelTools = new List<string>();
private void Start()
{
}
private void OnDestroy()
{
}
private void OnEnable()
{
SpriteRenderer.enabled = !Application.isPlaying;
allPieces.Clear();
if (currLevelData != null)
{
var allData = currLevelData.GetComponentsInChildren<Piece>();
foreach (var item in allData)
{
allPieces.Add(item.PieceData.LayPos, item);
}
}
dataNames.Clear();
levelTools.Clear();
ReadAllData(dataNames, "Assets/Resources/ShowLevels");
ReadAllData(levelTools, "Assets/Resources/LevelTool");
Debug.Log("编辑器启用");
SceneView.duringSceneGui += DrawSceneGUI;
pool = new ObjectPool<Piece>(CreateFunc, OnGet, OnRelease);
}
private void OnDisable()
{
SceneView.duringSceneGui -= DrawSceneGUI;
Debug.Log("禁用编辑器");
}
string GetPath(string prefabName)
{
return $"Assets/Resources/ShowLevels/{prefabName}.prefab";
}
string GetToolPath(string prefabName)
{
return $"Assets/Resources/LevelTool/{prefabName}.prefab";
}
void ReadAllData(List<string> container, string folder)
{
string folderPath = folder;
string[] guids = AssetDatabase.FindAssets("", new[] { folderPath });
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
string fileName = System.IO.Path.GetFileNameWithoutExtension(path);
container.Add(fileName);
}
}
void SetColliderSize()
{
SpriteRenderer.GetComponent<BoxCollider>().size = new Vector3(currWidth, currHeight, 1);
}
private void DrawSceneGUI(SceneView sceneView)
{
Handles.BeginGUI();
GUILayout.BeginVertical();
DrawFile();
DrawLevelData(sceneView);
GUILayout.EndVertical();
DrawRightBottom(sceneView);
DrawRightUp(sceneView);
if (mode == 1)
{
Selection.activeObject = null;
}
Handles.EndGUI();
}
void DrawLine()
{
GUI.color = Color.red;
GUILayout.Button("", GUILayout.Width(125), GUILayout.Height(5));
GUI.color = Color.white;
}
void InstBorder(int index, Vector3 pos, Vector3 scale)
{
GameObject obj = null;
BoxCollider collider = null;
if (currLevelData.BoxColliders == null || currLevelData.BoxColliders.Count < index + 1)
{
obj = new GameObject();
collider = obj.AddComponent<BoxCollider>();
currLevelData.BoxColliders.Add(collider);
}
else
{
collider = currLevelData.BoxColliders[index];
obj = collider.gameObject;
}
if (index == 2 || index == 3)
{
obj.tag = UIManager.Ground;
}
obj.transform.position = pos;
obj.transform.localScale = scale;
obj.transform.SetParent(currLevelData.transform);
}
void AddBorder()
{
InstBorder(0, new Vector3(currWidth / 2f, 0, 0), new Vector3(0.1f, currHeight, 1));
InstBorder(1, new Vector3(-currWidth / 2f, 0, 0), new Vector3(0.1f, currHeight, 1));
InstBorder(2, new Vector3(0, -currHeight / 2f, 0), new Vector3(currWidth, 0.1f, 1));
}
void Save()
{
if (currLevelData != null)
{
AddBorder();
using (ListPool<Vector2Int>.Get(out var keys))
{
keys.AddRange(allPieces.Keys);
foreach (var item in keys)
{
if (allPieces[item] == null)
{
Debug.Log("检测到丢失");
allPieces.Remove(item);
}
}
}
currLevelData.Pieces.Clear();
foreach (var item in allPieces)
{
currLevelData.Pieces.Add(item.Value.PieceData);
}
ResetPiece();
if (isNewFile)
{
dataNames.Add(levelName);
PrefabUtility.SaveAsPrefabAsset(currLevelData.gameObject, GetPath(levelName));
}
else
{
PrefabUtility.ApplyPrefabInstance(currLevelData.gameObject, InteractionMode.UserAction);
}
ClearObj();
}
}
void ClearObj()
{
ResetPiece();
DestroyImmediate(currLevelData.gameObject);
currLevelData = null;
levelName = null;
allPieces.Clear();
}
void DrawLevelData(SceneView sceneView)
{
if (currLevelData != null)
{
GUILayout.BeginHorizontal(widthSetting, heightSetting);
GUILayout.Button("宽");
var val = EditorGUILayout.IntField(currWidth);
if (currLevelData.currWidth != val)
{
currLevelData.currWidth = val;
ResetPiece();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(widthSetting, heightSetting);
GUILayout.Button("高");
var hVal = EditorGUILayout.IntField(currHeight);
if (currLevelData.currHeight != hVal)
{
currLevelData.currHeight = hVal;
ResetPiece();
}
GUILayout.EndHorizontal();
SetColliderSize();
SpriteRenderer.size = new Vector2(currWidth, currHeight);
HandleClick(sceneView);
foreach (var item in levelTools)
{
if (GUILayout.Button(item, widthSetting, heightSetting))
{
var obj = PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>(GetToolPath(item))) as GameObject;
obj.transform.parent = currLevelData.transform;
obj.transform.position = Vector3.zero;
Selection.activeGameObject = obj;
mode = 0;
}
}
}
}
public void ResetPiece()
{
foreach (var item in allPieces)
{
pool.Release(item.Value);
}
allPieces.Clear();
}
void DrawFile()
{
scrollPos = GUILayout.BeginScrollView(scrollPos, widthSetting, GUILayout.Height(200));
for (int i = dataNames.Count - 1; i >= 0; i--)
{
var item = dataNames[i];
GUILayout.BeginHorizontal();
if (currLevelData != null && currLevelData.name == item)
{
GUI.backgroundColor = Color.green;
}
if (GUILayout.Button(item))
{
if (currLevelData != null)
{
if (item == currLevelData.name)
{
return;
}
else
{
ClearObj();
}
}
levelName = item;
isNewFile = false;
var obj = PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>(GetPath(item))) as GameObject;
currLevelData = obj.GetComponent<SpecialLevelData>();
var childs = currLevelData.Pieces;
foreach (var child in childs)
{
InnerLay(child);
}
}
GUI.backgroundColor = Color.white;
if (GUILayout.Button("删除"))
{
if (item == levelName)
{
ClearObj();
}
bool success = AssetDatabase.DeleteAsset(GetPath(item));
if (success)
{
Debug.Log($"Prefab at {item} deleted successfully.");
dataNames.Remove(item);
}
else
{
Debug.LogError($"Failed to delete prefab at {item}.");
}
}
GUILayout.EndHorizontal();
}
GUILayout.EndScrollView();
levelName = GUILayout.TextField(levelName, widthSetting, heightSetting);
GUILayout.BeginHorizontal(widthSetting, heightSetting);
if (GUILayout.Button("新建文件"))
{
if (string.IsNullOrEmpty(levelName)) return;
Save();
var obj = new GameObject();
currLevelData = obj.AddComponent<SpecialLevelData>();
obj.name = levelName;
isNewFile = true;
}
if (GUILayout.Button("保存文件"))
{
Save();
}
GUILayout.EndHorizontal();
}
void DrawRightUp(SceneView sceneView)
{
Vector2 windowSize = new Vector2(sceneView.position.width, sceneView.position.height);
Vector2 padding = new Vector2(10, 10);
Vector2 size = new Vector2(150, 40 * 9);
Rect rect = new Rect(
windowSize.x - size.x - padding.x,
padding.y,
size.x,
size.y
);
GUI.Box(rect, GUIContent.none);
GUILayout.BeginArea(rect);
GUILayout.Label("数字棋子", EditorStyles.boldLabel);
currSelectRank = GUILayout.SelectionGrid(currSelectRank, new string[] { "1", "2", "3", "4", "5", "6", "7", "8", "9" }, 1, GUILayout.Width(120));
GUILayout.Space(20);
currLayMode = GUILayout.SelectionGrid(currLayMode, new string[] { "正常棋子", "墙壁", "冰封", }, 1, GUILayout.Width(120));
GUILayout.EndArea();
}
public void DestroyItem(Vector2Int pos)
{
var curSlot = allPieces[pos];
allPieces.Remove(pos);
pool.Release(curSlot);
}
void InnerLay(PieceData data)
{
var normalize = data.LayPos;
if (allPieces.ContainsKey(normalize))
{
var curSlot = allPieces[normalize];
allPieces.Remove(normalize);
pool.Release(curSlot);
}
var obj = pool.Get();
obj.transform.SetParent(currLevelData.transform);
obj.transform.position = TransferCenterPos(data.LayPos);
obj.SetRankEditor(data);
allPieces.Add(data.LayPos, obj);
}
public void SpecialLay(PieceData data)
{
switch ((LayMode)currLayMode)
{
case LayMode.NormalPiece:
if (currSelectRank == null) return;
data.PieceType = PieceType.Piece;
InnerLay(data);
break;
case LayMode.Wall:
data.PieceType = PieceType.Wall;
InnerLay(data);
break;
case LayMode.Ice:
if (allPieces.ContainsKey(data.LayPos))
{
var piece = allPieces[data.LayPos];
if (piece.PieceData.PieceType != PieceType.Wall)
{
allPieces[data.LayPos].SetIceState();
}
}
break;
default:
break;
}
}
#region 对象池
ObjectPool<Piece> pool;
Piece CreateFunc()
{
var obj = GameObject.Instantiate<Piece>(LayItemPrefab, PoolObj);
return obj;
}
void OnRelease(Piece obj)
{
obj.transform.parent = PoolObj;
obj.gameObject.SetActive(false);
}
void OnGet(Piece obj)
{
obj.gameObject.SetActive(true);
}
#endregion
public bool Transfer(Vector2 clickPos, out Vector2Int normalize)
{
normalize = Vector2Int.zero;
var originPosX = -(float)currWidth / 2;
var originPosY = -(float)currHeight / 2;
var localPos = SpriteRenderer.transform.InverseTransformPoint(clickPos);
var offsetX = localPos.x - originPosX;
var offsetY = localPos.y - originPosY;
var cellX = Mathf.FloorToInt(offsetX);
var cellY = Mathf.FloorToInt(offsetY);
var realX = cellX + 0.5f;
var realY = cellY + 0.5f;
if (cellX >= currWidth || cellX < 0) { return false; }
if (cellY >= currHeight || cellY < 0) { return false; }
normalize = new Vector2Int(cellX, cellY);
return true;
}
public Vector3 TransferCenterPos(Vector2Int centerPos)
{
var originPosX = -(float)currWidth / 2;
var originPosY = -(float)currHeight / 2;
var realX = centerPos.x + 0.5f;
var realY = centerPos.y + 0.5f;
var temp = new Vector2(realX + originPosX, realY + originPosY);
var pos = SpriteRenderer.transform.TransformPoint(temp);
return pos;
}
void DrawRightBottom(SceneView sceneView)
{
int width = 120;
int height = 180;
int padding = 20;
Rect rect = new Rect(
sceneView.position.width - width - padding,
sceneView.position.height - height - padding,
width,
height
);
GUI.Box(rect, GUIContent.none);
GUILayout.BeginArea(rect);
GUILayout.Label("模式转换", EditorStyles.boldLabel);
var val = GUILayout.SelectionGrid(mode, new string[] { "自由模式", "棋子模式", }, 1, widthSetting, GUILayout.Height(60));
mode = val;
if (GUILayout.Button("清空棋盘"))
{
ClearObj();
}
GUILayout.EndArea();
}
private void HandleClick(SceneView sceneView)
{
if (mode != 1) return;
Event e = Event.current;
if (e.type == EventType.MouseDown && e.button == 0)
{
Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
if (hit.collider.gameObject != SpriteRenderer.gameObject) { return; }
Debug.Log("Clicked on object at position: " + hit.point);
if (Transfer(hit.point, out var normalize))
{
var data = new PieceData() { LayPos = normalize, Rank = currSelectRank + 1 };
SpecialLay(data);
Selection.activeGameObject = null;
e.Use();
}
}
else
{
Debug.Log("No hit detected.");
}
}
if (e.type == EventType.MouseDown && e.button == 1)
{
Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
if (hit.collider.gameObject == SpriteRenderer.gameObject)
{
Debug.Log("Clicked on object at position: " + hit.point);
if (Transfer(hit.point, out var normalize))
{
if (allPieces.ContainsKey(normalize))
{
DestroyItem(normalize);
e.Use();
}
}
}
}
else
{
Debug.Log("No hit detected.");
}
}
}
}