Skip to main content
WizardMachine entities can be driven at run time by attaching a script written in AngelScript — a strongly-typed, C-like scripting language embedded in the engine (vendored at vendor/angelscript). This page is the complete reference for the scripting API exposed by the engine: the entity object model, global functions, math library, data types, and enumerations, together with the lifecycle rules and known quirks you need to know to use them correctly. The entire native binding surface documented here is implemented in one header, engine/WizmECS/includes/system/scripting_functions.h, inside wizm::ecs::register_scripting_api(). For a walkthrough of a complete first-person game built on this API (player rig, hitscan weapon, enemies, pickups, HUD), see the FPS guide. The AI part of the API — navigation queries (nav_*), nav agents (entity.agent_*), the blackboard (entity.set_ai_* / get_ai_*), state machine and behaviour tree control, perception, spawners (entity.spawner_*), the AI manager (ai_*), steering (steer_*), the bt_status enum and the AI callbacks (OnAIStateEnter, OnTargetPerceived, OnDestinationReached…) — has its own page: the AI reference.

Overview

A script is a plain-text .wscript file containing AngelScript source. It declares a behaviour class — the engine’s equivalent of a Unity MonoBehaviour. The file is imported as a script_asset and attached to an entity through a Scripting component (scripting_component), which owns a script_controller: the object that compiles the file, constructs one instance of the class for that entity, and drives its lifecycle every frame.
The methods the engine calls, all optional: The engine’s older snake_case names (on_start, on_update(float), on_destroy, on_message) are accepted as method names too, so a script written before classes keeps working once wrapped in a class. Which class is the behaviour is decided in this order:
  1. the class named after the file (enemy_patrol in enemy_patrol.wscript, case-insensitive) — the rule to rely on;
  2. otherwise, the only class in the file that declares an Update(float);
  3. otherwise, the file’s single class.
Helper classes declared alongside the behaviour are left alone. A file that declares no class at all still runs through global on_start / on_update(float) / on_destroy / on_message functions — the pre-class form, kept working for older scripts, with no inspector fields. A class without Update is a perfectly good data component: it never ticks, but its fields are set per entity in the editor and read by other behaviours - a spawn point saying what spawns there, a level’s settings. See Other behaviours’ fields. A script exception at run time (a null handle, a division by zero) aborts only the call it happened in; it is logged with the file, line and entity, once per place, instead of silently skipping the rest of that Update. Each entity that runs a script gets its own AngelScript module and its own class instance, even when two entities point at the same .wscript file. This means:
  • Members are per-entity state: two enemies running enemy_patrol have their own speed and their own m_elapsed, and nothing is reset between frames.
  • Only a single file is compiled per script (CScriptBuilder::AddSectionFromFile is called once) — there is no #include mechanism. Keep each .wscript self-contained.

Getting started

Attaching a script to an entity

1

Create the script

Create the script with + Add > New Script in the Content Browser - a behaviour class named after the file, with an empty Start and Update (see the Editor guide) - or put a .wscript file into the project’s Game folder or import one (+ Add > Import…); it is classified as a script asset by its extension.
2

Add a Scripting component

Select the target entity and add a Scripting component from the editor’s Add Component menu.
3

Assign the script

Assign the imported script asset to the component. The class’s public fields appear underneath it, each showing the value the script itself gives it — change the ones this entity needs.
4

Enter Play mode

Start() runs once, then Update(float) runs every frame. While the game runs, every editor panel is greyed out and ignores input; only the Stop button and Escape end the session, after which the level is restored to its pre-play state.
An entity can carry several Scripting components, each with its own script and its own field values, the way a GameObject carries several MonoBehaviours.
A Scripting component can only be added through the editor. entity.add_component(component_type::scripting) from inside a script always fails — see Component management.

Minimal example

Behaviour classes and inspector fields

