Game Design
Peter DeJong Attractor
The Noita Grid State System is a Unity-based grid simulation that models interacting materials like powders, liquids, gases, and solids with behaviours such as gravity, viscosity, and dynamic reactions.
KitKat Advertisement
May 2024A dive into learning C# and finding the most efficient way to calculate squart roots.
View projectProject Overview
What this project is
The Noita Grid State System is an experimental project where I attempted to recreate the core idea behind the simulation system used in Noita without researching how the original game implemented it. The goal was to explore how a grid-based particle simulation could support different material types interacting with each other in real time.
The result is a working sandbox that simulates elements such as powders, liquids, solids, and gases, each with their own behaviour rules. Materials respond to gravity, liquids flow depending on viscosity, and gases rise upward. Elements can also react with one another, allowing new materials to be created dynamically during the simulation. For example, when water and lava come into contact they produce stone and smoke.
The project originally started as a simple falling sand simulation, but gradually evolved into a more flexible system designed to support many different materials and interactions.
Problem
The challenge
The main challenge was designing a system that could simulate thousands of small particles interacting with each other while still remaining flexible enough to easily add new elements. Unlike traditional physics systems where objects are independent rigid bodies, this simulation works on a grid of cells where each cell represents a single particle of material. Each update step the simulation evaluates how particles should move and interact with neighbouring cells.
This means the system needs to handle several behaviours simultaneously:
- Gravity and falling particles
- Liquids flowing and spreading
- Powders forming piles
- Gases rising
- Chemical reactions between neighbouring materials
Another important challenge was expandability. Hardcoding behaviour for every element would quickly become difficult to maintain. Instead, the system needed to be designed in a way that made it easy to introduce new materials and interactions.
Interactive Demo
Playable browser build
This demo runs directly in your browser using a Unity WebGL build. Use your mouse and keyboard to interact with the simulation.
Notes
What is the interactive demo?
The demo on this page is a simplified sandbox built in Unity using a 2D grid simulation. Each visible pixel represents a particle in the simulation grid. When an element is placed into the world it immediately begins following the rules defined for that material.
Powders fall and accumulate naturally, liquids spread across surfaces depending on their viscosity, and gases rise upward through other materials. Certain elements can also react with each other, producing new materials as part of the simulation.
The purpose of this demo is not to perfectly replicate Noita, but to demonstrate how a grid-based particle system can support multiple material types and interactions in real time.
Approach
Element system [Scriptable Objects]
Elements in the simulation are defined using Unity Scriptable Objects. Each Scriptable Object represents a material and contains the properties that control how that element behaves inside the grid.
These properties include things such as gravity strength, viscosity, element type, and possible reactions with other elements. By defining elements as data rather than hardcoded logic, new materials can be created quickly inside the Unity editor without modifying the simulation code.
This makes the system highly expandable, allowing new materials such as lava, smoke, water, or acid to be introduced simply by creating new element definitions.
Code
using UnityEngine;
public enum ElementType { Empty, Powder, Liquid, Gas, Solid }
[CreateAssetMenu(menuName = "Simulation/Element")]
public class Element : ScriptableObject
{
[Header("Identity")]
public ushort id; // must be unique
public string displayName;
public Sprite sprite;
public ElementType type;
[Header("Visual")]
public Color32 color;
[Header("Physics")]
public byte dispersion = 2; // how far sideways it tries (liquid)
public byte fallSpeed = 1; // gravity
public short density = 10; // can be negative (smoke), positive (sand), etc.
public byte viscosity = 0; // 0 = water-ish, higher = syrup/soup
[Header("Reactions")]
public ReactionRule[] reactions;
}
[System.Serializable]
public class ReactionRule
{
public ushort withElementId; // neighbor element
public ushort resultSelfId; // what current cell becomes
public ushort resultOtherId; // what neighbor becomes
[Range(0f , 1f)] public float chance = 1f;
}
Technical Notes
Challenges and what I learned
Talk about the hardest parts, what changed during development, what trade-offs you made, and what you would improve next.
References