using UnityEngine; using UnityEngine.Events; using UnityEngine.Serialization; [ExecuteAlways] public class WeatherController : MonoBehaviour { public enum WeatherType { Clear, Rain, Snow } [System.Serializable] public class Automatic { public bool Enabled = false; [Tooltip("Minimum weather duration in seconds, including transitions.")] [Min(0.1f)] public float MinDuration = 10f; [Tooltip("Maximum weather duration in seconds, including transitions.")] [Min(0.1f)] public float MaxDuration = 20f; } [System.Serializable] public class Events { public UnityEvent OnStart = new UnityEvent(); public UnityEvent OnStop = new UnityEvent(); } [System.Serializable] public class Rain { [Min(0f)] public float RainSpeed = 1f; [Min(0f)] public float WetSpeed = 0.1f; public Events Events = new Events(); } [System.Serializable] public class Snow { [FormerlySerializedAs("SnowSpeed")] [Min(0f)] public float Speed = 0.1f; [Tooltip("Delay in seconds before snow starts accumulating.")] [Min(0f)] public float Delay = 1f; public Events Events = new Events(); } [SerializeField] private WeatherType currentWeather = WeatherType.Clear; [SerializeField] private Automatic _automatic = new Automatic(); [SerializeField] private Rain _rain = new Rain(); [SerializeField] private Snow _snow = new Snow(); private WeatherType previousWeather = WeatherType.Clear; private float snowDelayRemaining = 0f; private bool automaticWasEnabled = false; private float automaticTimeRemaining = 0f; // Current values sent to global shader properties. private float currentRain = 0f; private float currentWet = 0f; private float currentSnow = 0f; // Cached shader property IDs for efficient access. private static readonly int RainID = Shader.PropertyToID("_Rain"); private static readonly int WetID = Shader.PropertyToID("_Wet"); private static readonly int SnowID = Shader.PropertyToID("_Snow"); private void OnEnable() { previousWeather = WeatherType.Clear; snowDelayRemaining = 0f; automaticWasEnabled = false; automaticTimeRemaining = 0f; } private void Update() { if (Application.isPlaying) UpdateAutomaticWeather(); // Set target values based on the selected weather. float targetRain = 0f; float targetWet = 0f; float targetSnow = 0f; switch (currentWeather) { case WeatherType.Clear: targetRain = 0f; targetWet = 0f; // Dries in clear weather. targetSnow = 0f; break; case WeatherType.Rain: targetRain = 1f; targetWet = 1f; // Gets wetter while raining. targetSnow = 0f; break; case WeatherType.Snow: targetRain = 0f; targetWet = 0f; // Dries in snowy weather. targetSnow = 1f; break; } // Handle editor and runtime updates. if (Application.isPlaying) { if (currentWeather != previousWeather) snowDelayRemaining = currentWeather == WeatherType.Snow ? Mathf.Max(0f, _snow.Delay) : 0f; // Delay snow accumulation without delaying rain, wetness, or snow melting. float snowDeltaTime = Time.deltaTime; if (currentWeather == WeatherType.Snow && snowDelayRemaining > 0f) { float waitingTime = Mathf.Min(snowDelayRemaining, snowDeltaTime); snowDelayRemaining -= waitingTime; snowDeltaTime -= waitingTime; } // Apply gradual transitions at runtime. currentRain = Mathf.MoveTowards(currentRain, targetRain, Mathf.Max(0f, _rain.RainSpeed) * Time.deltaTime); currentWet = Mathf.MoveTowards(currentWet, targetWet, Mathf.Max(0f, _rain.WetSpeed) * Time.deltaTime); currentSnow = Mathf.MoveTowards(currentSnow, targetSnow, Mathf.Max(0f, _snow.Speed) * snowDeltaTime); } else { // Apply values immediately in the editor. currentRain = targetRain; currentWet = targetWet; currentSnow = targetSnow; } // Apply global shader properties (URP). Shader.SetGlobalFloat(RainID, currentRain); Shader.SetGlobalFloat(WetID, currentWet); Shader.SetGlobalFloat(SnowID, currentSnow); if (Application.isPlaying) UpdateWeatherEvents(); } private void UpdateAutomaticWeather() { if (!_automatic.Enabled) { automaticWasEnabled = false; automaticTimeRemaining = 0f; return; } // Keep the current weather for a full interval when automatic mode starts. if (!automaticWasEnabled || currentWeather != previousWeather) { automaticWasEnabled = true; automaticTimeRemaining = GetAutomaticDuration(); return; } automaticTimeRemaining -= Time.deltaTime; if (automaticTimeRemaining > 0f) return; // Select one of the other two weather types to avoid consecutive repeats. currentWeather = (WeatherType)(((int)currentWeather + Random.Range(1, 3)) % 3); automaticTimeRemaining = GetAutomaticDuration(); } private float GetAutomaticDuration() { float minDuration = Mathf.Max(0.1f, Mathf.Min(_automatic.MinDuration, _automatic.MaxDuration)); float maxDuration = Mathf.Max(minDuration, Mathf.Max(_automatic.MinDuration, _automatic.MaxDuration)); return Random.Range(minDuration, maxDuration); } private void UpdateWeatherEvents() { WeatherType nextWeather = currentWeather; if (nextWeather == previousWeather) return; WeatherType stoppedWeather = previousWeather; previousWeather = nextWeather; // Stop the previous weather before starting the next one. if (stoppedWeather == WeatherType.Rain) _rain.Events.OnStop?.Invoke(); else if (stoppedWeather == WeatherType.Snow) _snow.Events.OnStop?.Invoke(); if (nextWeather == WeatherType.Rain) _rain.Events.OnStart?.Invoke(); else if (nextWeather == WeatherType.Snow) _snow.Events.OnStart?.Invoke(); } }