Manual

Gameplay Systems

Physics, audio, animation, navigation, particles and input — all built in, all component-driven.

Jolt-powered

Physics

Physics runs on Jolt, stepped at a fixed timestep during play mode. Give an entity a Rigid Body (Static, Dynamic or Kinematic — with mass, friction and restitution) and optionally a Collider (Box, Sphere or Capsule; without one, a box is derived from the transform scale). A 2D-physics toggle routes bodies to a planar solver.

  • Triggers — a collider marked Is Trigger generates events without a collision response.
  • Character Controller — slope limit, step height, skin width, mass and gravity; scripts drive it with setVelocity and query isGrounded.
  • Raycastsraycast(origin, direction, maxDistance) returns the closest hit with entity, point, normal and distance, from Lua, Python, HorizonCode and C++.
  • Collision callbacks — scripts receive onCollisionEnter / onCollisionExit (Lua) or on_collision_enter / on_collision_exit (Python) with the other entity's id, for solid contacts and triggers alike.

Listen

Audio

An Audio Source component references an audio asset and plays it with volume, pitch, loop and Play on Start. Enabling Spatial gives it 3D positioning with linear attenuation between an inner (full-volume) and outer (silence) range. The first Audio Listener in the scene — typically on the camera or player — receives the mix and sets the master volume.

  • Mixer buses — sources route to named buses (music, sfx, …); scripts set bus volumes at runtime (audio.setBusVolume).
  • Script playbackaudio.play, audio.playAt (3D position), audio.stop / stopAll / isPlaying / setSoundPosition.
  • Formats — WAV import today, stored as PCM inside the asset.

Move

Animation

Skeletal meshes are skinned on the GPU (up to 128 joints, four weighted influences per vertex) and posed by a family of animator components — from a single looping clip to a full state machine. All animation systems tick in the editor too, so previews work without pressing Play.

ToolUse it for
Animator Playing one clip — speed, time, looping.
Animator Blend Mixing two clips with a blend alpha (e.g. walk ↔ run).
Animator State Machine Full character logic — see below.
Animation Layers Poses laid on top of any of the above, through a bone mask — an upper body with a job of its own.
Inverse Kinematics Correcting the finished pose against the world — feet onto the ground, head onto a target.
Property Animator Keyframed transform and material properties — cutscenes, moving platforms, pulsing materials.

Animator State Machine

A standalone asset edited on the shared graph canvas: states are nodes (each with an animation clip and a looping flag), transitions are links with a condition — a named float parameter compared against a threshold (greater / less / equal) — and a crossfade duration. The component on the entity references the asset and holds the live parameter values; gameplay code drives the parameters and the machine blends states automatically. One asset can drive any number of characters.

Blend spaces

A Blend Space is an asset of its own: animation clips placed as points in a parameter space, and a mix of whichever points the parameters are currently standing between. One axis (1D) is speed into walk / jog / run; two axes (2D) are speed and direction into a whole strafe set. Its editor is a diagram with draggable dots and a preview cursor that computes its weights with the same function the running game uses.

It goes wherever a clip goes — a state of a state machine or an animation layer can pose from one instead of from a single clip. All of a space's samples run on one shared phase rather than on their own seconds, so clips of different length are always read at the same point in their own cycle and a blend never crosses the feet. The sampler clamps to the outermost sample and never extrapolates; at most four samples are evaluated at once.

Animation layers and bone masks

The Animation Layers component lays further poses on top of whatever posed the entity — a clip, a two-clip blend or a state machine, it does not care which. Each layer has its own source (a clip or a blend space), its own playhead, speed and weight, and a Bone Mask saying which joints it may touch. A reload on the arms while the legs keep running is one layer plus one mask.

A layer is either Override, replacing the pose underneath wherever its mask allows, or Additive, adding its clip's difference against a reference pose on top of what is already there — a breathing wobble or a limp that keeps the run running while it leans on it. Additive works in local joint space; there is no mesh-space additive yet. Layers are applied in list order, each onto the result of the one before it, and gameplay addresses them by name (animator.setLayerWeight, animator.playLayer).

A bone mask is a list of joint names with a weight each, not a skeleton: it works on every rig that spells its joints the same way. It is an allow-list — a joint that is not in it has weight 0 — and weights below 1 down a few spine joints are what fades a masked layer into the base pose instead of ending it at a hard edge across the waist.

Inverse kinematics