A behaviour’s public members are its inspector fields. The editor compiles the file, reads the class’s members, and draws an editor for each one it can show — this is what a script is tuned with, instead of encoding parameters into the entity’s tags. Members declared private or protected are the behaviour’s own state and are never shown or stored. Members of any other type (arrays, other script classes, funcdefs) are ignored by the inspector — the script can still use them freely. Only the values you change are stored. A field left alone keeps whatever the script initialises it to, so editing a default in the .wscript reaches every entity that never overrode it; a field you change shows a Reset button that hands it back to the script. This is also why values are kept per field rather than per component: renaming or retyping a field in the script simply drops the stored value for it. Values reach the behaviour object right after it is constructed, well before Start(): when a level starts, every behaviour in it is built and given its values before the first Start runs, and a spawned prefab’s behaviours are built inside spawn_prefab itself. Editing a .wscript and saving it is picked up immediately: the panel recompiles the file, shows the new fields, and reports a compile error next to the script instead of only on the console.

Object references

An entity@ field is stored as the target entity’s id and resolved against the scene when the behaviour is built:
Ids of entities inside a prefab instance are regenerated on every load (see Prefabs), so point a reference at a level entity or at an instance root, not at content nested inside an instance. A reference whose target can’t be found when the behaviour is built is null (the editor shows it as <missing>). Like every entity handle, the field does not turn null when its entity is destroyed later - see Language notes.

Other behaviours’ fields

One behaviour reads or sets another’s public fields through the entity that carries it - Unity’s GetComponent<T>().field:
This is how data travels between behaviours without tags. Spawning is the common case: spawn_prefab builds the new entity’s behaviours on the spot (constructor and field values - Unity’s Awake), so the spawner can hand over parameters before the new behaviour’s Start runs on the next frame:
Reading works the same way - a door asks the player whether it carries a key, a player asks the enemy it hit where its head is:
Because a level’s behaviours all exist before any Start runs, a Start can read data components anywhere in the level, whatever order the entities were loaded in.

Prefab instances

Field values are the one part of a Scripting component a prefab instance may override. Placing the same Enemy prefab three times and giving each one a different max_health is an edit in the Properties panel; the level stores just those values, everything else still comes from the prefab file, and Reset to Prefab puts the prefab’s own values back. Editing the prefab afterwards leaves the per-instance values alone.

Buttons and other UI callbacks

A behaviour that creates a UI declares its widgets and handlers and they are bound by name, UMG-style (see Auto-binding widgets to a behaviour):
BindOnClicked("MethodName") still binds a handler by hand: a method of the behaviour class first, then a global function of the same name, for scripts that have not moved to a class.

Language notes

The API surface is idiomatic AngelScript, with a few points specific to this engine worth calling out:
  • Entity handles. entity is a reference type (entity@). Assign with the @ operator and compare against null with is/!is:
  • Entity handles do not keep entities alive. The entity type is registered with asOBJ_NOCOUNT — AngelScript performs no reference counting on it. If something else calls destroy() on an entity (or the scene deletes it), any handle your script is still holding becomes a dangling pointer. Re-acquire handles (e.g. via find_entity/find_entity_by_name) rather than caching them across a destroy, if you can’t guarantee lifetime.
  • self() returns the entity@ handle for the entity that owns the currently-running script — the equivalent of Unity’s gameObject. It is valid anywhere in the script: in Start/Update/OnDestroy/OnMessage, in any method or function they call, in a widget callback, and even in the class’s constructor.
  • Strings use AngelScript’s built-in string type (via the scriptstdstring add-on) and support + concatenation with other strings; convert numbers with to_string (see Strings).
  • Script functions can take const string &in and recurse, so helpers such as “find a child by name” are ordinary script code:

Data types

vec2

A plain 2-component float vector.

vec3

A plain 3-component float vector, used throughout for positions, rotations (Euler degrees), scale, and color.
Both types are plain-old-data value types (no heap allocation, copied by value).

Enumerations

component_type

Identifies a component slot on an entity. Used by Component management and Generic component properties.
Only static_mesh, sound, point_light, spot_light, directional_light, camera, skybox, fog, post_process, skeletal_mesh, and the five AI components can currently be added at run time via entity.add_component(). The remaining values exist for has_component()/property queries against components that were already added in the editor; calling add_component() with one of them always returns false.

key

Passed to is_key_down/is_key_pressed/is_key_released.

mouse_button

The entity object

Every script has implicit access to entity@ handles — its own, via self(), or others returned by lookup/spawn functions. All methods below are called as some_entity.method(...).

Transform

