using System; using Cysharp.Threading.Tasks; using UnityEngine; namespace DanieleMarotta.RiversongCodeShowcase { [RequiresWorldReadyForUpdate] [GameSystemGroup(typeof(LateGameSystemGroup))] [UpdateAfter(typeof(WorldTimeSystem))] public class DayNightCycleLightingSystem : GameSystem, IInitializable, IDisposable, IUpdatable { [InjectService] private GameConfig _config; [InjectService] private IScene _scene; [InjectService] private World _world; private Quaternion _initialLightRotation; public DayNightCycleLightingSystem(IServiceLocator serviceLocator) : base(serviceLocator) { } public UniTask InitializeAsync() { _initialLightRotation = _scene.MainLight.transform.localRotation; return UniTask.CompletedTask; } public void Dispose() { Shader.SetGlobalFloat(ShaderProperties.NightBlend, 0); } public void Update() { var nightBlend = _world.TimeState.NightBlend; Shader.SetGlobalFloat(ShaderProperties.NightBlend, nightBlend); UpdateLightRig(nightBlend); UpdateShadows(nightBlend); UpdateAmbientLighting(nightBlend); UpdatePostProcessing(nightBlend); } private void UpdateLightRig(float nightBlend) { _scene.LightRig.transform.localRotation = _world.TimeState.DayNightCycleStep switch { DayNightCycleStep.Day => Quaternion.identity, DayNightCycleStep.DayToNight => Quaternion.Euler(0, nightBlend * 360, 0), DayNightCycleStep.Night => Quaternion.identity, DayNightCycleStep.NightToDay => Quaternion.Euler(0, -nightBlend * 360, 0), _ => throw new ArgumentException() }; var dayRotation = _initialLightRotation; var nightRotation = _initialLightRotation * Quaternion.Euler(-90, 0, 0); _scene.MainLight.transform.localRotation = _world.TimeState.DayNightCycleStep switch { DayNightCycleStep.Day => dayRotation, DayNightCycleStep.DayToNight => Quaternion.Slerp(dayRotation, nightRotation, nightBlend), DayNightCycleStep.Night => nightRotation, DayNightCycleStep.NightToDay => Quaternion.Slerp(nightRotation, dayRotation, 1 - nightBlend), _ => throw new ArgumentException() }; _scene.NightLight.transform.localRotation = _world.TimeState.DayNightCycleStep switch { DayNightCycleStep.Day => nightRotation, DayNightCycleStep.DayToNight => Quaternion.Slerp(nightRotation, dayRotation, nightBlend), DayNightCycleStep.Night => dayRotation, DayNightCycleStep.NightToDay => Quaternion.Slerp(dayRotation, nightRotation, 1 - nightBlend), _ => throw new ArgumentException() }; } private void UpdateShadows(float nightBlend) { const float fadeSpeed = 2; var mainLightT = Mathf.Clamp01(fadeSpeed * nightBlend); UpdateShadows(_scene.MainLight, Mathf.Lerp(_config.Time.DayLighting.ShadowStrength, 0, mainLightT)); var nightLightT = Mathf.Clamp01(fadeSpeed * (1 - nightBlend)); UpdateShadows(_scene.NightLight, Mathf.Lerp(_config.Time.NightLighting.ShadowStrength, 0, nightLightT)); } private static void UpdateShadows(Light light, float shadowStrength) { light.shadowStrength = shadowStrength; light.shadows = shadowStrength > 0 ? LightShadows.Soft : LightShadows.None; } private void UpdateAmbientLighting(float nightBlend) { RenderSettings.ambientLight = Color.Lerp(_config.Time.DayLighting.AmbientColor, _config.Time.NightLighting.AmbientColor, nightBlend); } private void UpdatePostProcessing(float nightBlend) { _scene.NightVolume.weight = nightBlend; _scene.BloomVolume.weight = nightBlend; _scene.WarmTintVolume.weight = GetWarmTintBlend(); } private float GetWarmTintBlend() { var step = _world.TimeState.DayNightCycleStep; if (step != DayNightCycleStep.DayToNight && step != DayNightCycleStep.NightToDay) return 0; var config = _config.Time; var time = _world.TimeState.DayNightCycleTime; var blend = step == DayNightCycleStep.DayToNight ? Mathf.Clamp01(time / config.DayToNightDuration) : Mathf.Clamp01(time / config.NightToDayDuration); var rampUp = config.WarmTintRampUp; var rampDown = config.WarmTintRampDown; if (blend <= rampUp.x) return 0; if (blend < rampUp.y) return Mathf.SmoothStep(0, 1, Mathf.InverseLerp(rampUp.x, rampUp.y, blend)); if (blend <= rampDown.x) return 1; if (blend < rampDown.y) return Mathf.SmoothStep(1, 0, Mathf.InverseLerp(rampDown.x, rampDown.y, blend)); return 0; } } }