You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity 2017.3:编辑器预览脚本生成UI的重复创建问题咨询

解决Unity编辑器预览动态生成UI时的重复创建问题

这个问题我太熟悉了——用[ExecuteInEditMode]确实能让你在编辑器里看到动态生成的UI,但Awake方法在编辑模式激活、切换到播放模式时都会触发,没做判断的话自然会重复创建元素。下面给你几个实用的解决方案:

方案1:添加存在性检查,避免重复创建

最直接的办法是在创建UI前,先检查目标元素是否已经存在。如果已经有了,就直接复用,不用重新生成。修改你的代码如下:

[ExecuteInEditMode]
public class InGameCanvas : MonoBehaviour {
    protected InGameCanvas() { }
    private Canvas canvas;
    private Text balanceText; // 保存引用,方便后续更新

    private void Awake() {
        if ((canvas = gameObject.GetComponent<Canvas>()) == null) {
            Debug.LogError("InGameCanvas script not connected to a Canvas.");
            return;
        }

        // 先查找是否已经存在BalanceText子对象
        var balanceTextObject = transform.Find("BalanceText");
        if (balanceTextObject != null) {
            balanceText = balanceTextObject.GetComponent<Text>();
            return; // 已经存在,直接返回
        }

        // 不存在才执行创建逻辑
        balanceTextObject = new GameObject("BalanceText");
        balanceTextObject.transform.SetParent(gameObject.transform);
        balanceText = balanceTextObject.AddComponent<Text>();
        var balanceTextTransform = balanceTextObject.GetComponent<RectTransform>();
        
        balanceText.text = "Balance: 0";
        balanceText.font = Defaults.consolasFont;
        balanceTextTransform.sizeDelta = new Vector2(100, 20);
        balanceTextTransform.pivot = new Vector2(0, 1);
        balanceTextTransform.anchorMin = new Vector2(0, 1);
        balanceTextTransform.anchorMax = new Vector2(0, 1);
        balanceTextTransform.anchoredPosition = new Vector2(10, -10);
    }
}

这样不管是编辑模式还是播放模式,只要BalanceText已经存在,就不会重复创建。如果后续需要更新文本内容,直接用保存的balanceText引用即可。

方案2:区分编辑/运行模式,针对性处理

进入播放模式时,Unity会把编辑器里的对象复制一份到运行环境,这时候Awake再次触发会导致两份UI。你可以用Application.isPlaying区分模式,分别处理:

[ExecuteInEditMode]
public class InGameCanvas : MonoBehaviour {
    protected InGameCanvas() { }
    private Canvas canvas;
    private Text balanceText;

    private void Awake() {
        if ((canvas = gameObject.GetComponent<Canvas>()) == null) {
            Debug.LogError("InGameCanvas script not connected to a Canvas.");
            return;
        }

        if (!Application.isPlaying) {
            // 编辑模式:只在不存在时创建
            var existing = transform.Find("BalanceText");
            if (existing == null) {
                CreateBalanceText();
            } else {
                balanceText = existing.GetComponent<Text>();
            }
        } else {
            // 播放模式:先清理编辑器复制过来的旧UI,再创建新的
            var existing = transform.Find("BalanceText");
            if (existing != null) {
                Destroy(existing.gameObject);
            }
            CreateBalanceText();
        }
    }

    // 把创建逻辑封装成单独方法,代码更整洁
    private void CreateBalanceText() {
        var balanceTextObject = new GameObject("BalanceText");
        balanceTextObject.transform.SetParent(gameObject.transform);
        balanceText = balanceTextObject.AddComponent<Text>();
        var balanceTextTransform = balanceTextObject.GetComponent<RectTransform>();
        
        balanceText.text = "Balance: 0";
        balanceText.font = Defaults.consolasFont;
        balanceTextTransform.sizeDelta = new Vector2(100, 20);
        balanceTextTransform.pivot = new Vector2(0, 1);
        balanceTextTransform.anchorMin = new Vector2(0, 1);
        balanceTextTransform.anchorMax = new Vector2(0, 1);
        balanceTextTransform.anchoredPosition = new Vector2(10, -10);
    }
}

这个方案能确保编辑模式下的预览UI不会干扰播放模式的正常运行,同时播放模式下始终有干净的UI实例。

方案3:自定义编辑器,手动触发预览

如果不想让UI自动生成(比如频繁触发影响编辑器性能),可以做一个自定义编辑器,让你手动点击按钮生成预览UI,可控性更强:

首先写编辑器脚本(放在Editor文件夹下):

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(InGameCanvas))]
public class InGameCanvasEditor : Editor {
    public override void OnInspectorGUI() {
        // 先显示默认的Inspector面板
        DrawDefaultInspector();
        
        InGameCanvas targetCanvas = (InGameCanvas)target;
        
        // 添加自定义按钮
        if (GUILayout.Button("生成预览UI")) {
            // 先清理已有的预览UI,再重新生成
            var existing = targetCanvas.transform.Find("BalanceText");
            if (existing != null) {
                DestroyImmediate(existing.gameObject);
            }
            targetCanvas.CreateBalanceText();
        }
    }
}

然后修改原脚本,把创建方法设为public(或者internal):

[ExecuteInEditMode]
public class InGameCanvas : MonoBehaviour {
    protected InGameCanvas() { }
    private Canvas canvas;
    private Text balanceText;

    private void Awake() {
        if ((canvas = gameObject.GetComponent<Canvas>()) == null) {
            Debug.LogError("InGameCanvas script not connected to a Canvas.");
            return;
        }

        // 播放模式下自动创建UI
        if (Application.isPlaying) {
            CreateBalanceText();
        }
    }

    // 改为public,让编辑器脚本可以调用
    public void CreateBalanceText() {
        var balanceTextObject = new GameObject("BalanceText");
        balanceTextObject.transform.SetParent(gameObject.transform);
        balanceText = balanceTextObject.AddComponent<Text>();
        var balanceTextTransform = balanceTextObject.GetComponent<RectTransform>();
        
        balanceText.text = "Balance: 0";
        balanceText.font = Defaults.consolasFont;
        balanceTextTransform.sizeDelta = new Vector2(100, 20);
        balanceTextTransform.pivot = new Vector2(0, 1);
        balanceTextTransform.anchorMin = new Vector2(0, 1);
        balanceTextTransform.anchorMax = new Vector2(0, 1);
        balanceTextTransform.anchoredPosition = new Vector2(10, -10);
    }
}

这样你可以在Inspector面板里手动点击按钮生成预览,编辑模式下不会自动触发,完全由你控制,还能随时重新生成最新的UI效果。

以上三个方案可以根据你的需求选择:如果需要自动预览,方案1或2足够;如果希望更灵活的控制,方案3是最佳选择。其他脚本生成对象的问题,也可以用同样的思路解决——核心就是检查对象是否存在或者区分模式处理。

内容的提问来源于stack exchange,提问作者Maroš Beťko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:51:09