The Inverse Kinematics component corrects the finished pose against the world the character is standing in. Two independent halves: foot placement puts each foot on the ground actually under it — a ray down from the foot, a two-bone solve up the leg, the foot tilted to the surface normal within a pitch and roll limit, and the pelvis lowered by the deepest foot's drop so the low leg does not have to overextend — and look-at turns a chain of joints (spine, neck, head) towards a target entity or a world point, within a yaw and pitch limit, with the turn spread over the chain by weight.

Both are smoothed over time, because the ground under a foot really does jump by a whole step height in one frame at a stair edge; a character that is teleported is detected as such and snaps instead of easing. Foot placement needs a physics world, so it runs in play mode only and does nothing where there is no collider; look-at runs in the editor too.

The order a pose is built in

Each stage holds whatever the one before it came up with, and the order is fixed: the animator (clip, blend or state machine) decides the pose, root motion is taken out of it and turned into entity movement, the layer stack is applied on top of what is left, and inverse kinematics bends the result to the world. That order is what makes the four safe to add one at a time — and it is why an additive layer that moves the root joint moves the pose rather than the character.

Skeletal Mesh editor

Double-click a skeletal mesh asset for a dedicated tab: the joint hierarchy as a tree, an orbit-camera preview with a Show Skeleton bone overlay, and an optional clip slot with play/pause and time scrubbing — inspect a rig without touching any scene.

Sparks

Particles

Particle systems are graph assets: create a Particle System in the Content Browser and double-click it for a node-graph editor with a live simulated preview. A fixed Emitter Output node collects the emitter parameters; value nodes (Const Float/Vec3/Color, Random Range, Add, Multiply, Lerp) feed its pins.

Emitter parametersMeaning
Emit Rate · Max Particles · Looping Spawn rate and pool size.
Lifetime Min/Max Per-particle lifetime range.
Start/End Size · Start/End Color · Start/End Alpha Interpolated over each particle's life.
Initial Velocity · Velocity Spread · Gravity Motion model.
Collision · Restitution · Kill on Collision Optional per-particle world collision (raycast-based bounce or despawn).
Mesh · Material Optional mesh/material slots on the Output node for custom particle looks.

Attach the asset to an entity with the Particle System component. Rendering is GPU instanced — one batch per emitter — and on export the colour/alpha-over-life curves are baked into small per-backend shader snippets so packaged games evaluate them on the GPU.

Weather rain and snow use a separate camera-following simulation that runs fully on the GPU on capable backends — see Weather.

Control

Input

Input is asset-driven: instead of hard-coding key codes, you declare logical Input Actions and bind them in an Input Mapping Context.

  • Input Action — a named action with a value type: Button (pressed/released) or Axis (−1…1). The asset name is the action name (e.g. IA_Jump).
  • Input Mapping Context — binds keys and gamepad buttons to actions, and key pairs, button pairs, mouse sources or gamepad sticks/triggers (with scales) to axes.

In HorizonCode projects, Player Controller and Player Character classes are spawned automatically when play begins; each mapped action arrives as graph events — Input.<Action>.Pressed, Input.<Action>.Released and Input.<Action>.Axis — alongside BeginPlay and a per-frame Tick.

Gamepads work through the same assets: bind a pad button next to the keys, or pick a stick/trigger as an axis source, and the same Input.<Action>.* events fire — no script changes. All connected controllers merge into one virtual pad (single player), sticks get a radial deadzone (tunable in the editor settings), and the camera rig has its own stick sensitivity in degrees per second, separate from the mouse's per-pixel value. Button and axis names are SDL's mapping strings in Xbox-layout positions: a is the south button (Cross on a PlayStation pad), leftshoulder, dpup; axes are leftx, righty, lefttrigger, … Stick Y is positive downward — bind with scale −1 for an up-positive axis.

Scripts can also poll input directly: input.keyDown("Space"), input.mouseButton, input.mousePosition, input.mouseDelta, input.scrollDelta — and the pad: input.gamepadConnected(), input.gamepadButton("a"), input.gamepadAxis("leftx") (deadzone-filtered; a resting stick reads exactly 0).

While the game is paused: a pause (time.setTimeScale(0)) silences every action by default — otherwise the player keeps shooting through the pause menu. Each Input Action has a Fires while the game is paused switch for the few that must get through anyway: opening and closing the menu, navigating it, confirming. Presses that arrive while a silenced action is paused are dropped, not queued for the moment the game resumes; a key held across the pause is not mistaken for a fresh press either. Direct polling (input.keyDown and friends) is never gated.