Pitfalls
Each of these was found the hard way. They’re grouped by what you notice first. If your problem isn’t here, the Debugging recipes will help you narrow it down.
Nothing happens
Section titled “Nothing happens”An event never runs
- You see
- A print as the first line of your event never appears.
- Because
- The engine finds events by name. A misspelled name, a function without event export, or a component that isn't on any entity the game loads is never called, and nothing warns you.
- Do this
Spell the event exactly as the engine sends it (
tick,late_tick,created,trigger_overlapped; see the Events reference), write itevent export fn, and check the component is placed in a level that’s loaded.
Passing null to mean “me”
- You see
- A lookup like $entity.get_child_named(name, true, null) always comes back empty.
- Because
- Native descriptions call the entity “(Optional) … Omit to use the entity of the calling script.” You can't omit arguments, and null means “nothing”, not “me”.
- Do this
Pass your own entity explicitly:
$entity.get().
A placed prefab can't find its partner
- You see
- A search by name finds nothing, or finds the wrong copy, once the object is placed as a prefab.
- Because
- A prefab's entities become children of the entity that places it, and every instance shares the same internal names. A search from your own entity can't see siblings of the placement; a search of the whole world can't tell instances apart.
- Do this
Name the partner by its placement name (unique per level), and search upwards from your entity: each parent’s children in turn (see Something nearby). Keep the placements inside a group: something placed at the very top of the level has no parent to search from.
$world.get_entity finds nothing
- You see
- $world.get_entity(name, …) returns null for an entity you can see in the level.
- Because
- Observed: it doesn't find entities that a level merely places, with or without an owner.
- Do this
Search from your own entity with
$entity.get_child_named, climbing parents if needed.
Reading another component's setting gives nothing
- You see
- other.some_setting is empty, and the log may say the script “does not have a mutable named …”.
- Because
- The dot reaches another component's let mut fields. Its constants, the settings chosen in the editor, can't be read that way.
- Do this
Read mutables only. For a setting, look for a native or an event that reports it, or keep the value somewhere you can reach.
It works, then undoes itself
Section titled “It works, then undoes itself”A move doesn't stick
- You see
- A player standing still is moved correctly, but one who walks or jumps onto the spot only jerks and stays put.
- Because
- The player's movement writes their position at the end of its own update, from what it worked out at the start. A position you set earlier in the frame, in tick or inside trigger_overlapped, is overwritten.
- Do this
Record what should move in a field when the event happens, and move it in
late_tick. See Moving things.
A player move only happens on the server
- You see
- Online, the camera jumps and the player ends up where they started.
- Because
- Each player's game simulates their own movement. A position written only on the server is recalculated away by the client a moment later.
- Do this
Run the move on every machine: no
$net.is_server()guard around it, and$net.set_replicated(true, false)intype_created, as the shipped knockback scripts do.
A value changes back by itself
- You see
- You set a field and it snaps back to an older value a moment later.
- Because
- It's a replicated field and you changed it on a client. The server's next update overwrites the client's copy.
- Do this
Change replicated fields only on the server (
if !$net.is_server() { return }), and react on clients infield_replicated.
A ball goes back where it was
- You see
- Writing a ball's transform position does nothing, or it jumps back.
- Because
- The ball's own script owns where it is, and overwrites the transform from its own state.
- Do this
Ask the ball to move:
$component.queue_event("set_position_and_rotation", ball, [position, rotation], true)on itsball_transform. Leave held balls alone.
It breaks something
Section titled “It breaks something”Spawning an effect crashes the game
- You see
- The game closes with an access violation the moment the effect should play.
- Because
- The effect asset was read off an effect component with a dot. That field is a constant, so the read gave nothing, and spawning nothing crashes.
- Do this
Don’t spawn effects from references you fished out of other components. Place an
effectin the level withplay_on_createdoff and send itplay. See Effects and sound.
An effect replays out of nowhere
- You see
- A one-shot effect plays again whenever you look back at it, from any distance, with nobody near.
- Because
- Without kill_on_done, a finished one-shot effect still counts as playing, and it's drawn again whenever its entity comes back into view.
- Do this
Turn on
kill_on_doneon the effect component.
An inherited script half works
- You see
- A script built on a shipped one fails in ways that seem unrelated: fields the parent should have filled are null.
- Because
- Redefining created (or type_created) replaced the parent's. Without super::created() the parent never set itself up.
- Do this
Call
super::created()(orsuper::type_created()) first in any event you redefine.
An effect won't change colour
- You see
- The log says Invalid vfx attribute Color and the effect keeps its colour.
- Because
- The effect was made with its colour baked in, not exposed as an attribute. Only attributes the effect declares can be set.
- Do this
Use a different effect that declares a colour attribute, or colour the scene another way (a light, a material).
It won’t compile
Section titled “It won’t compile”“takes 3 arguments but 2 were given”
- You see
- A native call the reference calls optional is rejected (V0303).
- Because
- Every native argument must be passed, optional or not.
- Do this
Pass the value meaning none:
"",null,0orfalse. See Calling natives.
x += 1 or list[0] doesn't parse
- You see
- The compiler stops at += , ++ or [ with “expected one of …” (V0101).
- Because
- The language has no compound assignment, increment, or index syntax.
- Do this
Write
x = x + 1, and uselist.get(i)/list.set(i, value).
A field can't be typed as my enum
- You see
- “field … cannot be typed …” on let mut state: MyEnum.
- Because
- Enums name int values; a field's type must be one the engine stores.
- Do this
Declare the field
intand assign the enum’s options to it.