193 lines
6.4 KiB
C#
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();
|
|
}
|
|
}
|