// GDnD wiki example (2 of 3: ChemistryRules.cs, ChemistryBody.cs, ElementSource.cs) // Demonstrates: the "material" side of a Breath of the Wild-style chemistry engine. Put this // component on props, enemies AND the player, so one rule set (ChemistryRules) governs all of // them. It stores the current MaterialState, raises StateChanged for VFX/audio/damage, times // out temporary states, and spreads fire to neighbours by passing them an element (rule 3). // Related pages: [[breath-of-the-wild]], [[emergent-design]], [[game-atoms]], // [[source-botw-breaking-conventions-gdc-2017]] // Unity 6 (6000.x), 2D physics. // // Setup: add to any GameObject with a Collider2D and tick its Material Tags. Add a Rigidbody2D // too if wind should push it (tag it Light). Listen to StateChanged from another script to // swap sprites, play particles or apply damage (e.g. the player takes damage while Burning). using System; using System.Collections.Generic; using UnityEngine; namespace GDnD.Systemic.Chemistry { public class ChemistryBody : MonoBehaviour { [SerializeField] private MaterialTags tags = MaterialTags.Flammable; [SerializeField] private float burnDuration = 4f; // Burning -> Burnt after this [SerializeField] private float shockDuration = 1f; // Electrified -> Wet/Normal after this [SerializeField] private float spreadRadius = 0.75f; [SerializeField] private float spreadInterval = 0.5f; [SerializeField] private float windForce = 20f; public MaterialState State { get; private set; } = MaterialState.Normal; public MaterialTags Tags => tags; public event Action StateChanged; private readonly List neighbours = new(); private MaterialState stateBeforeShock; private float stateTimer; private float spreadTimer; private Rigidbody2D body; private void Awake() => body = GetComponent(); // The only way anything changes this body's state: an element arrives. public void ReceiveElement(Element element, Vector2 direction = default) { // Hand-off to physics: wind is the one element that moves things. ElementSource calls // this every physics step while the body is inside a gust, so use continuous force. if (element == Element.Wind && tags.HasFlag(MaterialTags.Light) && body != null) body.AddForce(direction.normalized * windForce, ForceMode2D.Force); MaterialState next = ChemistryRules.Apply(element, tags, State); if (next == MaterialState.Electrified && State != MaterialState.Electrified) stateBeforeShock = State; SetState(next); } private void SetState(MaterialState next) { if (next == State) return; State = next; stateTimer = 0f; StateChanged?.Invoke(State); } private void Update() { stateTimer += Time.deltaTime; if (State == MaterialState.Burning && stateTimer >= burnDuration) SetState(MaterialState.Burnt); if (State == MaterialState.Electrified && stateTimer >= shockDuration) SetState(stateBeforeShock); Element emitted = ChemistryRules.Emits(State); if (emitted == Element.None || (spreadTimer += Time.deltaTime) < spreadInterval) return; spreadTimer = 0f; // Rule 3: never edit a neighbour's state directly; pass it an element instead. Physics2D.OverlapCircle(transform.position, spreadRadius, ContactFilter2D.noFilter, neighbours); foreach (Collider2D other in neighbours) if (other.gameObject != gameObject && other.TryGetComponent(out ChemistryBody target)) target.ReceiveElement(emitted); } } }