feat: Weather
This commit is contained in:
192
Assets/_Content/Scripts/WeatherController.cs
Normal file
192
Assets/_Content/Scripts/WeatherController.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
2
Assets/_Content/Scripts/WeatherController.cs.meta
Normal file
2
Assets/_Content/Scripts/WeatherController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71ef1c7c5389fc949abc68860de5b6e8
|
||||
Reference in New Issue
Block a user