using System;
using System.Collections.Generic;
using NiloToon.NiloToonURP;
using UnityEngine;
namespace Streamingle.Background
{
///
/// 시간 흐름에 따라 스카이박스 블렌딩 + 디렉셔널 라이트를 제어하는 컨트롤러.
/// 3단계(Stage1→Stage2→Stage3) 전환을 timeOfDay(0~1)로 통합 제어.
/// [ExecuteAlways]로 에디터에서도 실시간 프리뷰 가능.
///
[ExecuteAlways]
public class SkyboxTimeController : MonoBehaviour
{
// ───────────────────────── References ─────────────────────────
[Tooltip("제어할 디렉셔널 라이트")]
public Light directionalLight;
[Tooltip("Skybox/Cubemap Blend 머티리얼")]
public Material skyboxMaterial;
// ───────────────────────── Time ─────────────────────────
[Range(0f, 1f)]
[Tooltip("0=Stage1, 0.5=Stage2, 1=Stage3")]
public float timeOfDay = 0f;
[Tooltip("자동 재생 여부")]
public bool autoPlay = false;
[Tooltip("한 사이클(0→1) 소요 시간(초)")]
public float cycleDuration = 60f;
[Tooltip("자동 재생 시 루프 여부")]
public bool loop = true;
// ───────────────────────── Stage Settings ─────────────────────────
public StageSettings stage1 = new StageSettings
{
lightColor = new Color(0.4f, 0.45f, 0.65f),
lightIntensity = 0.3f,
lightRotation = new Vector3(10f, -30f, 0f),
skyboxExposure = 0.5f,
skyboxTint = Color.white,
};
public StageSettings stage2 = new StageSettings
{
lightColor = new Color(1f, 0.95f, 0.85f),
lightIntensity = 1.2f,
lightRotation = new Vector3(50f, -30f, 0f),
skyboxExposure = 1.0f,
skyboxTint = Color.white,
};
public StageSettings stage3 = new StageSettings
{
lightColor = new Color(1f, 0.55f, 0.3f),
lightIntensity = 0.6f,
lightRotation = new Vector3(5f, 150f, 0f),
skyboxExposure = 0.7f,
skyboxTint = new Color(1f, 0.85f, 0.75f),
};
// ───────────────────────── Custom Material Properties ─────────────────────────
[Tooltip("머티리얼의 임의 프로퍼티를 3단계로 보간 제어")]
public List materialPropertyOverrides = new List();
// ───────────────────────── NiloToon Light Override ─────────────────────────
[Tooltip("NiloToon 캐릭터 메인 라이트 오버라이더 (디렉셔널 라이트와 동기화)")]
public NiloToonCharacterMainLightOverrider niloToonLightOverrider;
[Tooltip("NiloToon 라이트 컬러를 별도로 지정할지 여부 (false면 디렉셔널 라이트와 동일)")]
public bool niloToonSeparateColor = false;
public Color niloToonColorStage1 = Color.white;
public Color niloToonColorStage2 = Color.white;
public Color niloToonColorStage3 = Color.white;
[Tooltip("NiloToon 라이트 Intensity를 별도로 지정할지 여부 (false면 디렉셔널 라이트와 동일)")]
public bool niloToonSeparateIntensity = false;
public float niloToonIntensityStage1 = 1f;
public float niloToonIntensityStage2 = 1f;
public float niloToonIntensityStage3 = 1f;
// ───────────────────────── Ambient ─────────────────────────
[Tooltip("Ambient 컬러도 함께 보간")]
public bool controlAmbient = true;
public Color ambientStage1 = new Color(0.15f, 0.15f, 0.25f);
public Color ambientStage2 = new Color(0.45f, 0.45f, 0.45f);
public Color ambientStage3 = new Color(0.3f, 0.2f, 0.15f);
// ───────────────────────── Private ─────────────────────────
private Material _instanceMaterial;
private static readonly int BlendProp = Shader.PropertyToID("_Blend");
private static readonly int TintProp = Shader.PropertyToID("_Tint");
private static readonly int ExposureProp = Shader.PropertyToID("_Exposure");
// ───────────────────────── Lifecycle ─────────────────────────
private void OnEnable()
{
SetupMaterial();
}
private void OnDisable()
{
CleanupMaterial();
}
private void Update()
{
if (autoPlay && Application.isPlaying && cycleDuration > 0f)
{
timeOfDay += Time.deltaTime / cycleDuration;
if (loop)
timeOfDay %= 1f;
else
timeOfDay = Mathf.Clamp01(timeOfDay);
}
Apply();
}
private void OnValidate()
{
SetupMaterial();
Apply();
}
// ───────────────────────── Core ─────────────────────────
public void Apply()
{
float t = Mathf.Clamp01(timeOfDay);
StageSettings current = EvaluateStage(t);
// ── Skybox ──
Material mat = GetActiveMaterial();
if (mat != null)
{
mat.SetFloat(BlendProp, t);
mat.SetColor(TintProp, current.skyboxTint);
mat.SetFloat(ExposureProp, current.skyboxExposure);
}
// ── Custom Material Properties ──
foreach (var prop in materialPropertyOverrides)
{
if (prop.targetMaterial == null || string.IsNullOrEmpty(prop.propertyName)) continue;
if (!prop.targetMaterial.HasProperty(prop.propertyName)) continue;
int id = Shader.PropertyToID(prop.propertyName);
switch (prop.propertyType)
{
case MaterialPropertyType.Float:
{
float v = t < 0.5f
? Mathf.Lerp(prop.floatStage1, prop.floatStage2, t * 2f)
: Mathf.Lerp(prop.floatStage2, prop.floatStage3, (t - 0.5f) * 2f);
prop.targetMaterial.SetFloat(id, v);
break;
}
case MaterialPropertyType.Color:
{
Color c = t < 0.5f
? Color.Lerp(prop.colorStage1, prop.colorStage2, t * 2f)
: Color.Lerp(prop.colorStage2, prop.colorStage3, (t - 0.5f) * 2f);
prop.targetMaterial.SetColor(id, c);
break;
}
case MaterialPropertyType.Vector:
{
Vector4 v = t < 0.5f
? Vector4.Lerp(prop.vectorStage1, prop.vectorStage2, t * 2f)
: Vector4.Lerp(prop.vectorStage2, prop.vectorStage3, (t - 0.5f) * 2f);
prop.targetMaterial.SetVector(id, v);
break;
}
}
}
// ── Directional Light ──
if (directionalLight != null)
{
directionalLight.color = current.lightColor;
directionalLight.intensity = current.lightIntensity;
directionalLight.transform.rotation = Quaternion.Euler(current.lightRotation);
}
// ── NiloToon Light Override ──
if (niloToonLightOverrider != null)
{
// Direction: 디렉셔널 라이트와 동일한 rotation 적용
niloToonLightOverrider.transform.rotation = Quaternion.Euler(current.lightRotation);
// Color
if (niloToonSeparateColor)
{
niloToonLightOverrider.color = t < 0.5f
? Color.Lerp(niloToonColorStage1, niloToonColorStage2, t * 2f)
: Color.Lerp(niloToonColorStage2, niloToonColorStage3, (t - 0.5f) * 2f);
}
else
{
niloToonLightOverrider.color = current.lightColor;
}
// Intensity
if (niloToonSeparateIntensity)
{
niloToonLightOverrider.intensity = t < 0.5f
? Mathf.Lerp(niloToonIntensityStage1, niloToonIntensityStage2, t * 2f)
: Mathf.Lerp(niloToonIntensityStage2, niloToonIntensityStage3, (t - 0.5f) * 2f);
}
else
{
niloToonLightOverrider.intensity = current.lightIntensity;
}
}
// ── Ambient ──
if (controlAmbient)
{
Color amb = t < 0.5f
? Color.Lerp(ambientStage1, ambientStage2, t * 2f)
: Color.Lerp(ambientStage2, ambientStage3, (t - 0.5f) * 2f);
RenderSettings.ambientLight = amb;
}
}
public void SetTime(float t)
{
timeOfDay = Mathf.Clamp01(t);
Apply();
}
public Material GetActiveMaterial()
{
return _instanceMaterial != null ? _instanceMaterial : skyboxMaterial;
}
// ───────────────────────── Helpers ─────────────────────────
private StageSettings EvaluateStage(float t)
{
if (t < 0.5f)
return StageSettings.Lerp(stage1, stage2, t * 2f);
else
return StageSettings.Lerp(stage2, stage3, (t - 0.5f) * 2f);
}
private void SetupMaterial()
{
if (skyboxMaterial == null) return;
if (Application.isPlaying)
{
if (_instanceMaterial == null || _instanceMaterial.shader != skyboxMaterial.shader)
{
CleanupMaterial();
_instanceMaterial = new Material(skyboxMaterial);
_instanceMaterial.name = skyboxMaterial.name + " (Instance)";
}
}
else
{
_instanceMaterial = skyboxMaterial;
}
RenderSettings.skybox = _instanceMaterial;
}
private void CleanupMaterial()
{
if (_instanceMaterial != null && _instanceMaterial != skyboxMaterial)
{
if (Application.isPlaying)
Destroy(_instanceMaterial);
else
DestroyImmediate(_instanceMaterial);
}
_instanceMaterial = null;
}
// ───────────────────────── Data Structures ─────────────────────────
[Serializable]
public struct StageSettings
{
public Color lightColor;
public float lightIntensity;
public Vector3 lightRotation;
public float skyboxExposure;
public Color skyboxTint;
public static StageSettings Lerp(StageSettings a, StageSettings b, float t)
{
return new StageSettings
{
lightColor = Color.Lerp(a.lightColor, b.lightColor, t),
lightIntensity = Mathf.Lerp(a.lightIntensity, b.lightIntensity, t),
lightRotation = LerpRotation(a.lightRotation, b.lightRotation, t),
skyboxExposure = Mathf.Lerp(a.skyboxExposure, b.skyboxExposure, t),
skyboxTint = Color.Lerp(a.skyboxTint, b.skyboxTint, t),
};
}
private static Vector3 LerpRotation(Vector3 a, Vector3 b, float t)
{
return Quaternion.Slerp(Quaternion.Euler(a), Quaternion.Euler(b), t).eulerAngles;
}
}
public enum MaterialPropertyType
{
Float,
Color,
Vector,
}
[Serializable]
public class MaterialPropertyOverride
{
[Tooltip("제어할 머티리얼")]
public Material targetMaterial;
[Tooltip("셰이더 프로퍼티 이름 (예: _Metallic, _Color)")]
public string propertyName;
public MaterialPropertyType propertyType = MaterialPropertyType.Float;
// Float
public float floatStage1;
public float floatStage2;
public float floatStage3;
// Color
public Color colorStage1 = Color.white;
public Color colorStage2 = Color.white;
public Color colorStage3 = Color.white;
// Vector
public Vector4 vectorStage1;
public Vector4 vectorStage2;
public Vector4 vectorStage3;
}
}
}