Files
ShadingExercise/Assets/_Content/Scripts/WeatherController.cs
2026-09-21 15:05:47 +02:00

193 lines
6.4 KiB
C#

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();
}
}