Manual

Scripting

Four ways to write gameplay — Lua, Python, native C++ and visual HorizonCode — all reaching one engine API.

Pick one

Choosing a Language

The scripting language is chosen per project, at creation — it decides which gameplay assets the editor offers and keeps a project single-language. Widgets, the Level Script and the Game Instance are always HorizonCode graphs, in every project.

LanguageBest forYou write
HorizonCode Visual-first teams; designers Node graphs — compiled to native C++ on export. Guide →
Lua Fast iteration, small footprint Modules with lifecycle callbacks, attached via the Script component.
Python Python-fluent teams, tooling-heavy projects horizon.Behavior subclasses (real CPython, full stdlib).
C++ Maximum performance, engine-level access A native GameLogic library against IGameLogic, built with your own toolchain.

Lifecycle

How Scripts Run

A script is an asset (created in the Content Browser, edited in the built-in syntax-highlighted editor or your own IDE) attached to an entity through the Script component. The language lives on the asset — inferred from the extension — so scenes and prefabs never care which backend runs the code.

Lifecycle callbacks, all optional:

LuaPythonFires
onStart(self) on_start(self) Once, when the entity spawns in play mode.
onUpdate(self, dt) on_update(self, dt) Every frame, with elapsed seconds.
onCollisionEnter/Exit(self, other) on_collision_enter/_exit(self, other) Physics contacts and triggers, with the other entity's id.
onClick, onHoverEnter, onHoverExit on_click, on_hover_enter, on_hover_exit In-world UI interaction on the entity.

Each instance knows its entity — self.entityId in Lua, self.entity_id in Python — and passes it to the horizon API.

Default backend

Lua

A Lua script is a module that returns a table. Fields become per-instance state; functions become the callbacks:

spinner.lua
local M = {}

M.properties = { speed = 90.0 }  -- shows up in the Details panel

function M.onStart(self)
    self.angle = 0
end

function M.onUpdate(self, dt)
    self.angle = self.angle + self.speed * dt
    horizon.setRotation(self.entityId, 0, self.angle, 0)
end

return M

CPython

Python

Python scripts run on an embedded real CPython interpreter — the full standard library is available, and exports bundle it with your game. A script file contains exactly one horizon.Behavior subclass; typed class attributes become editor-exposed properties:

spinner.py
import horizon

class Spinner(horizon.Behavior):
    speed = 90.0  # exposed in the Details panel

    def on_start(self):
        self.angle = 0.0

    def on_update(self, dt):
        self.angle += self.speed * dt
        horizon.setRotation(self.entity_id, 0, self.angle, 0)

The API mirrors Lua exactly — the same function names, with vectors returned as tuples and raycast returning a dict (or None on miss).

One surface

The horizon API

All frontends bind to one central engine API. Scripts see it in two layers: flat gameplay functions directly on horizon, and namespaced groups (horizon.math.*, horizon.scene.* …) generated from the same registry that powers HorizonCode's Engine Call node — a graph and a script always reach exactly the same engine surface.

Flat functions

AreaFunctions
Identity & logging log, getName
Transform get/setPosition, get/setRotation, get/setScale
Lifecycle spawn(parent, name), destroy
Physics raycast, setVelocity, isGrounded
Materials get/setMaterialParam — animate node-graph material parameters per entity
Entity UI get/setUIText, …UIColor, …UIVisible, …UIPosition, …UISize, setUIMaterialParam
Widgets & cursor createWidget, destroyWidget, show/hideWidget, setWidgetZOrder, isWidgetVisible, callWidgetFunction, show/hideCursor

Namespaced groups

math, random, time, input, string, camera, env (every sky/weather field), entity, audio, debug (debug-draw lines/spheres/boxes), fs (sandboxed save-folder file I/O), save (key-value save game with slots) and scene (loading, streaming zones).

Full reference: every function with signatures and examples is on the Scripting API Reference page; the group-by-group catalogue is in the node reference.

Designer-friendly

Exposed Properties

Scripts expose tweakables to the editor — an M.properties table in Lua, typed class attributes in Python. Supported types: Float, Int, Bool, String. Each Script component instance can override the defaults in the Details panel, and the values are injected before onStart — one script, many differently-tuned entities.

Iterate

Hot Reload

Save a script while play mode runs and the engine patches the new code into every live instance — Lua swaps the module's functions (instance data survives), Python swaps the class (__dict__ state survives). Errors surface in the log without ending the session.

Native

Native C++

C++ projects ship gameplay as one native GameLogic shared library. The editor scaffolds a compilable Source/ tree at project creation — game runtime, GameInstance, a per-scene level script and a CMakeLists.txt — visible as its own root in the Content Browser, with an in-editor class editor and a New C++ Class workflow.

The library implements the three-method IGameLogic interface and works with the world directly — full engine headers, no wrapper layer:

GameLogic.cpp
class MyGame : public IGameLogic {
    void onStart(HorizonWorld& world) override;
    void onUpdate(HorizonWorld& world, float dt) override;
    void onStop(HorizonWorld& world) override;
};

extern "C" HE_GAME_API IGameLogic* HE_CreateGameLogic()
{ return new MyGame(); }
extern "C" HE_GAME_API void HE_DestroyGameLogic(IGameLogic* p)
{ delete p; }
  • Hot reload — rebuild the library and the engine reloads it live (it loads a uniquely-named copy, so the build never fights a locked file). Keep persistent state in the ECS, not in the logic object — the object is recreated on reload, the world survives.
  • Toolchain — needs CMake and a C++ compiler; the editor checks at startup and can install them for you (Preferences ▸ C++ Toolchain).
  • Shipping — the export copies GameLogic.dll/.dylib/.so next to the game executable. Export →