Core Concepts

The Engine and Game Project

Chisel is split into two projects: the Engine and your game project. The engine handles all the low-level systems: physics, rendering, sound, entity management, and so on. Your game project references the engine and sits on top of it, providing all game-specific logic.

Your game’s entry point is a class that inherits from MainEngine. The FPS template calls this GameEngine. This is where you set your game name, load content, load your first map, and hook into engine events. All the high-level setup you’d normally do in a MonoGame Game class happens here instead.

Entities

Everything that exists in the world is built from two parts working together: a WorldEntity and an EntityController.

The WorldEntity is the data side. It holds position, rotation, scale, physics body, properties, and the input/output connections set up in the editor. You create one by inheriting from WorldEntity and configuring it in the constructor:

[EntityDescriptor()]
public class MyEnemy : WorldEntity
{
    public MyEnemy()
    {
        controller = new MyEnemyController();
        bounds = new BoundingBox(-new Vector3(0.4f, 2.0f, 0.4f), new Vector3(0.4f, 0.15f, 0.4f));
        isSimulated = true;
        axisAlignedBox = true;
    }
}

Always set the controller and bounds in the constructor. Setting the controller here ensures OnSpawn is called correctly when the entity enters the world. Setting bounds here gives the physics engine something to work with immediately; without a valid bounding box the physics body won’t be created and the entity won’t collide with anything.

The EntityController is the logic side. This is where all the behavior lives. It receives callbacks throughout the entity’s lifetime:

OnSpawn()

Called when the entity enters the world. Initialize state, register inputs, set up your model, and prepare anything the entity needs here.

OnUpdate(GameTime)

Called every frame. This is where your game logic lives.

OnRender(GameTime)

Called every frame during the render pass. Draw your model here.

OnBeforeRender(GameTime)

Called just before the main render pass. Used for rendering shadow textures and other pre-pass work.

OnTakeDamage(DamageInfo)

Called when the entity receives damage from entity.TakeDamage().

OnDespawn()

Called when the entity is removed from the world. Free resources and clean up here.

Prefetch()

Called once per class on engine startup, before any instances are spawned. You can cache data here.

From inside a controller, access the parent entity via entity. For example: entity.position, entity.velocity, or entity.AddImpulse().

Key Entity Flags

Several boolean flags on WorldEntity control fundamental behavior:

isSimulated

When true, the physics engine drives this entity. Its position and velocity are updated by the physics simulation each frame. Set false for entities that move only when you tell them to, like lights or logic entities.

axisAlignedBox

When true, the physics body is locked to only translate, not rotate. Characters and enemies should have this set so they can’t tip over. Physics props you want to tumble should have it false.

ignoreCollision

When true, other entities and brushes pass through this entity. Used for dead ragdolls, triggers, and entities that need to coexist with the world without blocking anything.

isLight

Marks this entity as a light source. Light entities are excluded from the normal update and render loops and handled separately by the engine. You should never use this.

The [EntityDescriptor] Attribute

[EntityDescriptor] on a WorldEntity subclass is what makes it visible to the editor and the compiler. Without it, the entity won’t appear in Rockwall 2’s entity list and can’t be placed in a map. This is intentional: entities that exist purely as base classes or that are purely decorative don’t need to be visible in the editor, though be advised that only entities with this header can be saved and loaded.

After adding a new entity with [EntityDescriptor], run your game once with -compile to regenerate the definition file. See Editor Setup for details on why this matters.

Entity Properties

Entity properties are the values you configure per-instance in the editor inspector. Declare them with [ExposeEntityProperty] on the WorldEntity class:

[ExposeEntityProperty(“Move Speed”, EntityPropertyType.Float, “Units per second.”)]
[ExposeEntityProperty(“Alert Sound”, EntityPropertyType.Sound, “Played when this enemy spots the player.”)]
[EntityDescriptor()]
public class GuardEnemy : WorldEntity { … }

