#if UNITY_EDITOR using System.Reflection; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; [InitializeOnLoad] public static class GlobalAfterimageInstaller { private const string RendererDataPath = "Assets/Settings/Renderer2D.asset"; private const string GameplayVolumeProfilePath = "Assets/Scenes/gamePlay_gamePlay/gpGV.asset"; private static bool installScheduled; static GlobalAfterimageInstaller() { ScheduleInstall(); } [MenuItem("Bansonic/Rendering/Install Global Afterimage")] public static void EnsureInstalledMenu() { EnsureInstalled(); } private static void ScheduleInstall() { if (installScheduled) return; installScheduled = true; EditorApplication.delayCall += RunScheduledInstall; } private static void RunScheduledInstall() { installScheduled = false; EnsureInstalled(); } private static void EnsureInstalled() { if (EditorApplication.isCompiling || BuildPipeline.isBuildingPlayer) { ScheduleInstall(); return; } if (IsUnsafeSelectionActive()) { ScheduleInstall(); return; } EnsureRendererFeatureInstalled(); EnsureVolumeOverrideInstalled(); } private static void EnsureRendererFeatureInstalled() { ScriptableRendererData rendererData = AssetDatabase.LoadAssetAtPath(RendererDataPath); if (rendererData == null) return; if (!rendererData.TryGetRendererFeature(out GlobalAfterimageRendererFeature feature)) { feature = ScriptableObject.CreateInstance(); feature.name = "Global Afterimage Renderer Feature"; AssetDatabase.AddObjectToAsset(feature, rendererData); rendererData.rendererFeatures.Add(feature); rendererData.SetDirty(); EditorUtility.SetDirty(feature); EditorUtility.SetDirty(rendererData); TryValidateRendererFeatures(rendererData); AssetDatabase.SaveAssets(); AssetDatabase.ImportAsset(RendererDataPath); return; } if (feature.renderPassEvent != RenderPassEvent.AfterRenderingPostProcessing) { feature.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; rendererData.SetDirty(); EditorUtility.SetDirty(feature); EditorUtility.SetDirty(rendererData); AssetDatabase.SaveAssets(); } } private static void EnsureVolumeOverrideInstalled() { VolumeProfile profile = AssetDatabase.LoadAssetAtPath(GameplayVolumeProfilePath); if (profile == null) return; RemoveNullVolumeComponents(profile); if (profile.TryGet(out _)) return; GlobalAfterimageVolume component = profile.Add(true); component.active = false; component.opacity.overrideState = true; component.opacity.value = 0f; component.scale.overrideState = true; component.scale.value = 1.05f; component.affectSceneView.overrideState = true; component.affectSceneView.value = false; EditorUtility.SetDirty(component); EditorUtility.SetDirty(profile); AssetDatabase.SaveAssets(); } private static void RemoveNullVolumeComponents(VolumeProfile profile) { SerializedObject serializedObject = new SerializedObject(profile); SerializedProperty componentsProperty = serializedObject.FindProperty("components"); if (componentsProperty == null || !componentsProperty.isArray) return; bool removed = false; for (int i = componentsProperty.arraySize - 1; i >= 0; i--) { SerializedProperty element = componentsProperty.GetArrayElementAtIndex(i); if (element.objectReferenceValue != null) continue; componentsProperty.DeleteArrayElementAtIndex(i); removed = true; } if (!removed) return; serializedObject.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(profile); AssetDatabase.SaveAssets(); } private static void TryValidateRendererFeatures(ScriptableRendererData rendererData) { MethodInfo validateMethod = typeof(ScriptableRendererData).GetMethod("ValidateRendererFeatures", BindingFlags.Instance | BindingFlags.NonPublic); validateMethod?.Invoke(rendererData, null); } private static bool IsUnsafeSelectionActive() { Object activeObject = Selection.activeObject; if (activeObject is VolumeProfile || activeObject is Volume) return true; GameObject activeGameObject = Selection.activeGameObject; if (activeGameObject == null) return false; return activeGameObject.TryGetComponent(out _); } } #endif