Pickup
Pickup turns an entity into a coin: the nearest controlled entity within its radius takes its reward, and the coin is removed for good in the same step. The reward goes into the player’s wallet when the taker is the character the characters plugin spawned for that player, and into the taker’s own wallet otherwise, such as a kart a player drives. A game that keeps the wallet on the entity, as Keep coins across scenes and visits shows, pays the character itself. That taker is a character or any entity a player controls or once controlled, such as a kart its player left. Nothing brings a taken pickup back; a thing that comes back after it is taken is a behaviour of your own, as Write your own behaviour shows how to build. Put <Pickup> inside an <Entity>. The example template’s coin:
import { Bob, box, Entity, Pickup, type Position, RigidBodyType, sounds, Spin,} from "@spawnite/engine";
/** One coin: it turns, bobs, and goes into the wallet of whoever reaches it, * with a chime. * Its body follows the bob, so the ball bounces off it where it is drawn. */export interface CoinProps { position: Position;}
export function Coin({ position }: CoinProps) { return ( <Entity position={position} body={{ bodyType: RigidBodyType.Kinematic }} collider={{ shape: box({ size: [0.8, 0.1, 0.8] }) }} > <Spin speed={2} /> <Bob height={0.2} /> <Pickup reward={1} sound={sounds.pickup} /> <mesh> <cylinderGeometry args={[0.4, 0.4, 0.1, 24]} /> <meshStandardMaterial color="gold" /> </mesh> </Entity> );}Pickup measures a distance, so it needs no body and no collider. This coin has a kinematic body and a box collider only so that the example’s ball bounces off it.
Pickup takes three props, listed in PickupProps:
reward: the coins it pays. A fraction is allowed. A pickup with a reward of 0 is taken and pays nothing, and a reward below 0 throws in the step that would pay it, asaddCoinsdoes.radius: how close a character must come, in metres. 1 when left out.sound: a file, or a?urlimport, played once as the pickup is taken, as Sound plays one. It plays nothing before the player’s first input, and a headless run, such asspawnite simulate, never loads it. The engine ships a chime assounds.pickup.
What Pickup needs
Section titled “What Pickup needs”- The
behaviours()plugin in the game’spluginslist insrc/game.ts. The example template lists it. A scene that places a Pickup in a game without it throws as it loads, withbehaviours() is off in this world: add behaviours() to plugins in src/game.ts. - A controlled entity to take it: an entity that carries the engine’s
ControlledTraittrait, whether or not a player controls it now. Every character<CharacterSpawn>spawns carries it, and so does any entity acontrolcall links to a player, such as a kart, which keeps it after its player gets out. An NPC carries none and never takes a pickup. A view reads the player’s own character withuseCharacter().
Who takes a pickup
Section titled “Who takes a pickup”Each step, Pickup finds the controlled entity nearest to its own entity and gives it the pickup where it stands within radius. The distance is measured in 3D, from the character’s position, which is at its feet, to the pickup entity’s position:
- Height counts. With the default radius, a coin floating 1.5 m above the ground is out of reach of a character that walks under it, and is taken only where a jump lifts the character’s feet to within 1 m of it. Place a coin no more than about 1 m above the ground, or raise
radius. - Where several controlled entities are in reach, the nearest takes the whole reward.
- Pickup does not ask who reaches it. A downed character takes one, and so does a kart. For a coin that only some characters may take, such as one team’s, write a system that checks who stands near and pays with
addCoins, as Coins and the wallet shows. Then it callsemitEvent(step, TakenEvent, { entity })anddestroy(step, entity)on the coin, as Pickup does, so a view that hearsTakenEvent, such as the effect below, still plays.
Use Pickup for coins. For an item that goes into the bag, use Loot, which measures reach the same way. For something that happens when a character reaches a place, use Trigger.
What taking a pickup changes
Section titled “What taking a pickup changes”In the step a character takes it, Pickup does the following, in order:
- Pays
rewardwithaddCoinsinto the wallet of the player whose character took it, which thecharactersplugin gave her player as it spawned her character, unless the game keeps the wallet on the entity, and then into the character’s own. Any other taker, such as a kart a player drives, a character no player plays, or one in a world with no players, takes the coins into a wallet of its own, whichaddCoinsgives it where it has none. While a Round plays, the coin counts toward the round’s score. - Emits the
TakenEventevent on the pickup entity. A pickup the scene removes untaken emits none, so it plays no sound. - Destroys the entity.
The sound plays as the game hears TakenEvent, at the effects volume and as loud wherever the player stands: it is not positional. In a room, every player hears every pickup, whichever player took it.
Coins and the wallet covers reading the coins, spending them and keeping them in the save.
Who decides, alone and in a room
Section titled “Who decides, alone and in a room”Pickup changes a wallet, so it runs in the room, or in the game itself when it plays alone. A client, the game running in one player’s browser, never runs it in a room. The client draws the coin until the room’s update removes it, and the same update carries TakenEvent, which the client hears before it removes the coin.
The room takes the coin when its own copy of the character reaches it. A client draws the player’s character ahead of the room, so on the player’s screen the coin goes about one round trip to the room after the character touched it, plus a few ticks (an estimate from how prediction works).
An effect where it is taken
Section titled “An effect where it is taken”A “+1” or a burst of sparks where a coin is taken plays in a view that hears the coin’s TakenEvent. The scene must mount <FloatingText /> for the text, and <Particles effects> holding the sparks effect:
import { Entity, Pickup, TakenEvent, useEntity, useEvent, useFloatingText, useParticles, type ParticleEffect, type Position,} from "@spawnite/engine";
export interface CoinProps { position: Position; /** The burst where the coin is taken, one of the scene's * `<Particles effects>`. */ sparks: ParticleEffect;}
export function Coin({ position, sparks }: CoinProps) { return ( <Entity position={position}> <Pickup reward={1} /> <CoinTaken position={position} sparks={sparks} /> <mesh> <cylinderGeometry args={[0.4, 0.4, 0.1, 24]} /> <meshStandardMaterial color="gold" /> </mesh> </Entity> );}
// Under the coin's Entity, so `useEntity()` is this coin. Without// `{ entity }` the view hears every coin's TakenEvent, and each coin// would burst whenever any coin is taken.function CoinTaken({ position, sparks }: CoinProps) { const floatingText = useFloatingText(); const particles = useParticles(); useEvent( TakenEvent, () => { floatingText.show({ at: position, text: "+1", color: "gold" }); particles.spawn(sparks, { at: position, color: "#ffd166" }); }, { entity: useEntity() }, ); return null;}Three parts of it matter:
- The view sits under the coin’s
<Entity>and passes{ entity: useEntity() }touseEvent. Without it, every coin’s view hears every coin’sTakenEvent, and each coin plays its effect whenever any coin is taken. TakenEventcarries no data. Who took the coin is in the wallet, not in the event.- The view takes the place from the coin’s
positionprop, not from the entity. In a game played alone, the entity is already destroyed when the view hears the event. In a room, the client keeps the removed coin until its step and its views have read the event, then removes it. The prop gives the same place in both.
The engine’s test of this file mounts three coins, takes one, and checks that a single “+1” shows.
Check that a coin is taken
Section titled “Check that a coin is taken”The example game’s run scene places three coins in a line ahead of the character. The following command runs the scene headless, holds W, and stops as soon as any wallet holds a coin:
spawnite simulate --scene run --keys w --until "Object.values(entities).some((e) => e.wallet?.coins > 0)" --fields walletIn the example game on 2026-10-05, it printed The condition held on step 28; it returned true., then the player’s wallet with "coins": 1. The run joins a player, who holds the wallet from the start with 0 coins, so the condition holds on the step the first coin is taken. Where no coin is taken within the default budget of 10 s of game time, the command says the condition did not hold and exits 1.
Limits
Section titled “Limits”- One character takes each pickup, and takes the whole reward.
- A taken coin comes back each time its scene loads: Pickup keeps nothing in the save, and the scene places every coin it holds again. A Loot with an
idstays taken across loads. - The sound is the same everywhere in the world, and in a room every player hears every pickup.