Skip to content

Inheritance

Compiles

Often the game already has a script that does nearly what you want. Inheritanceinheritance: Building one script on another with super: yours gets the parent's fields and functions and changes only what it needs to. lets you start from it: your script gets all of its fields, functions and events, and you change only the parts you need.

The first line of an inheriting script names the parent by its path in the game’s data:

super "/data/catch/scripts/utility/entity_mover/entity_mover_rotate.vscript"
let bob_height: float = 40.0

That’s already a complete, working script: it spins exactly like the shipped rotator, and it has one extra setting. Everything the parent declared (its fields, its functions, its events) is part of yours.

A parent marks the functions a child may replace with virtual. Replace one by declaring a function with the same name and parameters, marked override:

super "/data/catch/scripts/utility/entity_mover/entity_mover_rotate.vscript"
override fn update_rotation() {
// The parent's rotation no longer happens: this body replaces it.
}

Usually you don’t want to replace the parent’s behaviour so much as add to it. super::name(…) runs the parent’s version from inside yours:

spinning_bobber.vscript
// spinning_bobber.vscript: the shipped rotator, plus a gentle bob up and
// down.
//
// Everything about spinning comes from the parent: its fields (`period`,
// `axis`) still show up in the editor, and its code still runs. This script
// adds two fields and changes one function.
super "/data/catch/scripts/utility/entity_mover/entity_mover_rotate.vscript"
// How far it rises and falls from where it was placed, in world units.
let bob_height: float = 40.0;
// Seconds for one full rise and fall.
let bob_seconds: float = 2.0;
// Where it started, so the bob is around its placed position.
let mut start_position: vec3 = vec3(0, 0, 0);
// The parent's `created` finds the transform and starts the spin; keep
// that, then remember where we are.
event export fn created() {
super::created();
start_position = transform.position;
}
// The parent calls this every tick to turn the entity. Turn it as before,
// then move it up or down.
override fn update_rotation() {
super::update_rotation();
let seconds: float = $time.ms_to_seconds($time.get_gameplay_time_ms());
let wave: float = $math.sin(seconds / bob_seconds * 6.2831855);
transform.position = start_position + $math.vec3(0.0, 0.0, wave * bob_height);
}

update_rotation runs the parent’s spin, then moves the entity up or down. created does everything the parent’s created did, then remembers where it started. The field transform is the parent’s; the child reads it as if it were its own.

Events work the same way: declare an event with the parent’s event’s name, and call super:: to keep the parent’s behaviour.

The shipped scripts most often used as parents are families built for exactly this: movers (entity_mover_base and its _rotate, _physics children), contract conditions, camera controllers, menus and widgets. A parent with several virtual functions is one designed to be extended.

For programmers

Single inheritance; the parent’s code is inlined into the child when compiled, with the child’s fields laid out after the parent’s. virtual / override give dynamic dispatch within the hierarchy, and super::f() is a static call to the parent’s copy. Inherited fields are addressed by bare name. A parent you name must exist in the game data, or every inherited name is unknown (V0203).