Local transform relative to the entity’s parent (world space if it has none).
Remarks
Every entity, cameras included, uses one Euler convention. Rotations compose as Rx * Ry * Rz with X outermost: rotation.y turns about world Y (yaw), rotation.x then tilts about the world X axis, rotation.z rolls. Local forward is -Z, so rotation.y = 0 faces -Z and rotation.y = 90 faces -X. Cameras and spot/directional lights all look down their entity’s -Z, so a camera child (a first-person weapon, a flashlight) lines up with the view. (Older engine versions read a camera’s rotation.x as yaw and rotation.y as pitch — that quirk is gone; a script written against it should switch to set_look_rotation.)Because X is applied after Y, set_rotation(pitch, yaw, 0) is not a look rotation once yaw is non-zero — it rolls the view. Anything that aims (cameras, heads, turrets) should use:
which turns by yaw about world Y and then tilts by pitch about the entity’s own X axis (positive pitch looks up), computing the equivalent XYZ triple for you. The alternative is to split the axes over a hierarchy — a body that only yaws with a head child that only pitches — which is what the first-person sample does.
Example:

World transform and direction vectors

Accessors for the entity’s fully composed world-space transform (parent transforms folded in). set_world_position moves the entity so that its world position equals the given point, whatever its parent’s transform is (a character controller ignores it — use set_character_position, see Physics). Remarks
get_forward(), get_right(), and get_up() are the entity’s local -Z, +X and +Y axes rotated by its full world matrix, normalised. They are meaningful on every entity: a camera’s get_forward() is exactly what it looks at, a spotlight’s is where it shines, a character’s is the way its rotation.y faces. A mesh that was modelled facing +Z (Mixamo characters, for instance) shows its back along get_forward() — rotate that mesh child by 180° about Y inside its prefab if it matters. For a yaw-only entity, forward == (-sin(yaw), 0, -cos(yaw)), and the yaw that faces a direction d is degrees(atan2(-d.x, -d.z)).

Identity, naming, and tags

Example — an enemy whose tuning is inspector fields and whose tag says what it is:

Hierarchy (parenting)

set_parent(null) detaches the entity, making its local transform equal to world space. Remarks
A .wprefab file can only save one root entity — anything else must be saved as a nested child of that root, even entities that conceptually shouldn’t inherit the root’s rotation (a common example: a third-person orbit camera saved as a child of the player it follows, purely so it round-trips with the prefab). Instances placed in a level are rebuilt from the file on every load, so their contents are edited in the prefab editor rather than in the level — see Prefabs. If a child needs a transform that’s free of its parent’s rotation at run time, detach it in Start():

Component management

Example — toggling a spotlight (from the flashlight example script):

Convenience setters

Thin, type-safe wrappers over common properties on specific component types. Each is a no-op if the entity doesn’t have the relevant component.

Materials

Materials work the way they do in Unreal. A mesh asset has material slots — one per material of its source file, named after it — and brings a material of its own for each. The entity’s mesh component (static_mesh or skeletal_mesh) may override any slot. A slot draws with its override, else the mesh’s own material, else the project’s default material. All of the calls below are no-ops (or return 0 / "" / -1) on an entity without a mesh.
set_material(string) sits on top of the per-slot overrides instead of replacing them, so a hit flash is two calls and leaves a multi-material character intact:
Scripts from before material slots keep working. component_type::material names the mesh component, whose material_asset_id property reads the material of slot 0 and writes like set_material(string). The old flash pattern — remember get_string(component_type::material, "material_asset_id", ""), set_material(flash), then set_material(remembered) — still restores the look: putting slot 0’s own material back is treated as “remove the whole-mesh material” rather than painting it over every slot.

Animation

Requires a skeletal_mesh component. The component plays either a single clip or an animation graph (a state machine driven by parameters - see Animation); the calls below cover both, and clip names are a .wanim asset’s file name or a clip embedded in the mesh. Animation graph (the component’s Animation Mode set to Animation Graph, or set_anim_graph called):
Override slot (either mode) - a clip played on top of whatever else plays, Unreal’s montage:
A non-looping override fades itself out so it ends exactly on its last frame; a looping one plays until stop_animation_override. Re-requesting the clip already playing keeps its place. Single clip (the older mode; unchanged):
Callbacks - optional methods on the behaviour class (or global functions in a class-less script). Events reach the scripts on the skeletal mesh’s entity, or, when none of them has the handler, the nearest ancestor that does:
There is no root-motion extraction in this engine — a played clip’s baked translation never moves the entity. Drive movement yourself (position/velocity), independent of which animation is playing.

