Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

Targeting

Targeting chooses which hostile the player’s abilities strike. The chosen target is the TargetTrait { entity, pinned } trait on her character. Every ability reads the trait, and none asks how the target was chosen.

Its props are in TargetingProps.

Put <Targeting /> anywhere in the scene:

import { CharacterSpawn, Targeting } from "@spawnite/engine";
export function Character() {
return (
<>
<CharacterSpawn abilities={["arcane-bolt"]} />
<Targeting />
</>
);
}

With no props, it chooses as follows:

  • Soft lock. Each frame it targets the nearest hostile target in front of her: within 8 m, and within 60 degrees either side of her facing.
  • A click or a tap on a hostile target pins it. A pinned target stays, behind her or far away, until it dies or she pins another. Then soft lock takes over again. Where the camera walks her to a click, a click on a hostile target selects it and walks her nowhere.
  • Tab pins the next hostile target in front, nearest first, and the nearest again after the farthest.

A target is anything with the TargetableTrait { faction } trait whose faction is Faction.Hostile, the default, and whose health is above zero. A click picks a target by the bounds of what it draws.

It also draws a ring on the ground under the target, sized to its body and coloured by its faction: red for hostile, yellow for neutral and green for friendly. The ring is faint and thin while the soft lock chose the target, solid and wider once the player pinned it, and it flashes each time a hit lands on the target.

<Health> alone does not make an entity a target, and neither does an NPC’s faction. Give each enemy TargetableTrait with a behaviour of the game’s own beside its <Health>, as Behaviours describes; the behaviour sits inside an <Npc> as it does inside an <Entity>. A system that spawns enemies adds TargetableTrait among their traits, as the spawner on the Health page does. The room’s tests mount this dummy in a room’s scene, check that a player’s page holds it as a hostile target and that the room lets a cast target it, and check that an entity with Health alone is no target:

packages/room/test/outside/TrainingDummy.tsx
import {
defineBehaviour,
Entity,
Faction,
Health,
RunContext,
TargetableTrait,
useBehaviour,
type EntityProps,
} from "@spawnite/engine";
const TargetableBehaviour = defineBehaviour({
name: "targetable",
trait: TargetableTrait,
runsOn: RunContext.Server,
description: "An entity abilities and Targeting can pick.",
});
interface TargetableProps {
/** Whose side it is on: abilities strike a hostile one. */
faction?: Faction | `${Faction}`;
}
export function Targetable({ faction = Faction.Hostile }: TargetableProps) {
useBehaviour(TargetableBehaviour, { faction: faction as Faction });
return null;
}
export function TrainingDummy({ position }: Pick<EntityProps, "position">) {
return (
<Entity position={position}>
<Health maximum={60} />
<Targetable />
<mesh>
<cylinderGeometry args={[0.4, 0.4, 1.8]} />
<meshStandardMaterial color="#b08850" />
</mesh>
</Entity>
);
}

Each prop changes one thing:

  • mode: TargetMode.SoftLock, the default, or TargetMode.Tab for classic tab-target, which selects nothing until the player clicks, taps or presses Tab.
  • range: metres within which a target is in front. 8 by default.
  • angle: degrees either side of her facing within which a target is in front. 60 by default.
  • cycleKey: the key that cycles, as KeyboardEvent.code names it. "Tab" by default.
  • marker: the mark under the target, one of the GroundMark shapes that the player’s walk marker draws: GroundMark.Ring, the default, Pulse, Sigil, Motes, or None for no mark.
  • colors: the mark’s colour for each faction, such as { [Faction.Hostile]: "#b04dff" }. A faction left out keeps its default.
import { Targeting, TargetMode } from "@spawnite/engine";
export function TabTarget() {
return <Targeting mode={TargetMode.Tab} range={30} />;
}

useTarget() returns the player’s target and whether she pinned it, as { entity, pinned }, and re-renders when either changes. A target frame reads it:

import { useHealth, useTarget } from "@spawnite/engine";
export function TargetFrame() {
const { entity } = useTarget();
const health = useHealth(entity);
return health ? <p>{health.current}</p> : null;
}

A game that draws its own mark under the target reads it the same way, and passes marker={GroundMark.None} so that <Targeting> draws none.

Targeting is one writer of TargetTrait. A game’s own system that writes it has made a new mode, such as the lowest-health ally for a heal. Leave <Targeting> out, and write the trait on the player’s character:

import type { Entity } from "koota";
import { readLocalCharacter, pinTarget, type World } from "@spawnite/engine";
export function targetBoss(world: World, boss: Entity) {
const character = readLocalCharacter(world);
if (character) pinTarget(character, boss);
}

The functions <Targeting> runs are exported for a game’s own mode: findTargetsInFront, updateTarget, cycleTarget, pinTarget and findClickedTarget.

TargetTrait is the page’s alone. No stream carries it, and the room never reads it: each cast press carries the target, which travels as the key the room files it under, and the room judges its own copy of that target. So a page cannot cast at a target out of range by writing TargetTrait, and a room’s own world, which has no player of its own, chooses no target.

World of Warcraft and Final Fantasy XIV tab-target, and the bolt always lands. Guild Wars 2 and New World aim at a reticle and can miss. Unreal’s Gameplay Ability System reads target data from a TargetActor that a game subclasses. Roblox, Unity, Godot and PlayCanvas leave targeting to each game. This engine soft-locks by default, so a player on a phone casts with no cursor, and keeps World of Warcraft’s click and Tab on top.

World of Warcraft marks the selected unit with a circle on the ground in its reaction colour. Final Fantasy XIV draws a turning ring and an arrow over the head, and Diablo outlines the model. This engine draws World of Warcraft’s circle by default, with the same shapes as the walk marker, so one setting names a mark for both.