Unity 2017.3:编辑器预览脚本生成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

