Tallow is released in two series while the ECS engine settles. 0.1 is the standard engine. 0.2 is the same editor on an entity-component-system (ECS) core, and it will become the main engine. Scripts are written in Beef in both, and they look alike, but 0.2 changes how the update callback works and adds new ways to work with data.
| 0.1 standard | 0.2 ECS | |
|---|---|---|
| Base class | Component |
Script (Component still works) |
| Per-frame logic | Update() with Time.DeltaTime |
Update(TickInput input, float deltaTime) |
| Networked logic | NetworkScript.OnTick(TickInput, float) |
The same Update: per tick when the object has a Network Identity |
| Controls | Input.GetKeyDown(.Space) |
input.Pressed(.Jump) |
| Local-only effects and UI | Update, OnGUI |
OnLocalUpdate, LateUpdate, OnGUI |
| Bulk data | Components and lists | [Component] struct data, Query<...>, plain entities |
| API reference | API → 0.1 | API → 0.2 |
0.1: components with Update
A component is a class with public fields and callbacks. Update runs once a frame and reads the frame time and the keyboard directly.
using Tallow;
namespace MyGame;
[Component]
class Rotator : Component
{
public Vector3 degreesPerSecond = .(0, 90, 0);
public bool spinning = true;
public override void Update()
{
if (Input.GetKeyDown(.Space))
spinning = !spinning;
if (spinning)
Transform.Rotate(degreesPerSecond * Time.DeltaTime);
}
public override void OnGUI()
{
GUI.Label(20, 20, scope $"Spinning: {spinning}");
}
}The callbacks are Awake, OnEnable, Start, FixedUpdate (60 Hz), Update, LateUpdate, OnGUI, OnDisable and OnDestroy.
Networked players are written separately, as a NetworkScript whose OnTick runs once per network tick on the server and, predicted, on the player’s machine:
[Component]
class Hover : NetworkScript
{
public float speed = 5;
[Sync] public int32 score;
public override void OnTick(TickInput input, float deltaTime)
{
Transform.Position += input.MoveDirection * speed * deltaTime;
if (IsServer && input.Pressed(.Use))
score++;
}
}0.2: one Update for everything
In 0.2 every script has a single Update(TickInput input, float deltaTime), and the engine decides when it runs. On an object with a Network Identity it runs once per network tick, predicted and replayed. On any other object it runs once a frame with this machine’s controls. The same script works in single player and multiplayer with no changes.
using Tallow;
namespace MyGame;
[Component]
class Rotator : Script
{
public Vector3 degreesPerSecond = .(0, 90, 0);
[Sync] public bool spinning = true;
public override void Update(TickInput input, float deltaTime)
{
if (input.Pressed(.Jump))
spinning = !spinning;
if (spinning)
Transform.Rotate(degreesPerSecond * deltaTime);
}
// IsMine: this machine controls the object.
public override void OnGUI()
{
if (IsMine)
GUI.LabelFormat(20, 20, "Spinning: {}", spinning);
}
}Three rules make this work:
- Take controls from
input, not fromInput, and time fromdeltaTime, not fromTime.DeltaTime. The Console underlines code that breaks this and links to the line. - Put work that is only for this machine (effects, sounds, cameras, UI) in
OnLocalUpdate(),LateUpdate()orOnGUI(). These are never networked or replayed. Updatecan run more than once for the same tick after a network correction, so do one-off things insideif (IsServer)orif (!IsReplaying).
NetworkScript still exists in 0.2: it adds the Network Identity for you, along with mouse-look settings and ReadInput. Its code goes in Update, the same as any other script.
0.2: data components
In 0.2 a GameObject is an ECS entity, and you can attach plain data to it: a struct marked [Component]. It shows in the Inspector, saves with the scene, and its [Sync] fields are networked when the object has a Network Identity.
[Component]
struct SpeedData
{
[Sync] public float speed;
}
[Component]
class Accelerate : Script
{
public override void Update(TickInput input, float deltaTime)
{
let speed = GetComponent<SpeedData>();
if (speed != null)
speed.speed += deltaTime;
}
}Keep values and engine references in data components, not objects the struct would own.
0.2: queries for many objects at once
When the profiler shows many objects doing the same work, a query walks their data in packed arrays instead of calling one script per object. Queries can be spread over the job threads.
[Component]
struct Boid
{
public Vector3 position;
public Vector3 velocity;
}
[Component]
class Swarm : Script
{
public int32 count = 100000;
List<Entity> mEntities = new .() ~ delete _;
Query<Boid> mQuery ~ delete _;
public override void Start()
{
Scene.World.CreateMany<Boid>(count, mEntities); // plain entities: no GameObject each
mQuery = new .(Scene.World);
}
public override void Update(TickInput input, float deltaTime)
{
// Spread over the job threads; each chunk is a packed array.
mQuery.ParallelFor(scope (chunk) =>
{
let boids = chunk.Write<Boid>();
for (int i < chunk.Count)
boids[i].position += boids[i].velocity * deltaTime;
});
}
}Scene.World.ObjectsWith<T>()is a ready-made cached query over scene GameObjects that have the dataT. Don’t delete it.- A query you create belongs to one world: keep it while the world lives and delete it before the world goes.
- Inside
ParallelFor, only change the row you are given. Don’t create or destroy entities, add or remove components, or touch shared objects. - Table order can change, so don’t rely on it for gameplay order, spawning or random numbers.
See Advanced systems and ordinary scripts for ordering, World.AddStep and measuring.
Visual scripting
Graphs work in both series and compile to a Beef component. In 0.2 the graph search has an ECS category: Has, Add and Remove for data components, Get and Set for their fields, and a For Each loop over every object with a given data type.
Moving a 0.1 project to 0.2
- Change
: Componentto: Scripton your scripts. This is optional, but it makes the intent clear. - Replace
public override void Update()withpublic override void Update(TickInput input, float deltaTime). - Use
deltaTimeinstead ofTime.DeltaTime, andinput(input.Pressed(.Jump),input.Move) instead ofInput. Code that has to read the keyboard directly, such as menus and debug keys, moves toOnLocalUpdate(). - Rename
NetworkScript.OnTicktoUpdate. - Build, then follow the Console’s determinism warnings: each one links to the line to change.
Scenes, prefabs, materials and assets open as they are.
Which should I use?
New projects can start on 0.2. That is where engine work is going, and its scripts run unchanged whether or not the game is networked. Choose 0.1 for an existing project you don’t want to change yet. 0.1 keeps getting fixes and the editor improvements that don’t change its API.