Source file unity-csharp/systemic-design/ChemistryRules.cs from the GDnD code examples. Download raw file
// GDnD wiki example (1 of 3: ChemistryRules.cs, ChemistryBody.cs, ElementSource.cs)
// Demonstrates: a small "chemistry engine" in the style described for The Legend of Zelda:
// Breath of the Wild (Dohta, GDC 2017). Elements (fire, water, ice, electricity, wind) carry
// state changes; materials (anything with a ChemistryBody: crates, grass, enemies, the player)
// have tags that decide how they react. Three rules, applied the same way to everything:
// 1. Elements can change the state of materials (fire sets a Flammable crate burning).
// 2. Elements can change each other (water puts out fire, fire melts ice into water).
// 3. Materials cannot change each other directly - only through an element they give off.
// Physics stays separate: Rigidbody2D moves things; the chemistry code only changes state and
// hands over to physics in one place (wind pushes Light bodies, see ChemistryBody).
// This file holds the data types and the whole rule table, so every object obeys the same rules.
// Related pages: [[breath-of-the-wild]], [[emergent-design]], [[game-atoms]],
// [[source-botw-breaking-conventions-gdc-2017]], [[unity-collider2d-and-triggers]]
// Unity 6 (6000.x), 2D physics. No input code, so it works with either input backend.
using System;
namespace GDnD.Systemic.Chemistry
{
public enum Element { None, Fire, Water, Ice, Electricity, Wind }
[Flags]
public enum MaterialTags
{
None = 0,
Flammable = 1 << 0, // wood, grass, cloth: catches fire
Conductive = 1 << 1, // metal: carries electricity even when dry
Freezable = 1 << 2, // can be frozen solid by ice even when dry
Light = 1 << 3, // pushed by wind
}
public enum MaterialState { Normal, Burning, Burnt, Wet, Frozen, Electrified }
public static class ChemistryRules
{
// Rule 2: what an element source becomes when another element reaches it.
public static Element Combine(Element present, Element incoming) => (present, incoming) switch
{
(Element.Fire, Element.Water) => Element.None, // water extinguishes fire
(Element.Ice, Element.Fire) => Element.Water, // fire melts ice
(Element.Water, Element.Ice) => Element.Ice, // ice freezes water
_ => present, // everything else: no change
};
// Rule 1: how an element changes a material's state, given its tags and current state.
public static MaterialState Apply(Element element, MaterialTags tags, MaterialState current)
{
if (current == MaterialState.Burnt) return current; // burnt is final
return element switch
{
Element.Fire when current == MaterialState.Frozen => MaterialState.Wet, // thaws
Element.Fire when current == MaterialState.Wet => MaterialState.Normal, // dries out
Element.Fire when tags.HasFlag(MaterialTags.Flammable) => MaterialState.Burning,
Element.Water => MaterialState.Wet, // also puts out Burning
Element.Ice when tags.HasFlag(MaterialTags.Freezable) || current == MaterialState.Wet
=> MaterialState.Frozen,
Element.Electricity when tags.HasFlag(MaterialTags.Conductive) || current == MaterialState.Wet
=> MaterialState.Electrified,
_ => current,
};
}
// The element a body gives off in a given state. Burning things spread fire; that is
// the only way one material affects another (rule 3).
public static Element Emits(MaterialState state) =>
state == MaterialState.Burning ? Element.Fire : Element.None;
}
}