Sound

Requires a sound component. Playback is backed by FMOD; a sound asset is loaded once and can be played as either 2D or 3D depending on the component’s is_3d property.
There’s no dedicated wrapper for volume/pitch/loop/3D-ness - use the generic component properties escape hatch:
Remarks
A 3D sound’s position is synced from its entity’s world position every frame while playing, and the 3D listener is the scene’s primary camera (the same one find_primary_camera() resolves) - if no camera is marked primary, the listener defaults to the world origin facing -Z. A component with play_on_start set fires play_sound() automatically once per Play session; sounds only actually play while the scene is running (entering Play mode, or in WizmPlayer) - they do not autoplay while just editing a level.

Physics

Requires a rigidbody component. Force, impulse, and velocity control only work on a Dynamic body.
Character controller — for an entity with a character_controller component (a capsule that sweeps through the world, with step-offset and slope limit; the player and enemies in the FPS sample):
Remarks
Body type determines how you’re allowed to move an entity:Exception: if you lock a rotation axis on a Dynamic body (Properties > Rigidbody > Freeze Rotation), the simulation never writes that axis back onto the entity — so a script can safely call set_rotation directly for a locked axis, exactly like a non-physics entity, while still getting real physics-driven position from the body. This is the recommended pattern for a physics-based character that needs to face an arbitrary direction: lock rotation entirely and drive facing with set_rotation, rather than trying to steer yaw with set_angular_velocity (ground-contact friction imparts small uncorrected torque on unlocked axes, which accumulates into visible drift over time) or reading get_rotation() back into a turn-toward-target loop (the underlying Euler decomposition only round-trips within ±90° of the extracted axis, and aliases to a different, non-monotonic value beyond that — see the third-person controller example, which hits both issues and works around them).

Raycasts

Casts a ray through the physics scene and returns the closest blocking hit. raycast_hit is a value type: Remarks
  • Only physics shapes are visible to rays: rigidbody colliders (box/sphere/capsule, Static/Kinematic/Dynamic) and character controllers. A static mesh without a collider is not hit. Trigger shapes never block.
  • ignore skips that entity and everything parented under it — pass self() when shooting from inside your own capsule.
  • direction does not have to be normalised.
  • The scene contains what physics knew about at the end of the last frame: an entity spawned this frame has no collider yet.

Generic component properties

An escape hatch for reading/writing a named property on any component, by type, without a dedicated wrapper method:
Getters return default_value if the entity has no component of that type, or the component doesn’t recognize the property name. Setters are a no-op in the same case. Known property names used internally by the engine’s own convenience setters (there may be more per component — check the relevant *_component.cpp’s get_/set_*_property overrides for the authoritative list): Example — reading a property that has no dedicated wrapper:

Global functions

Free functions, called without an object (e.g. log_info("..."), not entity.log_info(...)).

Logging

Entity lookup

All lookups skip entities already queued for deletion by destroy() this frame.

Entity spawning

Remarks A spawned prefab is complete immediately — components, children, tags, and its behaviours, which spawn_prefab builds before returning (constructor plus field values, so you can set their fields right away; see Other behaviours’ fields). Their Start and first Update run on the next script update, and the colliders reach the physics scene on the next physics step. Bullet impacts, muzzle flashes, dropped pickups and enemies in the FPS sample are all spawn_prefab calls; a prefab that should clean itself up carries a script that calls self().destroy() after a while.

Levels

Remarks Every entity - the player, the UIs it created, the caller itself - is destroyed by the switch, and the new level’s behaviours start from their Start. State that should survive goes through the save data, which is not cleared by a level change: write it before calling load_level, read it in the next level’s Start. set_mouse_hidden and the input mode are reset too; re-apply them in the new level. In the editor, load_level while playing swaps the play-mode scene only. Stop restores the level that was open before Play, so Save still writes that level.

Messaging between scripts

