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);
}
}