Interactive Actions & States
Describe runtime-interactive behaviour — doors that open, drawers that slide, lights that switch — directly in the MC3 source file.
MC3 has two complementary animation concepts:
- Keyframe animation (the
<actions>/<channel>/<keyframe>system) — timecoded curves exported to glTF. See Animation Reference. - Interactive state actions (this page) — named transforms and state-machine transitions triggered by in-game events (player interaction, collision, signals). These are stored in
<actions>with a different schema and are intended for runtime game engines.
The two systems share the <actions> top-level element name but differ in internal structure — keyframe actions contain <channel> children; interactive actions contain type attributes on the action itself.
Object States
Any object can declare a set of named configurations using the <states> child element.
Each <state> is a partial override of the object's attributes that the runtime applies
when the object enters that state.
<box name="FrontDoor"
size="1 2.1 0.08"
pivot="-0.5 0 0"
material="painted_wood"
state="closed">
<states>
<state id="closed" rotation="0 0 0"/>
<state id="open" rotation="0 90 0"/>
</states>
</box>
<states> element
Placed as a direct child of any object element. Contains one or more <state> children.
<state> attributes
| Attribute | Type | Description |
|---|---|---|
id | string | Unique state name within this object. Referenced by set_state actions. |
rotation | vec3 | Overrides the object rotation when in this state. |
position | vec3 | Overrides position. |
scale | vec3 | Overrides scale. |
visible | boolean | Overrides visibility. |
material | IDREF | Overrides the material reference. |
The state attribute on the object element (e.g., state="closed") sets the initial active state. When no state is set, the object's base attributes apply.
For rotating doors and lids, combine <states> with the pivot attribute to set the rotation axis correctly. Example: a door with pivot="-0.5 0 0" rotates around its left edge.
Interactive Actions
Interactive actions describe what happens when an object state changes or a trigger fires.
They are declared in the top-level <actions> element with a type attribute
that determines the kind of transform or state change.
Action attributes (common)
| Attribute | Type | Description |
|---|---|---|
id | string | Unique action identifier. Used in sequences and toggle definitions. |
target | string | Object name to act on. Omit for sequence and parallel types. |
type | string | Action type (see table below). |
duration | float | Transition duration in seconds. Default: 0 (instant). |
easing | string | linear, ease_in, ease_out, ease_in_out. Default: linear. |
relative | boolean | Whether the transform is relative to current state. Default depends on type. |
Action Types
rotate
Rotates the target object. Use with pivot on the target for door/lid motion.
<action id="open_door" target="FrontDoor" type="rotate"
axis="y" angle="90" duration="0.6" easing="ease_out"/>
| Attribute | Description |
|---|---|
axis | x, y, or z — local axis of rotation. |
angle | Rotation angle in degrees. |
rotation | Alternative: Euler vec3 ("0 90 0"). |
translate
Moves the target object by an offset.
<action id="slide_drawer" target="Drawer" type="translate"
offset="0 0 0.4" duration="0.4" easing="ease_in_out"/>
set_position
Sets the absolute world position of the target.
<action id="reset_chair" target="ChairA" type="set_position"
position="1 0 2" duration="0.3"/>
scale
Changes the scale of the target.
<action id="grow_platform" target="Platform" type="scale"
scale="2 1 2" duration="1.0"/>
set_visible
Shows or hides the target object.
<action id="hide_wall" target="SecretWall" type="set_visible" visible="false"/>
set_material
Swaps the material on the target.
<action id="turn_lamp_on" target="LampBulb" type="set_material"
material="glowing_yellow"/>
set_state
Transitions the target object to a named state. The object must have a matching <states> block.
<action id="open_front_door" target="FrontDoor" type="set_state"
state="open" duration="0.6" easing="ease_out"/>
<action id="close_front_door" target="FrontDoor" type="set_state"
state="closed" duration="0.6" easing="ease_in"/>
toggle
Syntactic sugar that cycles through named states in order, wrapping after the last. Each invocation advances one step.
<action id="toggle_door" type="toggle">
<state id="closed" target="FrontDoor" type="rotate" axis="y" angle="0" duration="0.5"/>
<state id="open" target="FrontDoor" type="rotate" axis="y" angle="90" duration="0.5"/>
</action>
toggle and <states> are complementary: use <states> on the object to declare named configurations, and a toggle action to cycle through them. Direct set_state actions remain available for explicit targeting.
sequence
Runs child steps one after the other. Each step waits for the previous to complete before starting.
<action id="open_and_move" type="sequence">
<step action="open_front_door"/>
<step action="slide_drawer"/>
</action>
parallel
Runs all child steps simultaneously.
<action id="open_double_door" type="parallel">
<step target="LeftDoor" type="rotate" axis="y" angle="-90" duration="0.6"/>
<step target="RightDoor" type="rotate" axis="y" angle="90" duration="0.6"/>
</action>
Triggers
A <trigger> child element on an action describes the condition under which the action fires automatically in a game runtime. Without a trigger the action is manual — it must be explicitly invoked by game code.
<action id="open_front_door" target="FrontDoor" type="set_state"
state="open" duration="0.6">
<trigger type="interact" prompt="Open door"/>
</action>
Trigger attributes
| Attribute | Type | Description |
|---|---|---|
type | string | Trigger type (see below). |
prompt | string | UI hint text displayed to the player (used with interact). |
signal | string | Named signal identifier (used with on_signal). |
Trigger types
| Type | When it fires |
|---|---|
manual | Never automatically — only when game code calls it explicitly. Default when no trigger is present. |
interact | When the player presses an interaction key while looking at or near the target object. |
on_load | Immediately when the scene is loaded into the runtime. |
on_collision | When a physics body collides with the target. |
on_enter_area | When an entity enters an <area> volume. |
on_signal | When game code broadcasts the named signal (see signal attribute). |
on_load example — auto-start a windmill
<action id="spin_windmill" target="WindmillBlades" type="rotate"
axis="z" angle="360" duration="4.0">
<trigger type="on_load"/>
</action>
on_signal example — open a gate on command
<action id="open_gate" target="CastleGate" type="translate"
offset="0 3 0" duration="1.5">
<trigger type="on_signal" signal="gate_opened"/>
</action>
Collision & Physics Attributes
The collision attribute on any object marks it for physics processing:
<box name="Wall" size="10 3 0.3" collision="static"/>
<box name="Crate" size="1 1 1" collision="dynamic"/>
<box name="Door" size="1 2 0.1" collision="kinematic"/>
| Value | Description |
|---|---|
none | No collision — object is purely visual. Default. |
static | Immovable solid. Use for floors, walls, terrain. |
dynamic | Full physics body — gravity, collisions, constraints. |
kinematic | Moved by actions/code, not by physics. Use for doors, elevators, platforms. |
trigger | Detects overlap events but does not block movement. |
The extended <collision> child element allows additional physics properties:
<box name="Gate">
<collision type="kinematic" shape="box" layer="environment">
<mask>player npc</mask>
</collision>
</box>
Area Objects
An <area> is an invisible logical volume used for trigger zones, checkpoints, or pathfinding regions. It has no visual geometry.
<area name="CheckpointZone" size="5 3 5"
position="0 1.5 10" tags="trigger checkpoint"/>
Areas are typically paired with on_enter_area triggers on other actions:
<action id="unlock_door" target="ExitDoor" type="set_visible" visible="true">
<trigger type="on_enter_area"/>
</action>
Complete Interactive Example
A front wall with a door hole cut out, a rotating door with open/close states, a glass window, and a movable chair:
<?xml version="1.0" encoding="UTF-8"?>
<mc3 version="0.3" model="InteractiveHouse" unit="meter">
<textures>
<texture id="brick_albedo" uri="textures/brick.png"
wrap_u="repeat" wrap_v="repeat"/>
<texture id="wood_albedo" uri="textures/wood.png"
wrap_u="repeat" wrap_v="repeat"/>
</textures>
<materials>
<material id="brick" roughness="0.9">
<base_color_texture>brick_albedo</base_color_texture>
</material>
<material id="painted_wood" roughness="0.6">
<base_color_texture>wood_albedo</base_color_texture>
</material>
<material id="glass" roughness="0.05" alpha_mode="blend">
<base_color>0.7 0.9 1.0 0.35</base_color>
</material>
</materials>
<definitions>
<definition id="SimpleChair">
<group>
<box name="Seat" size="1 0.15 1" position="0 0.55 0" material="painted_wood"/>
<box name="Back" size="1 1 0.15" position="0 1.1 0.45" material="painted_wood"/>
<box name="Leg1" size="0.1 0.55 0.1" position="-0.4 0.275 -0.4" material="painted_wood"/>
<box name="Leg2" size="0.1 0.55 0.1" position=" 0.4 0.275 -0.4" material="painted_wood"/>
<box name="Leg3" size="0.1 0.55 0.1" position="-0.4 0.275 0.4" material="painted_wood"/>
<box name="Leg4" size="0.1 0.55 0.1" position=" 0.4 0.275 0.4" material="painted_wood"/>
</group>
</definition>
</definitions>
<objects>
<!-- Front wall with door hole cut out via CSG difference -->
<difference name="FrontWall">
<box name="WallBase" size="8 3 0.3" position="0 1.5 -3" material="brick"/>
<box name="DoorHole" size="1.1 2.2 0.5" position="0 1.1 -3"
role="cutter" visible="false"/>
</difference>
<!-- Door — rotates around its left edge (pivot) -->
<box name="FrontDoor" size="1 2.1 0.08"
position="0 1.05 -3.08"
pivot="-0.5 0 0"
material="painted_wood"
collision="kinematic"
state="closed">
<states>
<state id="closed" rotation="0 0 0"/>
<state id="open" rotation="0 90 0"/>
</states>
</box>
<!-- Glass window -->
<box name="WindowGlass" size="1.2 1.0 0.05"
position="2.2 1.7 -3.08" material="glass"/>
<!-- Movable chair from definition -->
<instance name="ChairA" definition="SimpleChair"
position="1.5 0 1.5" rotation="0 20 0"
collision="dynamic"/>
</objects>
<actions>
<action id="open_front_door" target="FrontDoor"
type="set_state" state="open"
duration="0.6" easing="ease_out">
<trigger type="interact" prompt="Open door"/>
</action>
<action id="close_front_door" target="FrontDoor"
type="set_state" state="closed"
duration="0.6" easing="ease_in">
<trigger type="interact" prompt="Close door"/>
</action>
<action id="push_chair" target="ChairA"
type="translate" offset="1.0 0 0"
duration="0.4" easing="ease_in_out">
<trigger type="on_signal" signal="push_chair"/>
</action>
</actions>
</mc3>
Best Practices for Interactive Objects
- Always give names to objects that actions reference — unnamed objects cannot be targeted.
- Use
pivotto place the rotation axis correctly (door hinge, lid axis, lever pivot). - Mark CSG cutter objects with
role="cutter"andvisible="false"so they do not appear in the scene. - Keep interactive objects (doors, chairs, levers) as separate named objects — do not bake them into CSG operations together with the surrounding static geometry.
- Set
collision="kinematic"on objects moved by actions; setcollision="static"on immovable walls and floors. - Use
<definitions>for repeating furniture — instantiate with<instance>at different positions. - Prefer
set_stateover rawrotate/translatewhen an object has two or more configurations, as it gives the runtime a clear state to query.
Relationship with Keyframe Animation
| Interactive State Actions | Keyframe Animation | |
|---|---|---|
| Schema | <action type="rotate"/set_state/…> | <action name="…"><channel><keyframe> |
| Triggered by | In-game events (interact, collision, signal) | Timeline playback, game code |
| Duration/easing | Per action | Per keyframe interpolation curve |
| glTF export | Not exported (runtime-only concept) | Exported as animations[] |
| Best for | Doors, levers, triggers, state machines | Character clips, camera paths, continuous motion |