Scripts on different entities talk through named messages. The sender calls:
and the receiving behaviour implements either of:
Remarks
  • Delivery is synchronous: OnMessage runs before send_message returns, on its own script context, so a script can message itself and chains (A → B → A) are fine. self(), input, time and the UI functions all work inside a handler.
  • sender is the entity whose script called send_message (the binder entity inside a widget callback).
  • A receiver that hasn’t run yet gets its Start first, so handlers never see an uninitialised behaviour.
  • The payload is one float; anything richer belongs in the sender’s or receiver’s public fields (Other behaviours’ fields) - the receiver gets the sender as a handle to read them from.
The FPS sample uses these messages: "damage" (value = amount), "health" and "ammo" (pickups → player), "enemy_killed" (enemy → player), "player_respawned" (player → everyone).

Input

Remarks Everything above is filtered by the current input mode (see UI and widgets). In game_and_ui mode a click that lands on a hit-testable widget is never reported to scripts (is_mouse_button_pressed stays false, and the button stays “not down” for as long as the widget holds it), and the scroll wheel is masked while the pointer is over UI. In ui_only mode every key, button, scroll and mouse-motion query returns false/zero. In game_only mode nothing is filtered.
set_mouse_hidden(true) both hides the OS cursor and enables relative/captured mouse-look (it is backed by the same platform call used for hide+capture, GLFW_CURSOR_DISABLED). There is currently no way to hide just the cursor icon while keeping normal absolute mouse input — calling this always grabs the mouse for look-around input, matching the pattern used in the third-person controller example.The request is remembered rather than applied immediately: the engine pushes the effective cursor state to the window once per frame, only when it changes, and only while the input mode is game_only. Switching to game_and_ui/ui_only frees the cursor regardless; switching back to game_only re-applies whatever set_mouse_hidden last asked for. Calling it every frame is therefore harmless (and no longer costs a platform call per frame).

UI and widgets

Runtime UI is a GUI component on an entity that references a .wgui layout asset authored in the editor’s GUI Designer. The widget model follows Unreal’s UMG: canvas panels with anchors, horizontal / vertical boxes, text, images, buttons, progress bars, check boxes and sliders, each with a visibility, a render transform and a z-order. The Runtime UI page covers this in full - widget kinds, layout rules, the designer, the complete widget API and the auto-binding described below. This section is the short version.
Auto-binding. A behaviour that calls CreateUI gets its widget handle fields (TextWidget@ Health;) filled by widget name, and its OnClicked_<Widget>(), OnPressed_, OnReleased_, OnHovered_, OnUnhovered_, OnCheckStateChanged_<Widget>(bool) and OnValueChanged_<Widget>(float) methods bound as handlers - no FindWidget / cast / BindOnClicked lines needed. BindWidgets(ui) does the same for a UI the behaviour did not create. Names match exactly first, then ignoring case, _, - and spaces. Every widget kind has SetVisible, SetVisibility, SetEnabled, SetPosition / SetOffset, SetSize, SetAnchors, SetAlignment, SetZOrder, SetColor / SetTint, SetTexture, SetOpacity, the render transform setters, GetScreenPosition / GetScreenSize, GetParent / GetChild, IsHovered / IsPressed; TextWidget adds SetText, SetFontSize, SetJustification, SetWordWrap, SetShadow; ButtonWidget the BindOn* family and custom colours; ProgressBarWidget SetPercent; CheckBoxWidget SetChecked / IsChecked; SliderWidget SetValue / SetRange / SetStep. Changes affect that UI instance only - the .wgui on disk is never touched.

Input modes

The engine keeps one explicit record of who is processing input: the current input mode, the UI entity that has focus, and — refreshed every frame — which widget the pointer is over and whether a widget is holding a press. Scripts read and change it with:
Mode is global for the running game, not per UI: in the two UI modes every shown GUI component is hit-tested, topmost first (later entities and later widgets draw on top). The ui_to_focus argument is what GetFocusedUI() returns afterwards; it is tracked by entity id, so destroying that entity simply clears the focus. A button fires on_clicked on release inside the button that was pressed, like every desktop UI: press on it, drag off, release elsewhere = no click. While a button is held it “captures” the pointer, so gameplay sees neither the press nor the release. Buttons tint themselves lighter while hovered and darker while pressed, and half-transparent when disabled. Only widgets marked Blocks Input in the GUI Editor (hit_test in the .wgui) take part in hit-testing. Buttons default to on, everything else to off, so a HUD label never swallows a gameplay click while a menu panel does. IsPointerOverUI() reports exactly this: the pointer is over a blocking widget, or a button is holding a press. The on_clicked callback runs with the full script API available: self() is the entity whose script called BindOnClicked, and find_entity, HideUI, DestroyUI, SetInputModeGameOnly, is_key_down and friends all work inside it. Example — a pause menu that hands input to the UI and back:

