using System.Collections; using System.Collections.Generic; using Unheimlich.VFX; using UnityEngine; using UnityEngine.Rendering; public class ScarecrowFire : MonoBehaviour { [System.Serializable] public class FireElement { public GameObject VFX; [HideInInspector] public Vector3 OriginalScale; } [Header("Fire VFX Elements")] [SerializeField] private FireElement _fireBase; [SerializeField] private FireElement _fireTorso; [SerializeField] private FireElement _fireArmLeft; [SerializeField] private FireElement _fireArmLeftEnd; [SerializeField] private FireElement _fireArmRight; [SerializeField] private FireElement _fireArmRightEnd; [SerializeField] private FireElement _fireHead; [SerializeField] private FireElement _fireBig; [Header("Propagation Timings")] [SerializeField] private float _growDuration = 3f; [Range(0f, 1f)] [SerializeField] private float _overlapFactor = 0.4f; [SerializeField] private AnimationCurve _growCurve = new AnimationCurve( new Keyframe(0f, 0f, 0f, 0f), new Keyframe(0.3f, 0.1f, 0f, 2f), new Keyframe(0.7f, 0.8f, 2f, 0.5f), new Keyframe(1f, 1f, 0.2f, 0f) ); [Header("Post Process")] [SerializeField] private Volume _heatVolume; [SerializeField] private AnimationCurve _heatCurve = new AnimationCurve( new Keyframe(0f, 0f, 0f, 0.5f), new Keyframe(0.5f, 0.4f, 1f, 1f), new Keyframe(1f, 1f, 0.5f, 0f) ); [Header("Heat Shader")] [SerializeField] private Material _heatMaterial; [Header("Lights")] [SerializeField] private List _lights = new List(); [System.Serializable] public class LightEntry { public LightFlicker Flicker; public float TargetIntensity = 3f; } private bool _fireSequenceComplete = false; private float _totalSequenceDuration = 0f; // ───────────────────────────────────────────────────────────────────────── void Awake() { InitElement(_fireBase); InitElement(_fireTorso); InitElement(_fireArmLeft); InitElement(_fireArmLeftEnd); InitElement(_fireArmRight); InitElement(_fireArmRightEnd); InitElement(_fireHead); InitElement(_fireBig); float overlapDelay = _growDuration * (1f - _overlapFactor); _totalSequenceDuration = _growDuration + (7f * overlapDelay); SetHeatShaderWeight(0f); foreach (var entry in _lights) if (entry.Flicker) entry.Flicker.SetBaseIntensity(0f); } void Start() { TriggerFire(); } private void InitElement(FireElement el) { if (!el.VFX) return; el.OriginalScale = el.VFX.transform.localScale; el.VFX.transform.localScale = new Vector3( el.OriginalScale.x * 0.2f, el.OriginalScale.y * 0.1f, el.OriginalScale.z * 0.2f ); el.VFX.SetActive(false); } // ───────────────────────────────────────────────────────────────────────── public void TriggerFire() { _fireSequenceComplete = false; StartCoroutine(FireSequence()); } private IEnumerator FireSequence() { float overlapDelay = _growDuration * (1f - _overlapFactor); StartCoroutine(DriveHeatEffects()); StartCoroutine(GrowFire(_fireBase)); yield return new WaitForSeconds(overlapDelay); StartCoroutine(GrowFire(_fireTorso)); yield return new WaitForSeconds(overlapDelay); StartCoroutine(PropagateArm(_fireArmLeft, _fireArmLeftEnd, overlapDelay)); StartCoroutine(PropagateArm(_fireArmRight, _fireArmRightEnd, overlapDelay)); yield return new WaitForSeconds(overlapDelay * 2f); StartCoroutine(GrowFire(_fireHead)); yield return new WaitForSeconds(overlapDelay); yield return StartCoroutine(GrowFire(_fireBig)); _fireSequenceComplete = true; } private IEnumerator PropagateArm(FireElement shoulder, FireElement tip, float overlapDelay) { StartCoroutine(GrowFire(shoulder)); yield return new WaitForSeconds(overlapDelay); yield return StartCoroutine(GrowFire(tip)); } private IEnumerator GrowFire(FireElement el) { if (!el.VFX) yield break; el.VFX.SetActive(true); Vector3 startScale = Vector3.zero; Vector3 targetScale = el.OriginalScale; float elapsed = 0f; while (elapsed < _growDuration) { elapsed += Time.deltaTime; float t = Mathf.Clamp01(elapsed / _growDuration); float curveT = _growCurve.Evaluate(t); el.VFX.transform.localScale = Vector3.Lerp(startScale, targetScale, curveT); yield return null; } el.VFX.transform.localScale = targetScale; } // ───────────────────────────────────────────────────────────────────────── private IEnumerator DriveHeatEffects() { float elapsed = 0f; float lastWeight = 0f; // Phase 1 : monte avec la heatCurve jusqu'à ce que la séquence feu soit finie. // On snapshote lastWeight à chaque frame pour partir de la vraie valeur courante // en phase 2, évitant tout creux si la courbe redescend après t=1. while (!_fireSequenceComplete) { elapsed += Time.deltaTime; float t = elapsed / _totalSequenceDuration; lastWeight = _heatCurve.Evaluate(Mathf.Clamp01(t)); ApplyHeatWeight(lastWeight); yield return null; } // Phase 2 : lerp propre de la valeur courante vers 1, sans jamais redescendre. float lerpElapsed = 0f; float lerpDuration = 8f; float startWeight = lastWeight; // ← valeur réelle au moment du flag, pas heatCurve(0.99) while (lerpElapsed < lerpDuration) { lerpElapsed += Time.deltaTime; float weight = Mathf.Lerp(startWeight, 1f, lerpElapsed / lerpDuration); ApplyHeatWeight(weight); yield return null; } // Phase 3 : maintien à 1 à l'infini. ApplyHeatWeight(1f); } /// Applique un weight normalisé à tous les effets de chaleur + lumière. private void ApplyHeatWeight(float weight) { if (_heatVolume) _heatVolume.weight = weight; SetHeatShaderWeight(weight); // ScarecrowFire pilote la base — LightFlicker applique le flicker par-dessus // sans écrire directement sur targetLight.intensity. foreach (var entry in _lights) if (entry.Flicker) entry.Flicker.SetBaseIntensity(entry.TargetIntensity * weight); } private void SetHeatShaderWeight(float weight) { if (!_heatMaterial) return; _heatMaterial.SetFloat("_Weight", weight); } }