Cozy Wather + buld systeme
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ashwild.Environment
|
||||
{
|
||||
/// <summary>
|
||||
/// Drives the day/night cycle for the "GAME/StylizedSky" skybox. Rotates the sun light and
|
||||
/// pushes every time-dependent value (gradient colors, sun/moon direction, cloud params,
|
||||
/// star opacity) into shader globals — mirroring how SeasonManager broadcasts "_Season".
|
||||
/// The skybox shader itself is dumb: it only renders whatever globals this manager writes.
|
||||
/// Runs in edit mode (ExecuteAlways) so the sky updates live while scrubbing the time slider.
|
||||
/// Purely visual and local; when co-op time-sync is added, only the driving time value needs
|
||||
/// to come from the server (see the "not yet networked" note in CLAUDE.md, like SeasonManager).
|
||||
/// </summary>
|
||||
[ExecuteAlways]
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("GAME/Environment/Time Of Day Manager")]
|
||||
public class TimeOfDayManager : MonoBehaviour
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
public static TimeOfDayManager Instance { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serialized Fields
|
||||
|
||||
[Header("References")]
|
||||
[Tooltip("Directional light rotated to act as the sun. Optional but recommended.")]
|
||||
[SerializeField] private Light sunLight;
|
||||
[Tooltip("Skybox material using GAME/StylizedSky. Assigned to RenderSettings on enable when set.")]
|
||||
[SerializeField] private Material skyboxMaterial;
|
||||
|
||||
[Header("Time")]
|
||||
[Tooltip("0 = midnight, 0.25 = sunrise, 0.5 = noon, 0.75 = sunset.")]
|
||||
[Range(0f, 1f)] [SerializeField] private float timeOfDay = 0.5f;
|
||||
[SerializeField] private bool autoAdvance = true;
|
||||
[Tooltip("Real seconds for one full 24h cycle. Only advances in Play.")]
|
||||
[SerializeField] private float dayLengthSeconds = 300f;
|
||||
[Tooltip("Compass offset of the sun's arc, in degrees.")]
|
||||
[Range(0f, 360f)] [SerializeField] private float sunYaw = 20f;
|
||||
|
||||
[Header("Sun Light")]
|
||||
[SerializeField] private Gradient sunColorOverDay = new Gradient();
|
||||
[SerializeField] private AnimationCurve sunIntensityOverDay = AnimationCurve.Constant(0f, 1f, 1f);
|
||||
[Tooltip("Peak directional-light intensity reached around noon.")]
|
||||
[SerializeField] private float maxSunIntensity = 1.3f;
|
||||
|
||||
[Header("Sky Gradient")]
|
||||
[SerializeField] private Gradient zenithColorOverDay = new Gradient();
|
||||
[SerializeField] private Gradient horizonColorOverDay = new Gradient();
|
||||
[SerializeField] private Gradient groundColorOverDay = new Gradient();
|
||||
|
||||
[Header("Moon")]
|
||||
[SerializeField] private Color moonColor = new Color(0.85f, 0.88f, 0.96f);
|
||||
|
||||
[Header("Clouds")]
|
||||
[SerializeField] private Gradient cloudColorOverDay = new Gradient();
|
||||
[SerializeField] private Gradient cloudShadowColorOverDay = new Gradient();
|
||||
[Tooltip("Coverage threshold over the day. Higher = fewer clouds.")]
|
||||
[SerializeField] private AnimationCurve cloudCoverageOverDay = AnimationCurve.Constant(0f, 1f, 0.48f);
|
||||
[Range(0f, 1f)] [SerializeField] private float cloudOpacity = 1f;
|
||||
[SerializeField] private Vector2 windDirection = new Vector2(1f, 0.35f);
|
||||
[SerializeField] private float windSpeed = 0.01f;
|
||||
|
||||
[Header("Stars")]
|
||||
[Tooltip("Sun elevation at which stars start fading in (upper) and are fully visible (lower).")]
|
||||
[SerializeField] private float starFadeInElevation = 0.05f;
|
||||
[SerializeField] private float starFullElevation = -0.12f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
private Vector2 cloudOffset;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shader Property IDs
|
||||
|
||||
private static readonly int ZenithColorId = Shader.PropertyToID("_SkyZenithColor");
|
||||
private static readonly int HorizonColorId = Shader.PropertyToID("_SkyHorizonColor");
|
||||
private static readonly int GroundColorId = Shader.PropertyToID("_SkyGroundColor");
|
||||
private static readonly int SunDirectionId = Shader.PropertyToID("_SkySunDirection");
|
||||
private static readonly int SunColorId = Shader.PropertyToID("_SkySunColor");
|
||||
private static readonly int MoonDirectionId = Shader.PropertyToID("_SkyMoonDirection");
|
||||
private static readonly int MoonColorId = Shader.PropertyToID("_SkyMoonColor");
|
||||
private static readonly int CloudColorId = Shader.PropertyToID("_SkyCloudColor");
|
||||
private static readonly int CloudShadowColorId = Shader.PropertyToID("_SkyCloudShadowColor");
|
||||
private static readonly int CloudParamsId = Shader.PropertyToID("_SkyCloudParams");
|
||||
private static readonly int StarOpacityId = Shader.PropertyToID("_SkyStarOpacity");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Registers the instance, assigns the skybox and pushes an initial state so the sky is
|
||||
/// correct the moment the component becomes active (including in the editor).
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
Instance = this;
|
||||
if (skyboxMaterial != null)
|
||||
RenderSettings.skybox = skyboxMaterial;
|
||||
Apply();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the shared instance so a disabled manager never keeps answering as the current one.
|
||||
/// </summary>
|
||||
private void OnDisable()
|
||||
{
|
||||
if (Instance == this)
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-clamps tuning and refreshes the sky whenever a value is edited in the inspector.
|
||||
/// </summary>
|
||||
private void OnValidate()
|
||||
{
|
||||
dayLengthSeconds = Mathf.Max(1f, dayLengthSeconds);
|
||||
Apply();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances time (Play only) and scrolls the clouds, then re-applies every frame.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (Application.isPlaying && autoAdvance)
|
||||
timeOfDay = Mathf.Repeat(timeOfDay + Time.deltaTime / dayLengthSeconds, 1f);
|
||||
|
||||
cloudOffset += windDirection.normalized * (windSpeed * Time.deltaTime);
|
||||
Apply();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Apply
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the current time of day into sun orientation and shader globals, and mirrors
|
||||
/// the resolved sun color/intensity onto the directional light so scene lighting matches
|
||||
/// the sky. This is the single place every time-dependent value is broadcast.
|
||||
/// </summary>
|
||||
private void Apply()
|
||||
{
|
||||
Vector3 sunDir = OrientSun();
|
||||
Vector3 moonDir = -sunDir;
|
||||
|
||||
Color sunColor = sunColorOverDay.Evaluate(timeOfDay);
|
||||
DriveSunLight(sunColor);
|
||||
|
||||
Shader.SetGlobalColor(ZenithColorId, ToRendered(zenithColorOverDay.Evaluate(timeOfDay)));
|
||||
Shader.SetGlobalColor(HorizonColorId, ToRendered(horizonColorOverDay.Evaluate(timeOfDay)));
|
||||
Shader.SetGlobalColor(GroundColorId, ToRendered(groundColorOverDay.Evaluate(timeOfDay)));
|
||||
|
||||
Shader.SetGlobalVector(SunDirectionId, sunDir);
|
||||
Shader.SetGlobalColor(SunColorId, ToRendered(sunColor));
|
||||
Shader.SetGlobalVector(MoonDirectionId, moonDir);
|
||||
Shader.SetGlobalColor(MoonColorId, ToRendered(moonColor));
|
||||
|
||||
Shader.SetGlobalColor(CloudColorId, ToRendered(cloudColorOverDay.Evaluate(timeOfDay)));
|
||||
Shader.SetGlobalColor(CloudShadowColorId, ToRendered(cloudShadowColorOverDay.Evaluate(timeOfDay)));
|
||||
Shader.SetGlobalVector(CloudParamsId, new Vector4(
|
||||
cloudCoverageOverDay.Evaluate(timeOfDay), cloudOpacity, cloudOffset.x, cloudOffset.y));
|
||||
|
||||
Shader.SetGlobalFloat(StarOpacityId,
|
||||
Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(starFadeInElevation, starFullElevation, sunDir.y)));
|
||||
|
||||
RepaintEditor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the sun light so noon sits overhead and sunrise/sunset sit on the horizon,
|
||||
/// and returns the world-space direction pointing toward the sun (what the shader wants).
|
||||
/// Works from the current time even when no light is assigned.
|
||||
/// </summary>
|
||||
private Vector3 OrientSun()
|
||||
{
|
||||
float pitch = timeOfDay * 360f - 90f;
|
||||
Quaternion rotation = Quaternion.Euler(pitch, sunYaw, 0f);
|
||||
if (sunLight != null)
|
||||
sunLight.transform.rotation = rotation;
|
||||
return -(rotation * Vector3.forward);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tints the directional light and fades its intensity to zero at night so the world
|
||||
/// darkens together with the sky. No-op when no light is wired up.
|
||||
/// </summary>
|
||||
private void DriveSunLight(Color sunColor)
|
||||
{
|
||||
if (sunLight == null) return;
|
||||
sunLight.color = sunColor;
|
||||
sunLight.intensity = sunIntensityOverDay.Evaluate(timeOfDay) * maxSunIntensity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an authored sRGB color to the value the shader must receive. Colors pushed via
|
||||
/// Shader.SetGlobalColor are NOT auto-gamma-corrected (unlike material properties), so in a
|
||||
/// linear project they must be pre-converted or the whole sky washes out to white.
|
||||
/// </summary>
|
||||
private static Color ToRendered(Color c)
|
||||
{
|
||||
return QualitySettings.activeColorSpace == ColorSpace.Linear ? c.linear : c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the Scene view to redraw while scrubbing values outside Play, matching SeasonManager.
|
||||
/// </summary>
|
||||
private void RepaintEditor()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
UnityEditor.SceneView.RepaintAll();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// Current normalized time of day (0 = midnight, 0.5 = noon).
|
||||
/// </summary>
|
||||
public float TimeOfDay => timeOfDay;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the time of day directly (wrapped into [0,1)) and refreshes the sky immediately.
|
||||
/// </summary>
|
||||
public void SetTimeOfDay(float normalized)
|
||||
{
|
||||
timeOfDay = Mathf.Repeat(normalized, 1f);
|
||||
Apply();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Defaults
|
||||
|
||||
/// <summary>
|
||||
/// Seeds all gradients/curves with a bright, soft daytime palette matched to the Raygeas
|
||||
/// stylized skybox reference, so a freshly added manager looks right without hand-authoring.
|
||||
/// Invoked by the editor when the component is added or reset.
|
||||
/// </summary>
|
||||
private void Reset()
|
||||
{
|
||||
timeOfDay = 0.5f;
|
||||
sunYaw = 20f;
|
||||
dayLengthSeconds = 300f;
|
||||
SeedStylizedPreset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-applies the tuned color palette WITHOUT clearing the wired references (Sun Light,
|
||||
/// Skybox Material) or the current time — unlike Reset, which nulls everything. Exposed on
|
||||
/// the component's context menu so the palette can be re-seeded during iteration.
|
||||
/// </summary>
|
||||
[ContextMenu("Apply Stylized Sky Preset")]
|
||||
private void SeedStylizedPreset()
|
||||
{
|
||||
zenithColorOverDay = BuildGradient(
|
||||
(0.00f, new Color(0.02f, 0.03f, 0.07f)),
|
||||
(0.23f, new Color(0.18f, 0.28f, 0.40f)),
|
||||
(0.30f, new Color(0.14f, 0.40f, 0.56f)),
|
||||
(0.50f, new Color(0.13f, 0.42f, 0.58f)),
|
||||
(0.70f, new Color(0.14f, 0.38f, 0.53f)),
|
||||
(0.78f, new Color(0.22f, 0.26f, 0.40f)),
|
||||
(1.00f, new Color(0.02f, 0.03f, 0.07f)));
|
||||
|
||||
horizonColorOverDay = BuildGradient(
|
||||
(0.00f, new Color(0.04f, 0.05f, 0.10f)),
|
||||
(0.23f, new Color(0.86f, 0.50f, 0.36f)),
|
||||
(0.32f, new Color(0.32f, 0.56f, 0.68f)),
|
||||
(0.50f, new Color(0.26f, 0.52f, 0.66f)),
|
||||
(0.68f, new Color(0.30f, 0.50f, 0.60f)),
|
||||
(0.78f, new Color(0.86f, 0.48f, 0.32f)),
|
||||
(1.00f, new Color(0.04f, 0.05f, 0.10f)));
|
||||
|
||||
groundColorOverDay = BuildGradient(
|
||||
(0.00f, new Color(0.03f, 0.04f, 0.08f)),
|
||||
(0.25f, new Color(0.26f, 0.30f, 0.34f)),
|
||||
(0.50f, new Color(0.22f, 0.44f, 0.56f)),
|
||||
(0.75f, new Color(0.28f, 0.28f, 0.30f)),
|
||||
(1.00f, new Color(0.03f, 0.04f, 0.08f)));
|
||||
|
||||
sunColorOverDay = BuildGradient(
|
||||
(0.00f, new Color(0.40f, 0.40f, 0.50f)),
|
||||
(0.23f, new Color(1.00f, 0.55f, 0.30f)),
|
||||
(0.50f, new Color(1.00f, 0.95f, 0.85f)),
|
||||
(0.77f, new Color(1.00f, 0.50f, 0.28f)),
|
||||
(1.00f, new Color(0.40f, 0.40f, 0.50f)));
|
||||
|
||||
cloudColorOverDay = BuildGradient(
|
||||
(0.00f, new Color(0.10f, 0.11f, 0.18f)),
|
||||
(0.25f, new Color(0.98f, 0.78f, 0.66f)),
|
||||
(0.50f, new Color(1.00f, 1.00f, 1.00f)),
|
||||
(0.75f, new Color(0.98f, 0.76f, 0.64f)),
|
||||
(1.00f, new Color(0.10f, 0.11f, 0.18f)));
|
||||
|
||||
cloudShadowColorOverDay = BuildGradient(
|
||||
(0.00f, new Color(0.05f, 0.06f, 0.10f)),
|
||||
(0.25f, new Color(0.55f, 0.45f, 0.50f)),
|
||||
(0.50f, new Color(0.70f, 0.76f, 0.84f)),
|
||||
(0.75f, new Color(0.56f, 0.44f, 0.48f)),
|
||||
(1.00f, new Color(0.05f, 0.06f, 0.10f)));
|
||||
|
||||
sunIntensityOverDay = new AnimationCurve(
|
||||
new Keyframe(0.00f, 0f), new Keyframe(0.23f, 0.05f), new Keyframe(0.30f, 0.8f),
|
||||
new Keyframe(0.50f, 1f), new Keyframe(0.70f, 0.8f), new Keyframe(0.78f, 0.05f),
|
||||
new Keyframe(1.00f, 0f));
|
||||
|
||||
cloudCoverageOverDay = AnimationCurve.Constant(0f, 1f, 0.55f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a Gradient from (time, color) tuples with full alpha, keeping color authoring
|
||||
/// in one compact place. Alpha is unused by the sky (every color is opaque).
|
||||
/// </summary>
|
||||
private static Gradient BuildGradient(params (float time, Color color)[] stops)
|
||||
{
|
||||
var colorKeys = new GradientColorKey[stops.Length];
|
||||
for (int i = 0; i < stops.Length; i++)
|
||||
colorKeys[i] = new GradientColorKey(stops[i].color, stops[i].time);
|
||||
|
||||
var gradient = new Gradient();
|
||||
gradient.SetKeys(colorKeys, new[]
|
||||
{
|
||||
new GradientAlphaKey(1f, 0f),
|
||||
new GradientAlphaKey(1f, 1f)
|
||||
});
|
||||
return gradient;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user