Time

Math library

Global functions mirroring <cmath>, plus small engine-specific helpers. Trig functions operate on radians; use radians()/degrees() to convert to/from the degree values used by transforms.

Strings

The string type only concatenates with other strings, so numbers go through to_string first: ammo_text.SetText(to_string(ammo_in_mag) + " / " + to_string(reserve_ammo));.

Save data

A small key/value persistence layer. Values live in a single in-memory store shared by every script in the running game — set them from any entity’s script and read them back from any other, then write the whole store out to a named slot file when you actually want it on disk. The store survives load_level, which is how a player carries its inventory from one level to the next (see the FPS guide).
slot_name is just a save’s identifier, e.g. "slot1" or "autosave" — don’t include a path or extension, and don’t use it for player-facing text: only the filename portion is ever used (so a value like "../../whatever" still resolves inside Saves/), and .zer is appended automatically if missing. Example — a simple checkpoint system:
Remarks The slot file is a ZER document (vendor/ZER/ZER/ZER.h) with one block per key, so keys that contain each other ("weapon" / "weapons", "level" / "level_index") are safe:
Values are stored as text (%.9g for floats, x,y,z for a vec3, 1/0 for a bool), which is why a getter of the wrong type falls back to its default rather than reinterpreting bits.

Complete examples

Two real scripts from a project built on this engine, included in full because they exercise most of the API together — transform, hierarchy, physics, input, and the camera yaw/pitch quirk — and demonstrate the recommended patterns for each.

Third-person character controller

WASD moves relative to the camera; the character turns to face its movement direction; the mouse orbits a detached camera around the player. The player root has a character controller; the camera is a child in the prefab (a .wprefab can only have one root) and is detached at run time so it doesn’t inherit the character’s rotation. This is test_script.wscript in the Test project.

First-person shooter

The complete first-person campaign — player rig with head pitch and a weapon parented under the camera, four weapon classes found along the way (hitscan raycast shooting and a rocket launcher), dash and double jump, grenades, damage over send_message, melee, ranged, charging and boss enemies, rooms that lock down and spawn their enemies when entered, key doors, teleporters that carry the player’s state to the next level through the save data, dynamic music layers, pickups and a HUD — is documented in the FPS guide; the scripts are fps_player, fps_enemy, fps_shooter, fps_brute, fps_boss, fps_projectile, fps_rocket, fps_explosion, fps_explosive, fps_grenade, fps_area, fps_door, fps_teleporter, fps_music, fps_pickup, fps_despawn, the data components fps_level, fps_spawn_point and fps_music_layer and, for the older arena level, fps_wave_director, fps_target and fps_spawner (.wscript) in the Test project’s Game/Scripts folder.

Flashlight toggle

A minimal example of input handling and toggling component visibility — attach to an entity that has a spot_light component.

Known limitations and quirks

