diff --git a/crest/Assets/Crest/Crest-Examples/Shared/Scripts/UnderwaterEnvironmentalLighting.cs b/crest/Assets/Crest/Crest-Examples/Shared/Scripts/UnderwaterEnvironmentalLighting.cs index 67bdff489..2abe1d30b 100644 --- a/crest/Assets/Crest/Crest-Examples/Shared/Scripts/UnderwaterEnvironmentalLighting.cs +++ b/crest/Assets/Crest/Crest-Examples/Shared/Scripts/UnderwaterEnvironmentalLighting.cs @@ -40,13 +40,18 @@ public class UnderwaterEnvironmentalLighting : MonoBehaviour public const float DEPTH_OUTSCATTER_CONSTANT = 0.25f; + OceanRendererLifeCycleHelper _oceanRendererLifeCycle; bool _isInitialised = false; + void Awake() + { + _oceanRendererLifeCycle = new OceanRendererLifeCycleHelper(this); + } + void OnEnable() { - if (OceanRenderer.Instance == null) + if (!_oceanRendererLifeCycle.OnEnable()) { - enabled = false; return; } @@ -76,6 +81,8 @@ void OnEnable() void OnDisable() { + _oceanRendererLifeCycle.OnDisable(); + if (!_isInitialised) { return; @@ -93,6 +100,11 @@ void OnDisable() _isInitialised = false; } + void OnDestroy() + { + _oceanRendererLifeCycle.OnDestroy(); + } + void LateUpdate() { if (OceanRenderer.Instance == null) diff --git a/crest/Assets/Crest/Crest/Scripts/Helpers/OceanRendererLifeCycleHelper.cs b/crest/Assets/Crest/Crest/Scripts/Helpers/OceanRendererLifeCycleHelper.cs new file mode 100644 index 000000000..ca7cc71ef --- /dev/null +++ b/crest/Assets/Crest/Crest/Scripts/Helpers/OceanRendererLifeCycleHelper.cs @@ -0,0 +1,155 @@ +// Crest Ocean System + +// This file is subject to the MIT License as seen in the root of this folder structure (LICENSE) + +namespace Crest +{ + using UnityEngine; + + /// + /// Enables/disables a component based on the OceanRenderer's life cycle. It will allow a component that depend on + /// the OceanRenderer to initialise after the OceanRenderer has initialised (i.e. delayed initialisation). + /// + /// It stores the "enabled" state set by the user as it enables or disables the component if the OceanRenderer is + /// enabled or disabled respectively using event subscription broadcasted from the OceanRenderer. + /// + /// It supports [ExecuteAlways/InEditMode] by storing the "enabled" state, but does not enable or disable the + /// component as that would change serialized data. + /// + /// Components must call OnEnable, OnDisable and OnDestroy from their MonoBehaviour counterparts. + /// + public class OceanRendererLifeCycleHelper + { + // The mono behaviour we want to track the enabled/disabled preference. + readonly MonoBehaviour _monoBehaviour; + + // Store the "enabled" state set by the user so we can respect it. + internal bool _enabledValueSetByTheUser; + + // Whether the state change came from the user or from the ocean life cycle event. Generally, this should always + // be true since state changes from users are unknown. + bool _isStateChangeFromUser = false; + + public OceanRendererLifeCycleHelper(MonoBehaviour monoBehaviour) + { + _monoBehaviour = monoBehaviour; + // Store the "enabled" state set by the user. + _enabledValueSetByTheUser = _monoBehaviour.enabled; + + OceanRenderer.OnOceanRendererEnabled -= OnOceanRendererEnabled; + OceanRenderer.OnOceanRendererEnabled += OnOceanRendererEnabled; + OceanRenderer.OnOceanRendererDisabled -= OnOceanRendererDisabled; + OceanRenderer.OnOceanRendererDisabled += OnOceanRendererDisabled; + } + + ~OceanRendererLifeCycleHelper() + { + OceanRenderer.OnOceanRendererEnabled -= OnOceanRendererEnabled; + OceanRenderer.OnOceanRendererDisabled -= OnOceanRendererDisabled; + } + + /// + /// Needs to be called to clean up resources. Destructors do not work when domain reload is disabled. + /// + public void OnDestroy() + { + OceanRenderer.OnOceanRendererEnabled -= OnOceanRendererEnabled; + OceanRenderer.OnOceanRendererDisabled -= OnOceanRendererDisabled; + } + + /// + /// Sets enabled based on OceanRenderer's life cycle. Callee must return early if false is returned. + /// + public bool OnEnable() + { +#if UNITY_EDITOR + if (!Application.isPlaying) + { + // In edit mode just store "enabled" as we do not disable the component anyway. + _enabledValueSetByTheUser = _monoBehaviour.enabled; + return OceanRenderer.Instance != null; + } +#endif + + // This will be "false" when we know when the user has not changed the state which is something we can + // more easily determine like when we change the state ourselves. + if (_isStateChangeFromUser) + { + // Flip value as it could be either the user enabling or disabling the component. For the case where the + // ocean renderer is not active and this component is set to be enabled, the component will be disabled. + // The user will see an unchecked checkbox for "enable" as they should. When they toggle it, it will + // enable the component but since the ocean is disabled it will disable itself after recording the + // user's preference. + _enabledValueSetByTheUser = !_enabledValueSetByTheUser; + } + + // Always revert to "true" as whether the change comes from a user is unknown. + _isStateChangeFromUser = true; + + if (OceanRenderer.Instance == null) + { + // Mark "false" as we will now disable the component and the subsequent OnDisable is not from the user. + _isStateChangeFromUser = false; + _monoBehaviour.enabled = false; + return false; + } + + return true; + } + + /// + /// Sets enabled based on OceanRenderer's life cycle. + /// + public void OnDisable() + { +#if UNITY_EDITOR + if (!Application.isPlaying) + { + // In edit mode just store "enabled" as we do not disable the component anyway. + _enabledValueSetByTheUser = _monoBehaviour.enabled; + return; + } +#endif + + if (_isStateChangeFromUser) + { + _enabledValueSetByTheUser = false; + } + + // Always revert to "true" as whether the change comes from a user is unknown. + _isStateChangeFromUser = true; + } + + // This will be called by the OceanRenderer through an event. If during the component's OnEnable event the + // OceanRenderer is already ready, then this will be skipped. + void OnOceanRendererEnabled() + { +#if UNITY_EDITOR + // We do not want to change a serialised value in edit mode. + if (!Application.isPlaying) + { + return; + } +#endif + + // If these two are equal then this event will do nothing and next OnEnable/OnDisable could be the user. + _isStateChangeFromUser = _monoBehaviour.enabled == _enabledValueSetByTheUser; + // Apply the "enabled" state that the user wants now that OceanRenderer is ready. + _monoBehaviour.enabled = _enabledValueSetByTheUser; + } + + // This will be called by the OceanRenderer through an event. + void OnOceanRendererDisabled() + { + if (!Application.isPlaying) + { + return; + } + + // If the user has set the component to disabled, then this event will do nothing and next + // OnEnable/OnDisable could be the user. + _isStateChangeFromUser = _enabledValueSetByTheUser == false; + _monoBehaviour.enabled = false; + } + } +} diff --git a/crest/Assets/Crest/Crest/Scripts/Helpers/OceanRendererLifeCycleHelper.cs.meta b/crest/Assets/Crest/Crest/Scripts/Helpers/OceanRendererLifeCycleHelper.cs.meta new file mode 100644 index 000000000..b5783661b --- /dev/null +++ b/crest/Assets/Crest/Crest/Scripts/Helpers/OceanRendererLifeCycleHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e5719391a5a244f85b470c23e28befe9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/crest/Assets/Crest/Crest/Scripts/LodData/OceanDepthCache.cs b/crest/Assets/Crest/Crest/Scripts/LodData/OceanDepthCache.cs index 05982db18..08156ce2d 100644 --- a/crest/Assets/Crest/Crest/Scripts/LodData/OceanDepthCache.cs +++ b/crest/Assets/Crest/Crest/Scripts/LodData/OceanDepthCache.cs @@ -88,13 +88,32 @@ public enum OceanDepthCacheRefreshMode Camera _camDepthCache; Material _copyDepthMaterial; + OceanRendererLifeCycleHelper _oceanRendererLifeCycle; + + bool _isInitialized = false; + + void Awake() + { + _oceanRendererLifeCycle = new OceanRendererLifeCycleHelper(this); + } + + void OnEnable() + { + _oceanRendererLifeCycle.OnEnable(); + } + + void OnDisable() + { + _oceanRendererLifeCycle.OnDisable(); + } + + void OnDestroy() + { + _oceanRendererLifeCycle.OnDestroy(); + } + void Start() { - if (OceanRenderer.Instance == null) - { - enabled = false; - return; - } #if UNITY_EDITOR if (EditorApplication.isPlaying && _runValidationOnStart) @@ -111,11 +130,18 @@ void Start() { PopulateCache(); } + + _isInitialized = true; } #if UNITY_EDITOR void Update() { + if (!_isInitialized) + { + Start(); + } + // We need to switch the quad texture if the user changes the cache type in the editor. InitCacheQuad(); diff --git a/crest/Assets/Crest/Crest/Scripts/OceanRenderer.cs b/crest/Assets/Crest/Crest/Scripts/OceanRenderer.cs index 4b47a16ae..b35c99adb 100644 --- a/crest/Assets/Crest/Crest/Scripts/OceanRenderer.cs +++ b/crest/Assets/Crest/Crest/Scripts/OceanRenderer.cs @@ -338,6 +338,8 @@ public enum DefaultClippingState SampleHeightHelper _sampleHeightHelper = new SampleHeightHelper(); public static OceanRenderer Instance { get; private set; } + public static System.Action OnOceanRendererEnabled; + public static System.Action OnOceanRendererDisabled; // A hash of the settings used to generate the ocean, used to regenerate when necessary int _generatedSettingsHash = 0; @@ -528,6 +530,8 @@ void OnEnable() } _canSkipCulling = false; + + OnOceanRendererEnabled?.Invoke(); } private void OnDisable() @@ -548,6 +552,8 @@ private void OnDisable() } #endif + OnOceanRendererDisabled?.Invoke(); + CleanUp(); Instance = null; diff --git a/crest/Assets/Crest/Crest/Scripts/Reflection/OceanPlanarReflection.cs b/crest/Assets/Crest/Crest/Scripts/Reflection/OceanPlanarReflection.cs index 77f6974b5..44a0287d3 100644 --- a/crest/Assets/Crest/Crest/Scripts/Reflection/OceanPlanarReflection.cs +++ b/crest/Assets/Crest/Crest/Scripts/Reflection/OceanPlanarReflection.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using UnityEditor; using UnityEngine; @@ -125,11 +126,17 @@ public class OceanPlanarReflection : MonoBehaviour const int CULL_DISTANCE_COUNT = 32; float[] _cullDistances = new float[CULL_DISTANCE_COUNT]; - private void Start() + internal OceanRendererLifeCycleHelper _oceanRendererLifeCycle; + + private void Awake() { - if (OceanRenderer.Instance == null) + _oceanRendererLifeCycle = new OceanRendererLifeCycleHelper(this); + } + + private void OnEnable() + { + if (!_oceanRendererLifeCycle.OnEnable()) { - enabled = false; return; } @@ -383,6 +390,8 @@ static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane private void OnDisable() { + _oceanRendererLifeCycle.OnDisable(); + if (_camViewpoint != null) { PreparedReflections.Remove(_camViewpoint.GetHashCode()); @@ -400,5 +409,31 @@ private void OnDisable() _camReflections = null; } } + + private void OnDestroy() + { + _oceanRendererLifeCycle.OnDestroy(); + } } + +#if UNITY_EDITOR + [CustomEditor(typeof(OceanPlanarReflection)), CanEditMultipleObjects] + public class OceanPlanarReflectionEditor : Editor + { + public override void OnInspectorGUI() + { + var target = this.target as OceanPlanarReflection; + + if (OceanRenderer.Instance == null && target._oceanRendererLifeCycle._enabledValueSetByTheUser) + { + EditorGUILayout.Space(); + EditorGUILayout.HelpBox("The component is disabled as there is no active OceanRenderer in " + + "the scene. It will be enabled once there is one.", MessageType.Info); + EditorGUILayout.Space(); + } + + base.OnInspectorGUI(); + } + } +#endif }