using System.Collections; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif // Atrybut [InitializeOnLoad] musi być tutaj, nad deklaracją klasy. // Dzięki niemu statyczny konstruktor tej klasy zostanie wywołany automatycznie przez edytor. #if UNITY_EDITOR [InitializeOnLoad] #endif [ExecuteAlways] [RequireComponent(typeof(Collider))] public class URPFogZone_Master : MonoBehaviour { #region Ustawienia Publiczne [Header("Ustawienia Docelowe Mgły")] public bool targetFogEnabled = true; public Color targetFogColor = new Color(0.5f, 0.5f, 0.5f); public FogMode targetFogMode = FogMode.Exponential; [Header("Ustawienia dla trybu Exponential / Exp2")] [Range(0f, 1f)] public float targetFogDensity = 0.02f; [Header("Ustawienia dla trybu Linear")] public float targetFogStartDistance = 0f; public float targetFogEndDistance = 300f; [Header("Ustawienia Przejścia (tylko w trybie gry)")] public float transitionDuration = 2.0f; public string triggerTag = "MainCamera"; #endregion #region Pola Prywatne i Statyczne private static bool s_defaultsSaved = false; private static bool s_defaultFogEnabled; private static Color s_defaultFogColor; private static FogMode s_defaultFogMode; private static float s_defaultFogDensity; private static float s_defaultFogStartDistance; private static float s_defaultFogEndDistance; private static URPFogZone_Master s_activeZone = null; private Coroutine transitionCoroutine; private Collider zoneCollider; #endregion #region Logika dla Edytora #if UNITY_EDITOR // Statyczny konstruktor. Wywoływany dzięki atrybutowi [InitializeOnLoad] nad klasą. static URPFogZone_Master() { EditorApplication.playModeStateChanged += OnPlayModeStateChanged; } private static void OnPlayModeStateChanged(PlayModeStateChange state) { if (state == PlayModeStateChange.ExitingPlayMode) { s_defaultsSaved = false; s_activeZone = null; } } private void OnValidate() { if (Application.isPlaying) return; if (s_activeZone == this) { ApplyTargetSettingsDirectly(); } } private void EditorUpdate() { if (Application.isPlaying) return; var sceneView = SceneView.lastActiveSceneView; if (sceneView == null || sceneView.camera == null) return; if (zoneCollider == null) zoneCollider = GetComponent(); if (zoneCollider == null) return; bool isCurrentlyInside = zoneCollider.bounds.Contains(sceneView.camera.transform.position); if (isCurrentlyInside && s_activeZone != this) { s_activeZone = this; SaveDefaultFogSettings(); ApplyTargetSettingsDirectly(); } else if (!isCurrentlyInside && s_activeZone == this) { s_activeZone = null; RestoreDefaultSettings(); } } #endif #endregion #region Cykl Życia Obiektu private void OnEnable() { zoneCollider = GetComponent(); zoneCollider.isTrigger = true; #if UNITY_EDITOR if (!Application.isPlaying) { EditorApplication.update += EditorUpdate; } #endif } private void OnDisable() { if (s_activeZone == this) { if (transitionCoroutine != null) StopCoroutine(transitionCoroutine); RestoreDefaultSettings(); s_activeZone = null; } #if UNITY_EDITOR EditorApplication.update -= EditorUpdate; #endif } #endregion #region Logika Trybu Gry private void OnTriggerEnter(Collider other) { if (!Application.isPlaying || !other.CompareTag(triggerTag)) return; SaveDefaultFogSettings(); s_activeZone = this; StartTransition(true); } private void OnTriggerExit(Collider other) { if (!Application.isPlaying || !other.CompareTag(triggerTag)) return; if (s_activeZone == this) { s_activeZone = null; StartTransition(false); } } #endregion // ================================================================================= #region NOWOŚĆ: Publiczne Metody do sterowania przez Eventy (dla Cutscen) /// /// Aktywuje mgłę z tej strefy. Podepnij tę funkcję pod event na początku cutsceny. /// public void ActivateZoneFog() { Debug.Log($"[FogZone] Ręczna aktywacja mgły dla strefy: {gameObject.name}", this); SaveDefaultFogSettings(); s_activeZone = this; StartTransition(true); } /// /// Przywraca domyślne ustawienia mgły. Podepnij tę funkcję pod event na końcu cutsceny. /// public void RevertToDefaultFog() { // Sprawdzamy, czy TA strefa jest aktywna. To ważne zabezpieczenie. if (s_activeZone == this) { Debug.Log($"[FogZone] Ręczne przywracanie domyślnej mgły ze strefy: {gameObject.name}", this); s_activeZone = null; StartTransition(false); } } #endregion // ================================================================================= #region Metody Główne private static void SaveDefaultFogSettings() { if (s_defaultsSaved) return; s_defaultFogEnabled = RenderSettings.fog; s_defaultFogColor = RenderSettings.fogColor; s_defaultFogMode = RenderSettings.fogMode; s_defaultFogDensity = RenderSettings.fogDensity; s_defaultFogStartDistance = RenderSettings.fogStartDistance; s_defaultFogEndDistance = RenderSettings.fogEndDistance; s_defaultsSaved = true; } private static void RestoreDefaultSettings() { if (!s_defaultsSaved) return; RenderSettings.fog = s_defaultFogEnabled; RenderSettings.fogColor = s_defaultFogColor; RenderSettings.fogMode = s_defaultFogMode; RenderSettings.fogDensity = s_defaultFogDensity; RenderSettings.fogStartDistance = s_defaultFogStartDistance; RenderSettings.fogEndDistance = s_defaultFogEndDistance; #if UNITY_EDITOR SceneView.RepaintAll(); #endif } private void ApplyTargetSettingsDirectly() { RenderSettings.fog = targetFogEnabled; RenderSettings.fogMode = targetFogMode; RenderSettings.fogColor = targetFogColor; RenderSettings.fogDensity = targetFogDensity; RenderSettings.fogStartDistance = targetFogStartDistance; RenderSettings.fogEndDistance = targetFogEndDistance; #if UNITY_EDITOR SceneView.RepaintAll(); #endif } private void StartTransition(bool toTarget) { if (transitionCoroutine != null) { StopCoroutine(transitionCoroutine); } transitionCoroutine = StartCoroutine(TransitionFogCoroutine(toTarget)); } private IEnumerator TransitionFogCoroutine(bool toTarget) { float elapsed = 0f; Color startColor = RenderSettings.fogColor; float startDensity = RenderSettings.fogDensity; float startStartDist = RenderSettings.fogStartDistance; float startEndDist = RenderSettings.fogEndDistance; Color finalColor = toTarget ? targetFogColor : s_defaultFogColor; float finalDensity = toTarget ? targetFogDensity : s_defaultFogDensity; float finalStartDist = toTarget ? targetFogStartDistance : s_defaultFogStartDistance; float finalEndDist = toTarget ? targetFogEndDistance : s_defaultFogEndDistance; if (toTarget) { RenderSettings.fog = targetFogEnabled; RenderSettings.fogMode = targetFogMode; } else { RenderSettings.fogMode = s_defaultFogMode; } if (transitionDuration <= 0f) { RenderSettings.fogColor = finalColor; RenderSettings.fogDensity = finalDensity; RenderSettings.fogStartDistance = finalStartDist; RenderSettings.fogEndDistance = finalEndDist; if (!toTarget) RenderSettings.fog = s_defaultFogEnabled; yield break; } while (elapsed < transitionDuration) { // Używam unscaledDeltaTime, aby działało poprawnie w cutscenach elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / transitionDuration); RenderSettings.fogColor = Color.Lerp(startColor, finalColor, t); RenderSettings.fogDensity = Mathf.Lerp(startDensity, finalDensity, t); RenderSettings.fogStartDistance = Mathf.Lerp(startStartDist, finalStartDist, t); RenderSettings.fogEndDistance = Mathf.Lerp(startEndDist, finalEndDist, t); yield return null; } if (!toTarget) { RenderSettings.fog = s_defaultFogEnabled; } transitionCoroutine = null; } #endregion }