The available property types are Color, Position, Direction, Float, Texture, Model, Material, Sound, and String. Read values back in code with entity.ReadProperty():

float speed = (float)entity.ReadProperty(“Move Speed”, EntityPropertyType.Float);
string sound = (string)entity.ReadProperty(“Alert Sound”, EntityPropertyType.Sound);

All properties are stored as strings internally and parsed when you read them. ReadProperty will throw if the property is null or blank, so check first or use a try/catch if designers might leave a field empty.

Entity Inputs and Outputs

Chisel uses a map logic system nearly identical to Source Engine’s. Entities expose named outputs that fire when something happens and named inputs that other entities can call. Connections between them are set up entirely in Rockwall 2.

Declare them with attributes on your WorldEntity subclass:

[RegisterEntityOutputs(“OnActivated”, “OnDestroyed”)]
[RegisterEntityInputs(“Activate”, “Destroy”)]
[EntityDescriptor()]
public class MyEntity : WorldEntity { … }

Declaring them with attributes makes them visible in the editor’s outputs panel. To wire up actual logic for an input, call RegisterInputLocally() from OnSpawn():

RegisterInputLocally(“Destroy”, (s, f) => {
    EntityManager.DespawnEntity(entity);
});

Fire an output from code by calling entity.CallOutput(). The engine automatically invokes whatever connections the level designer has set up:

entity.CallOutput(“OnActivated”, this);
Note

See Advanced Outputs for delays, refire counts, the _activator target, and pass variables.

The EntityManager

EntityManager is the static class that owns all active entities. The most useful methods are:

EntityManager.SpawnEntity(entity)

Adds an entity to the world. Calls OnSpawn immediately and queues it to appear in the entity list on the next frame.

EntityManager.DespawnEntity(entity)

Removes an entity from the world. Calls OnDespawn immediately and removes it from the list on the next frame.

EntityManager.entities.GetValues()

Returns the full array of currently active entities. Iterate this when you need to find entities by type or condition.

The world has a hard cap of 1024 entities at any time. This is the combined count of everything: players, enemies, props, lights, logic entities, and all brush entities.

Maps and Brushes

A map is a level file. It contains all the brushes (solid geometry) and entities that make up a level. Maps are created in Rockwall 2.

Raw maps are stored as .rok files, a JSON format the editor reads. Before the engine can load a map it has to be compiled. The compiler processes the .rok file and produces .clm and .cmap files, which the engine reads at runtime. Compiled maps include baked lighting, BSP tree data, PVS culling, and final mesh geometry.

Brushes are the solid geometry of a map: walls, floors, ceilings, platforms. They’re convex solids defined by a collection of planes, similar to Quake or Source. Faces can have individual materials applied to them. Brushes can also be marked as trigger volumes, turning them into invisible regions that fire events when entities enter them. A BrushEntity ties a brush to an entity, giving it behavior like a door or a moving platform.

Note

You’ll never work with .clm or .cmap files directly. Always work with .rok source files in Rockwall 2 and let the compiler handle the rest.

The Frame Loop

Understanding the order things happen each frame helps when debugging or doing precise multi-system work. Each frame runs in this order (this is already handled in MainEngine.cs, hence why you call base.Update):

1. SaveManager.Handle()

Applies any pending save or load operation from the previous frame.

2. EntityManager.UpdateEntities()

Processes all pending spawn and despawn queues, then calls OnUpdate on every entity controller.

3. PhysicsEngine.Update()

Steps the physics simulation, integrating forces and resolving collisions.

4. EntityManager.PostUpdateEntities()

Reads physics results back into entity position and velocity, handles stepping, and processes queued tasks (delayed outputs).

5. ParticleManager.UpdateSystems()

Ticks all active particle systems.

6. Draw

Calls OnBeforeRender then OnRender on all entities, then composites the scene and draws the UI.

To make everything as seamless as possible, the entity manager makes an active effort to merge the physics and update results. Typically you dont have to think about the physics engine, and setting entity position or velocity will update everything as you’d expect.