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.
| Language | Best for | You 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:
| Lua | Python | Fires |
|---|---|---|
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:
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:
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
| Area | Functions |
|---|---|
| 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:
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/.sonext to the game executable. Export →