API Fundamentals
This page covers engine behaviours that cut across multiple extension types and are important to understand before writing non-trivial extensions. Topics here are not tied to a single extension point but affect how animations, audio, and Python scripts behave at runtime.
Extension Parameters
Every extension point exposes its configurable settings through LimonTypes::GenericParameter. This single struct covers three concerns at once - loading saved configuration, serializing it back to disk, and building the editor interface to edit it - so an extension describes its parameters once and gets all three for free.
The pattern is uniform across extension points: getParameters() returns the extension’s parameter vector - each entry carrying both its descriptor (request type, description, value type) and its value - and setParameters() stores edited or loaded values back. There is a single vector, not a separate schema and value list: right after construction it holds the defaults the extension seeded, and after an editor edit or a map load it holds the configured values. That one vector is the source of truth for editing, serialization, and runtime. Triggers, Actors, and Player Extensions hold it on a protected parameters member; RenderMethods persist theirs in both the editor node graph and the runtime pipeline file.
For the full contract and the per-extension-point breakdown, see the unified parameter contract.
Animation Blending
The engine supports simultaneous playback of two animations during a transition. This allows smooth crossfades between, for example, an idle animation and a walk cycle.
How Blending Works
Blending is implemented by interpolating translation, scale, and rotation separately for each bone:
Translation and Scale - linear interpolation between the two animations.
Rotation - spherical linear interpolation (slerp). This avoids the candy-wrap artefact that occurs when linearly interpolating rotation matrices or quaternions.
The final bone matrix is reconstructed from the three blended components.
This approach explicitly avoids dual-quaternion blending, which does not support scale animation.
Note
The maximum number of simultaneously contributing animations is two. There are no blend trees, state machines, or additive layers in 0.7. Complex animation graphs must be managed manually by switching animations from extension code.
Blend API
Animation blending is not configurable in the editor - it must be driven via API from a C++ or Python extension.
Method |
Description |
|---|---|
|
Apply an asset animation by name immediately (no blend). (C++ | Python) |
|
Crossfade from the current animation to the named one. |
|
Speed multiplier applied to the active animation. Values below 0.001 are rejected. (C++ | Python) |
|
Returns the name of the currently playing asset animation. Returns empty string if a custom sequencer animation is active. (C++ | Python) |
|
Returns |
Each animation’s playback speed is independently configurable. The start point of the incoming animation relative to the outgoing one is also configurable via the blend parameters.
Sound
The audio backend is OpenAL with a dedicated audio thread. Supported formats are OGG and WAV.
Placing and Controlling Sounds
playSound is the primary way to create a sound from an extension. It returns a sound world-object ID used for all subsequent control calls.
Method |
Description |
|---|---|
|
Create and play a sound. Returns a sound world-object ID, or 0 on failure. (C++ | Python) |
|
|
|
Pause a playing sound. Can be resumed from the same position. (C++ | Python) |
|
|
|
Set per-sound gain. Effective gain = this × channel volume × master volume. (C++ | Python) |
|
Change loop state. Can transition a looping sound to one-shot after the current cycle. (C++ | Python) |
|
Returns |
The positionRelative Parameter
playSound accepts a boolean positionRelative argument:
positionRelative = False- the position is a world-space coordinate. The sound stays fixed at that location regardless of where the player moves. Use for ambient sounds tied to geometry.positionRelative = True- the position is relative to the player. The sound moves with the player and is always heard at the same relative offset. Use for UI feedback sounds, HUD beeps, or any sound that should follow the player.
Attaching a Sound to a Moving Object
Sound is a first-class world object and participates in the standard attachment system. To make a sound follow a model or bone, create it with playSound and then attach it:
uint32_t soundID = limonAPI->playSound("footstep.wav", glm::vec3(0,0,0), false, true);
limonAPI->attachObjectToObject(soundID, modelID);
The sound follows the model’s transform every frame. To stop it, call stopSound(soundID) — this removes the sound from the world. Multiple sounds can be attached to the same model by attaching multiple sound world objects.
Audio Channels
Every sound is mixed on one of five channels. The effective gain of a sound is: per-sound gain × channel volume × master volume.
Channel |
Option name |
|
Description |
|---|---|---|---|
|
|
invalid |
Global volume multiplier applied to all channels. Not an assignable channel — passing it to |
|
|
invalid |
Dedicated music channel. Managed exclusively by |
|
|
default |
Sound effects. Default channel for sounds played via |
|
|
valid |
Speech and voice-over. |
|
|
valid |
Environmental / ambient sounds. |
Channel volumes are global options, not API calls. Change them via getOptions() / saveOptions() using the option names above. See Engine Options Reference for the defaults.
Note
Only mono audio files are spatialized by OpenAL. Stereo files play at a fixed volume regardless of position — positionRelative, referenceDistance, and maxDistance have no spatial effect on stereo sources. Use mono audio for any sound that needs 3D positioning.
Note
The sound attenuation model (the mathematical curve — linear, inverse, etc.) is world-level and configured under World Properties in the editor. OpenAL does not support per-sound attenuation models. The per-sound referenceDistance and maxDistance parameters control the distances at which full volume and full attenuation are reached within whatever model the world uses.
Music API
Each level has a single dedicated music track playing on the MUSIC channel, looped by default. Set in the editor under World Properties, or switched at runtime:
Method |
Description |
|---|---|
|
Switch level music. |
|
Stop level music with optional fade-out. Returns |
|
Returns the current music asset path, or empty string if no music is set. (C++ | Python) |
|
Returns |
Named Variables
The engine provides a world-scoped variable store for communicating state between extensions without direct coupling. Any extension can read or write a variable by name through the same API instance.
// Writer (e.g. an AI Actor that spotted the player)
limonAPI->getVariable("alerted").value.boolValue = true;
limonAPI->getVariable("alerted").valueType = LimonTypes::GenericParameter::ValueTypes::BOOLEAN;
limonAPI->getVariable("alerted").isSet = true;
// Reader (e.g. a Trigger that reacts to the alert)
auto& v = limonAPI->getVariable("alerted");
if(v.isSet && v.value.boolValue) { ... }
C++: getVariable(name) returns a mutable reference directly into the store. Assigning to the returned reference is the setter — there is no separate setVariable.
Python: get_variable(name) and set_variable(name, value) are separate methods.
Warning
Named variables are not saved with the world. They are runtime-only and reset to empty on every world load. Do not use them to carry state that must survive a level transition.
Multi-Interpreter Python
Limon Python scripting is built on pybind11. Each world instance gets its own isolated Python interpreter - scripts from one world cannot access or interfere with scripts from another.
Key behaviours:
When a world is unloaded, its Python interpreter is torn down. Any global state in scripts running in that world (module-level variables, open resources) is released at that point.
When a world is loaded (or reloaded), a fresh interpreter starts. Scripts do not carry state across world loads unless they persist data externally (e.g., to a file or via the engine’s
setVariable/getVariableAPI).C++ extensions and Python extensions can coexist in the same user library. A C++ Action and a Python Player Extension running in the same world share the same engine API instance but have separate execution contexts.
All five extension types are implementable in Python: Actions, Player Extensions, AI Actors, Camera Attachments, and RenderMethods. Python extensions have full parity with the C++ API - the same GenericParameter contract, the same enums, and automatic type conversion via pybind11.
Note
Because Python interpreter state is world-scoped, be careful with module-level singletons or caches that assume persistent lifetime. An extension that registers itself globally on first load will re-register on every world load, which may cause double-registration if the pattern is not world-aware.