A consolidated list of every non-obvious behavior called out in this document, for quick reference:
  • Rotation order is X-outermost. Euler angles compose as Rx * Ry * Rz, so set_rotation(pitch, yaw, 0) tilts about the world X axis and rolls the view once yaw is non-zero. Use set_look_rotation(pitch, yaw) for anything that aims, or split yaw and pitch across a parent/child pair. See Transform.
  • Forward is -Z for everything — cameras, spot/directional lights, get_forward(). A mesh modelled facing +Z (Mixamo characters) shows its back along get_forward(); rotate the mesh child 180° about Y inside its prefab. See World transform and direction vectors.
  • destroy() is deferred to the end of the frame’s script update. Handles stay valid until then; lookups and send_message already skip queued entities. See Identity, naming, and tags.
  • A spawned prefab’s behaviours exist as soon as spawn_prefab returns, but Start runs next frame, and its colliders enter the physics scene on the next physics step. Set its fields right after spawning; anything its Start computes from them isn’t there yet. See Entity spawning.
  • send_message carries one float. Richer data belongs on the entity (component properties, the receiver’s own fields); the receiver gets the sender as a handle. See Messaging between scripts.
  • Raycasts only see physics shapes (colliders and character controllers), never bare static meshes; trigger shapes never block. See Raycasts.
  • A character controller overwrites set_position every physics step — teleport it with set_character_position. See Physics.
  • A .wprefab can only save one root entity. Anything else must be a nested child, even if it logically shouldn’t inherit the root’s transform — detach it at run time with set_parent(null) instead. See Hierarchy.
  • Prefab instances are rebuilt from their file on every level load, and the entities inside them get fresh ids each time — find them by name, not by id. Only an instance root’s name, position, rotation, tags, script field values and any scale axis changed on the instance belong to the level; everything else — its scale included — comes from the prefab. See Prefabs.
  • load_level is deferred to the end of the frame’s script update, and destroys every entity including the caller. Carry state across it in the save data. See Levels.
  • entity.add_component() can’t create every component_type. Only static_mesh, sound, point_light, spot_light, directional_light, camera, skybox, fog, post_process, and skeletal_mesh are runtime-creatable; material (there is no such component any more — materials live on the mesh component), scripting, rigidbody, the collider types, and sprite always return false. See Component management.
  • Only a Dynamic rigidbody responds to forces/velocity. Kinematic and Static bodies make every physics function in this API a no-op; move a Kinematic body with set_position/set_rotation instead. See Physics.
  • A Dynamic body’s transform is owned by physics after simulation — direct set_position/set_rotation calls are overwritten next frame, unless the corresponding axis is locked (Freeze Rotation), in which case that one axis is left untouched by the simulation and safe to write directly. See Physics.
  • Reading a Dynamic body’s get_rotation().y back as a continuous yaw signal is unreliable past ±90° of turn (the underlying Euler decomposition aliases beyond that range). Track intended yaw as a script-side variable instead of reading it back — see the third-person controller example.
  • set_mouse_hidden(true) also captures/grabs the mouse for relative look input — there’s no way to hide just the cursor icon while keeping normal absolute mouse movement. It is only honoured in game_only input mode. See Input.
  • Input is filtered by the input mode. A click a widget consumed never reaches is_mouse_button_pressed; in ui_only mode every input query reads idle. Check GetInputMode()/IsPointerOverUI() rather than guessing. See UI and widgets.
  • Only widgets marked “Blocks Input” are hit-tested. A panel or image that should block clicks to the world behind it needs the flag on; buttons have it on by default. See UI and widgets.
  • HideUI sticks. A GUI component’s auto show only fires once, when the component is first updated, so hiding a UI is not undone next frame.
  • Entity handles (entity@) aren’t reference-counted. Destroying an entity elsewhere leaves any handle your script was holding dangling. See Language notes.
  • No root-motion extraction. A playing animation clip never moves the entity — drive position/velocity yourself. See Animation.
  • Each entity gets its own class instance (and its own module), so members — and the script globals of a class-less script — are per-entity state, never shared between two entities running the same .wscript. See Overview.
  • Only field values that differ from the script’s own initialiser are stored. Change a default in the .wscript and every entity that never overrode that field follows it; that is deliberate, but it does mean a value you set to exactly the default is not recorded. See Behaviour classes and inspector fields.
  • An entity@ field is stored by entity id. Ids inside a prefab instance are regenerated on every load, so reference a level entity or an instance root, never content nested inside an instance.
  • The editor compiles and constructs a script to read its defaults, so a behaviour’s constructor runs in the editor as soon as the component shows it. Keep constructors to initialising members; do the work in Start().
  • No #include. Each .wscript is compiled as a single, self-contained file.
  • A script that fails to compile is reported once and left alone until its asset changes or the level is reloaded; it is not retried every frame.
  • key is the name of the input enum, so a script global or local named key is a compile error (“Name conflict. ‘key’ is an extended data type”). The same goes for the other registered type names (entity, vec3, mouse_button, component_type, input_mode, the widget types).