如何在Unity非运行时通过代码自动检查并创建丢失的GameObject
编辑器下自动恢复必要场景对象的实现方案
原实现代码存在的硬伤
- 类定义错误:
[InitializeOnLoad]修饰的是纯编辑器静态工具类,完全不需要继承MonoBehaviour。原写法会引入不必要的运行时类依赖,甚至可能因为类的序列化逻辑在打包后残留冗余代码,违反“自动恢复逻辑绝不参与游戏运行”的要求。 - 对象查找失效:
GameObject.Find只能检索场景中处于激活状态的对象,失活状态的同名必要对象会被误判为丢失,触发重复创建。 - 无性能防护:
EditorApplication.hierarchyChanged事件在Hierarchy面板的每一次操作(拖动物体、改名字、改组件参数)时都会触发,没有节流逻辑会导致全场景遍历频繁执行,拖慢编辑器响应速度。 - 创建逻辑缺失:仅创建空的同名GameObject,没有挂载
ObjectsGenerator、PlayerData等必要业务脚本,等于做了无用功。 - 无运行时隔离:没有判断当前编辑器是否处于Play模式,很容易在游戏运行过程中误触发创建逻辑。
修正后可直接使用的实现
所有逻辑通过编译宏完全隔离在编辑器环境内,不会被打包进游戏包体:
#if UNITY_EDITOR using System.Linq; using UnityEditor; using UnityEngine; // 编辑器启动时自动初始化的静态校验类,无任何运行时依赖 [InitializeOnLoad] public static class RequiredSceneObjectChecker { // 封装必要对象的配置,避免到处硬编码名字和组件 private class RequiredObjectConfig { public string TargetObjectName; public System.Type[] RequiredComponents; } static RequiredSceneObjectChecker() { // 初始化时注册回调 EditorApplication.hierarchyChanged += OnHierarchyDirty; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; // 项目刚打开、编辑器加载完成时,执行一次全量校验 CheckAndRestoreRequiredObjects(); } // Play模式切换回调,确保运行时完全关闭校验逻辑 private static void OnPlayModeStateChanged(PlayModeStateChange state) { // 进入运行模式时,直接解绑Hierarchy变化的监听,绝不执行任何创建 if (state == PlayModeStateChange.EnteredPlayMode) { EditorApplication.hierarchyChanged -= OnHierarchyDirty; } // 退出运行模式、回到编辑态时,重新开启监听 if (state == PlayModeStateChange.ExitingPlayMode) { EditorApplication.hierarchyChanged += OnHierarchyDirty; } } // 节流标记,合并短时间内多次高频触发的Hierarchy变更 private static bool _isCheckScheduled; private static void OnHierarchyDirty() { // 编辑态才处理,运行中直接跳过 if (EditorApplication.isPlaying) return; if (_isCheckScheduled) return; _isCheckScheduled = true; // 延迟到编辑器的下一帧执行校验,避免连续操作时重复跑逻辑 EditorApplication.delayCall += () => { CheckAndRestoreRequiredObjects(); _isCheckScheduled = false; }; } private static void CheckAndRestoreRequiredObjects() { // 进入运行模式的过程中,绝不执行任何创建、修改操作 if (EditorApplication.isPlayingOrWillChangePlaymode) return; // 在这里配置所有需要强制存在的场景对象,维护时只需要改这个列表 var requiredObjectList = new[] { new RequiredObjectConfig() { TargetObjectName = "SceneBuilder", // 把这个对象上必须挂的所有脚本列在这里 RequiredComponents = new[] { typeof(ObjectsGenerator), typeof(PlayerData) } } }; foreach (var config in requiredObjectList) { // 查找时包含失活对象,避免误判丢失 var targetObj = Object.FindObjectsOfType<GameObject>(includeInactive: true) .FirstOrDefault(go => go.name == config.TargetObjectName); if (targetObj == null) { // 对象完全丢失时,新建对应对象并挂载所有必要组件 var restoredObj = new GameObject(config.TargetObjectName); foreach (var componentType in config.RequiredComponents) { restoredObj.AddComponent(componentType); } Debug.Log($"已自动恢复必要场景对象:{config.TargetObjectName}"); } else { // 对象存在但组件缺失时,自动补全缺失的组件 foreach (var componentType in config.RequiredComponents) { if (targetObj.GetComponent(componentType) == null) { targetObj.AddComponent(componentType); Debug.Log($"为对象{config.TargetObjectName}补全缺失组件:{componentType.Name}"); } } } } } } #endif
实现效果
- 完全隔离运行时:所有逻辑被
UNITY_EDITOR宏包裹,且运行模式下会主动解绑所有事件监听,绝对不会在游戏运行、打包后执行任何创建逻辑。 - 适配全空场景:哪怕打开的场景没有任何对象,只要处于编辑态就会自动创建带全量必要组件的目标对象,不依赖任何已存在的引用。
- 维护成本极低:后续新增需要固定存在的场景对象,只需要在
requiredObjectList配置列表中添加对应条目即可,不需要手动做菜单、不需要写自定义Editor扩展。 - 性能损耗可忽略:通过延迟调用机制合并高频的Hierarchy变更事件,不会因为频繁的场景操作导致编辑器卡顿。
内容的提问来源于stack exchange,提问作者messirekhagen
相关产品推荐
相关产品推荐

