当前位置: 首页 > article >正文

在Scene里面绘制编辑工具

功能要求
  • 策划要在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;

//[Serializable]
//class EditContext
//{
//    public int currLayMode;
//    public int currSelectRank;
//    public Vector2 scrollPos;
//    public string levelName;
//    public int mode;
//    public bool isNewFile;
//    public List<string> dataNames = new List<string>();
//    public List<string> levelTools = new List<string>();
//}
[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;// { get { return editContext.currLayMode; } set { editContext.currLayMode = value; } }
    [HideInInspector] public int currSelectRank;// { get { return editContext.currSelectRank; } set { editContext.currSelectRank = value; } }
    [HideInInspector] public int mode;//{ get { return editContext.mode; } set { editContext.mode = value; } }
    [HideInInspector] public Vector2 scrollPos;// { get { return editContext.scrollPos; } set { editContext.scrollPos = value; } }
    [HideInInspector] public string levelName;// { get { return editContext.levelName; } set { editContext.levelName = value; } }
    [HideInInspector] public bool isNewFile;//{ get { return editContext.isNewFile; } set { editContext.isNewFile = value; } }
    #endregion

    [HideInInspector] public SpecialLevelData currLevelData;

    GUILayoutOption widthSetting => GUILayout.Width(120);
    GUILayoutOption heightSetting => GUILayout.Height(30);
    //Dictionary<string, path> allData = new Dictionary<string, SpecialLevelData>();
    List<string> dataNames = new List<string>();//=> editContext.dataNames;
    List<string> levelTools = new List<string>();//=> editContext.levelTools;


    private void Start()
    {
    }

    private void OnDestroy()
    {
    }

    // EditContext editContext;
    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;
        //if(editContext != null)
        //{
        //    string projectPath = Application.dataPath;
        //    string filePath = Path.Combine(projectPath, "TempEditData.txt");
        //    File.WriteAllText(filePath, JsonUtility.ToJson(editContext));
        //    editContext = null;
        //}
        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;

        // 获取文件夹中的所有资源的 GUID(包括子文件夹)
        string[] guids = AssetDatabase.FindAssets("", new[] { folderPath });

        // 遍历 GUID,并打印出文件路径和文件名
        foreach (string guid in guids)
        {
            // 将 GUID 转换为资源路径
            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 绘制一个按钮
        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));
        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;
            //            var prefabPath = GetPath(levelName);
            //#if UNITY_EDITOR
            //           
            //#endif
        }

        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);  // UI 的宽高

        Rect rect = new Rect(
            windowSize.x - size.x - padding.x, // X 坐标:窗口宽度 - UI 宽度 - 边距
            padding.y,                         // Y 坐标:直接使用顶部边距
            size.x,
            size.y
        );

        // 绘制背景框
        GUI.Box(rect, GUIContent.none);

        // 使用 GUILayout 自动布局内部内容
        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)
    {
        //   lineRenderer.gameObject.SetActive(false);

        //那就进行一个移除
        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;


        // Debug.Log($"{realX}_{realY}");
        var temp = new Vector2(realX + originPosX, realY + originPosY);
        //判断完成之后再转回世界坐标
        var pos = SpriteRenderer.transform.TransformPoint(temp);
        return pos;
    }

    void DrawRightBottom(SceneView sceneView)
    {
        // 计算 UI 的位置(右下角)
        int width = 120; // UI 宽度
        int height = 180; // UI 高度
        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();
        }
        // DrawSpecialTool();
        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.");
            }


        }

    }
}

http://www.kler.cn/a/523305.html

相关文章:

  • java基础-容器
  • [蓝桥杯 2014 省 AB] 蚂蚁感冒
  • Linux C++
  • neo4j-community-5.26.0 install in window10
  • 使用 Redis 实现分布式锁的基本思路
  • LangGraph系列-1:用LangGraph构建简单聊天机器人
  • 双指针(典型算法思想)——OJ例题算法解析思路
  • 05_任务的删除
  • 【第十天】零基础入门刷题Python-算法篇-数据结构与算法的介绍-两种常见的字符串算法(持续更新)
  • JavaScript系列(48)-- 3D渲染引擎实现详解
  • week08_文本匹配任务
  • 嵌入式知识点总结 Linux驱动 (一)-指令-常用Linux指令 GCC指令 GDB调试指令 驱动开发指令
  • 个人通知~~~
  • 【愚公系列】《循序渐进Vue.js 3.x前端开发实践》030-自定义组件的插槽Mixin
  • Julius AI 人工智能数据分析工具介绍
  • Ubuntu20.04 磁盘空间扩展教程
  • 安卓入门四十三 转场动画
  • LSQL导入器的使用教程-保姆级
  • 中国现代篆刻
  • 全面解析文件上传下载删除漏洞:风险与应对
  • OpenBMC:编译
  • (2023 RESS ) Federated multi-source domain adversarial adaptation framework
  • C++中类成员的访问权限
  • 网络管理功能实现:从协议到工程实践
  • C++ Lambda 表达式的本质及原理分析
  • 大话特征工程:2.特征组合与描述