using System.Collections; using UnityEngine; using UnityEngine.Rendering; public class ScarecrowFire : MonoBehaviour { [System.Serializable] public class FireElement { public GameObject VFX; /// Original scale captured at initialization, used as lerp target. [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")] [Tooltip("Duration of the scale lerp for each fire element (seconds).")] [SerializeField] private float _growDuration = 3f; [Tooltip("How early the next stage starts before the current one finishes (0 = no overlap, 1 = instant).")] [Range(0f, 1f)] [SerializeField] private float _overlapFactor = 0.4f; [Tooltip("Curve driving the scale growth — tweak directly in the Inspector.")] [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")] [Tooltip("Volume driving the heat haze effect — weight is driven up as fire spreads.")] [SerializeField] private Volume _heatVolume; [Tooltip("Curve driving all heat effects over the full sequence duration.")] [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")] [Tooltip("Material using the HeatHaze shader — _Weight property is driven from 0 to 1.")] [SerializeField] private Material _heatMaterial; [Header("Light")] [Tooltip("LightFlicker component to drive — baseIntensity is ramped up with the fire.")] [SerializeField] private MonoLimboStudio.LightFlicker _lightFlicker; [Tooltip("Final light intensity reached when the fire is fully ablaze.")] [SerializeField] private float _targetLightIntensity = 3f; // Flag set when the last GrowFire completes — DriveHeatEffects waits on this 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); // Used as a time reference for _heatCurve mapping — doesn't need to be exact // since DriveHeatEffects now syncs to _fireSequenceComplete rather than this value float overlapDelay = _growDuration * (1f - _overlapFactor); _totalSequenceDuration = _growDuration + (7f * overlapDelay); // Ensure everything starts at zero SetHeatShaderWeight(0f); if (_lightFlicker) _lightFlicker.SetBaseIntensity(0f); } void Start() { TriggerFire(); } /// /// Captures the original Inspector scale, then sets the starting scale to /// (x * 0.2, y * 0.1, z * 0.2) so each element grows from near-nothing. /// 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); } // ───────────────────────────────────────────────────────────────────────── /// Starts the full fire propagation sequence. public void TriggerFire() { _fireSequenceComplete = false; StartCoroutine(FireSequence()); } /// /// Propagates fire bottom-up with overlapping stages for an organic feel. /// Sets _fireSequenceComplete when the last element finishes growing. /// private IEnumerator FireSequence() { float overlapDelay = _growDuration * (1f - _overlapFactor); // Drive all heat effects independently — syncs via _fireSequenceComplete StartCoroutine(DriveHeatEffects()); // Feet StartCoroutine(GrowFire(_fireBase)); yield return new WaitForSeconds(overlapDelay); // Torso overlaps with feet still growing StartCoroutine(GrowFire(_fireTorso)); yield return new WaitForSeconds(overlapDelay); // Both arms ignite simultaneously, each propagating from shoulder to tip StartCoroutine(PropagateArm(_fireArmLeft, _fireArmLeftEnd, overlapDelay)); StartCoroutine(PropagateArm(_fireArmRight, _fireArmRightEnd, overlapDelay)); yield return new WaitForSeconds(overlapDelay * 2f); // Head StartCoroutine(GrowFire(_fireHead)); yield return new WaitForSeconds(overlapDelay); // Full blaze — wait for it to fully complete before flagging done yield return StartCoroutine(GrowFire(_fireBig)); _fireSequenceComplete = true; } /// /// Propagates fire along one arm: shoulder ignites first, /// then tip starts after overlapDelay rather than waiting for shoulder to finish. /// private IEnumerator PropagateArm(FireElement shoulder, FireElement tip, float overlapDelay) { StartCoroutine(GrowFire(shoulder)); yield return new WaitForSeconds(overlapDelay); yield return StartCoroutine(GrowFire(tip)); } /// /// Activates a fire element and lerps its scale from near-zero to its original /// Inspector scale, driven by _growCurve for an organic progression. /// private IEnumerator GrowFire(FireElement el) { if (!el.VFX) yield break; el.VFX.SetActive(true); Vector3 startScale = el.VFX.transform.localScale; 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; } // ───────────────────────────────────────────────────────────────────────── /// /// Drives all heat effects (post-process volume, heat shader, light intensity) /// using _heatCurve until _fireSequenceComplete is set, then snaps cleanly to 1. /// Decoupled from individual fire element timing for smoother blending. /// private IEnumerator DriveHeatEffects() { float elapsed = 0f; while (!_fireSequenceComplete) { elapsed += Time.deltaTime; float t = elapsed / _totalSequenceDuration; float weight = _heatCurve.Evaluate(Mathf.Min(t, 0.99f)); if (_heatVolume) _heatVolume.weight = weight; SetHeatShaderWeight(weight); if (_lightFlicker) _lightFlicker.SetBaseIntensity(_targetLightIntensity * weight); yield return null; } float lerpElapsed = 0f; float lerpDuration = 8f; float startWeight = _heatCurve.Evaluate(0.99f); while (lerpElapsed < lerpDuration) { lerpElapsed += Time.deltaTime; float weight = Mathf.Lerp(startWeight, 1f, lerpElapsed / lerpDuration); if (_heatVolume) _heatVolume.weight = weight; SetHeatShaderWeight(weight); if (_lightFlicker) _lightFlicker.SetBaseIntensity(_targetLightIntensity * weight); yield return null; } if (_heatVolume) _heatVolume.weight = 1f; SetHeatShaderWeight(1f); if (_lightFlicker) _lightFlicker.SetBaseIntensity(_targetLightIntensity); } /// /// Drives the heat shader via a single normalized weight (0-1). /// All visual parameters are tweaked directly on the material in the Inspector. /// private void SetHeatShaderWeight(float weight) { if (!_heatMaterial) return; _heatMaterial.SetFloat("_Weight", weight); } }