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 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:
- the class named after the file (
enemy_patrolinenemy_patrol.wscript, case-insensitive) — the rule to rely on; - otherwise, the only class in the file that declares an
Update(float); - otherwise, the file’s single class.
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_patrolhave their ownspeedand their ownm_elapsed, and nothing is reset between frames. - Only a single file is compiled per script (
CScriptBuilder::AddSectionFromFileis called once) — there is no#includemechanism. Keep each.wscriptself-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.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
Anentity@ field is stored as the target entity’s id and resolved against the scene when the behaviour is built:
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’sGetComponent<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:
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 sameEnemy 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.
entityis a reference type (entity@). Assign with the@operator and compare againstnullwithis/!is: -
Entity handles do not keep entities alive. The
entitytype is registered withasOBJ_NOCOUNT— AngelScript performs no reference counting on it. If something else callsdestroy()on an entity (or the scene deletes it), any handle your script is still holding becomes a dangling pointer. Re-acquire handles (e.g. viafind_entity/find_entity_by_name) rather than caching them across a destroy, if you can’t guarantee lifetime. -
self()returns theentity@handle for the entity that owns the currently-running script — the equivalent of Unity’sgameObject. It is valid anywhere in the script: inStart/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
stringtype (via thescriptstdstringadd-on) and support+concatenation with other strings; convert numbers withto_string(see Strings). -
Script functions can take
const string &inand 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 tois_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
Example:
World transform and direction vectors
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 askeletal_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:
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 asound 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:
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 arigidbody 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
Raycasts
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.
ignoreskips that entity and everything parented under it — passself()when shooting from inside your own capsule.directiondoes 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: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:
Remarks
- Delivery is synchronous:
OnMessageruns beforesend_messagereturns, 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. senderis the entity whose script calledsend_message(the binder entity inside a widget callback).- A receiver that hasn’t run yet gets its
Startfirst, 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.
"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.
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.
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
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 survivesload_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:
vendor/ZER/ZER/ZER.h) with one block per key, so keys that contain each other ("weapon" / "weapons", "level" / "level_index") are safe:
%.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 (hitscanraycast 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 aspot_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, soset_rotation(pitch, yaw, 0)tilts about the world X axis and rolls the view once yaw is non-zero. Useset_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 alongget_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 andsend_messagealready skip queued entities. See Identity, naming, and tags.- A spawned prefab’s behaviours exist as soon as
spawn_prefabreturns, butStartruns next frame, and its colliders enter the physics scene on the next physics step. Set its fields right after spawning; anything itsStartcomputes from them isn’t there yet. See Entity spawning. send_messagecarries onefloat. 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_positionevery physics step — teleport it withset_character_position. See Physics. - A
.wprefabcan 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 withset_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_levelis 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 everycomponent_type. Onlystatic_mesh,sound,point_light,spot_light,directional_light,camera,skybox,fog,post_process, andskeletal_meshare runtime-creatable;material(there is no such component any more — materials live on the mesh component),scripting,rigidbody, the collider types, andspritealways returnfalse. 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_rotationinstead. See Physics. - A Dynamic body’s transform is owned by physics after simulation — direct
set_position/set_rotationcalls 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().yback 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 ingame_onlyinput mode. See Input.- Input is filtered by the input mode. A click a widget consumed never reaches
is_mouse_button_pressed; inui_onlymode every input query reads idle. CheckGetInputMode()/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.
HideUIsticks. 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
.wscriptand 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.wscriptis 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.
keyis the name of the input enum, so a script global or local namedkeyis 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).