Unity [ExecuteAlways]特性首次编辑模式不生效问题排查
问题相关信息
- 开发环境:Unity 2021.3.2f1 Personal 版本
- 问题复现代码:
using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; [ExecuteAlways] public class DrawOnBoxCollider : MonoBehaviour { public enum DrawStates { DrawCube, DrawWireCube } public Transform boxColliderToDrawOn; [Range(0f, 1f)] public float colorTransparency; public bool draw = false; public DrawStates drawStates; private BoxCollider boxCollider = null; // Start is called before the first frame update void Start() { if (boxColliderToDrawOn != null) { boxCollider = boxColliderToDrawOn.GetComponent<BoxCollider>(); } drawStates = DrawStates.DrawCube; } // Update is called once per frame void Update() { } private void DrawGizmosOnRunTime(Color color) { if (boxCollider != null && draw) { Gizmos.color = color; Matrix4x4 rotationMatrix = Matrix4x4.TRS(boxCollider.transform.position, boxCollider.transform.rotation, boxCollider.transform.lossyScale); Gizmos.matrix = rotationMatrix; switch (drawStates) { case DrawStates.DrawCube: Gizmos.DrawCube(boxCollider.center, boxCollider.size); break; case DrawStates.DrawWireCube: Gizmos.DrawWireCube(boxCollider.center, boxCollider.size); break; } } } private void OnDrawGizmos() { Color c = Color.red; c.a = colorTransparency; DrawGizmosOnRunTime(c); } }
- 异常现象:
- 脚本添加
[ExecuteAlways]特性后,首次打开工程时BoxCollider上无任何Gizmos绘制内容 - 必须先进入运行模式,碰撞体上才会正常显示彩色绘制效果,退出运行模式回到编辑模式后,绘制效果也可正常显示
- 断点调试确认:无论是否添加
[ExecuteAlways]特性,OnDrawGizmos()方法内的代码都会执行,但首次编辑模式下始终无绘制内容,仅在运行一次游戏后编辑模式下的绘制才会正常生效,需要确认该现象是否为当前Unity版本的Bug。
- 脚本添加
结论
这个现象不是Unity 2021.3.2f1版本的Bug,是代码生命周期逻辑存在缺陷导致的。
原因说明
[ExecuteAlways]特性下的脚本,Start()方法在编辑模式的触发时机并不可靠:首次打开工程加载场景时,OnDrawGizmos会先于编辑模式下的Start()执行,此时用来缓存碰撞体的私有变量boxCollider还是初始值null,绘制逻辑里的判断条件if (boxCollider != null && draw)不成立,自然不会渲染任何内容。
进入运行模式时,Play模式下的Start()会被强制触发,boxCollider拿到有效引用,退出运行模式后这个缓存的引用不会被清空,所以回到编辑模式绘制就恢复正常了。
另外代码里在Start()中强制给drawStates赋值为DrawCube,会覆盖用户在Inspector中设置的绘制模式,也属于不合理的逻辑写法。
修复方案
- 把引用获取、初始化逻辑移到
OnEnable()中,[ExecuteAlways]下的OnEnable()在编辑模式域加载、脚本编译、打开工程时都会可靠触发 - 在
OnDrawGizmos中加一层兜底判断,如果boxCollider为空就重新尝试获取,避免因为引用丢失导致绘制失效 - 去掉
Start()中强制覆盖drawStates的逻辑,直接给字段设置默认值即可,不要随意覆盖用户在Inspector中的配置 - 绘制完成后重置Gizmos矩阵,避免修改全局Gizmos状态影响场景内其他Gizmos的渲染
修复后的核心代码参考:
using UnityEngine; [ExecuteAlways] public class DrawOnBoxCollider : MonoBehaviour { public enum DrawStates { DrawCube, DrawWireCube } public Transform boxColliderToDrawOn; [Range(0f, 1f)] public float colorTransparency = 0.5f; public bool draw = true; // 直接给字段设默认值,不要在生命周期方法里强制覆盖用户配置 public DrawStates drawStates = DrawStates.DrawCube; private BoxCollider boxCollider = null; private void OnEnable() { // OnEnable在编辑模式下触发时机可靠,在这里初始化引用 if (boxColliderToDrawOn != null) { boxCollider = boxColliderToDrawOn.GetComponent<BoxCollider>(); } } private void DrawGizmos(Color color) { // 兜底判断,引用丢失时重新获取 if (boxCollider == null && boxColliderToDrawOn != null) { boxCollider = boxColliderToDrawOn.GetComponent<BoxCollider>(); } if (boxCollider != null && draw) { Gizmos.color = color; Matrix4x4 rotationMatrix = Matrix4x4.TRS(boxCollider.transform.position, boxCollider.transform.rotation, boxCollider.transform.lossyScale); Gizmos.matrix = rotationMatrix; switch (drawStates) { case DrawStates.DrawCube: Gizmos.DrawCube(boxCollider.center, boxCollider.size); break; case DrawStates.DrawWireCube: Gizmos.DrawWireCube(boxCollider.center, boxCollider.size); break; } // 绘制完重置Gizmos矩阵,避免影响其他Gizmos绘制 Gizmos.matrix = Matrix4x4.identity; } } private void OnDrawGizmos() { Color c = Color.red; c.a = colorTransparency; DrawGizmos(c); } }
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

