Skip to content
GitSiteEmail

Trackers

The Trackers component efficiently renders multiple elements at game-world positions—such as minimap blips, nameplates, and damage numbers—that update frequently. Rather than using SolidJS signals for updates, you call updateTrackers directly or emit events from the game engine, and Trackers immediately applies the new positions to the DOM in the most performant way.

Every component ships with the boilerplate. If you are working in your own project, add Trackers with the Gameface CLI:

Terminal window
npx gameface-cli add Trackers

The CLI pulls in whatever Trackers depends on. To refresh an existing copy, update it instead.

Give Trackers an id - this is the channel other code will call updateTrackers on. Inside it, each Trackers.Item needs its own id, which is what you’ll target when moving that specific item.

import Trackers, { updateTrackers } from '@components/Performance/Trackers/Trackers';
<>
<button onClick={() => updateTrackers('minimap', { id: 'blip-1', x: Math.random() * 180, y: Math.random() * 180 })} style={{ background: '#4fd6ff', color: '#000', padding: '0.5rem 1rem', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>Click to change position</button>
<div style={{ position: 'relative', width: '200px', height: '200px', background: '#222' }}>
<Trackers id="minimap">
<Trackers.Item
id="blip-1"
style={{ width: '12px', height: '12px', 'border-radius': '50%', background: '#4fd6ff' }}
/>
</Trackers>
</div>
</>

To test this sample click the button a few times - each click calls updateTrackers('minimap', { id: 'blip-1', x, y }) and the blip jumps straight to the new spot.

Prop NameTypeDefaultDescription
idstring-The identifier name that addresses this Trackers instance. This is the first argument you pass to updateTrackers method.
styleJSX.CSSProperties{}Inline styles to apply directly to the component’s root element.
classstring""Additional CSS classes to apply to the component.
Prop NameTypeDefaultDescription
idstring-The item’s own id. This is what you target with updateTrackers to move, hide, or show this specific item.
styleJSX.CSSProperties{}Inline styles for the item.
classstring""CSS classes for the item.
scaleAccessor<number> | numberundefinedProperty used to control the item’s scale. Pass a signal getter or a number and the item scales whenever it changes.
rotateAccessor<number> | numberundefinedProperty used to control the item’s rotation in degrees. Pass a signal getter or a number and the item rotates whenever it changes.

This method can be used to update the position and visibility of Trackers.Item components within a specific Trackers instance directly from JavaScript.

updateTrackers(trackersId: string, updates: TrackersEventPayload | TrackersEventPayload[]): void
ParameterTypeDescription
trackersIdstringThe id of the Trackers component to update.
updatesTrackersEventPayload | TrackersEventPayload[]A single item update, or an array of them to update several items in one call.

Where TrackersEventPayload is:

FieldTypeDescription
idstringThe id of the Trackers.Item to update.
xnumberNew horizontal position, in pixels.
ynumberNew vertical position, in pixels.
hidebooleanSet to true to hide the item, false (or omit it) to show it.

Calling updateTrackers for a Trackers id that isn’t currently mounted, or a Trackers.Item id that doesn’t exist, is safe - it’s simply ignored (with a warning logged in development).

  • Setting class or style on a Trackers.Item, should not overwrite the following properties otherwise it won’t be positioned correctly at runtime:
    • position - Internally Trackers.Item is positioned absolutely within its parent container.
    • transform - The Trackers.Item uses this CSS property to control its position with translate. If you need to scale or rotate the item use the scale and rotate props. Do not set transform yourself in an item’s style as it will be overwritten by the component.
  • Trackers.Item renders a div element in place, so it should be used directly as the wrapper around your item’s content.
// ✅ Good: Trackers.Item owns the transformation, while scale and rotate are composed safely.
<Trackers.Item
id="player"
scale={1.25}
rotate={15}
style={{ width: '24px', height: '24px', background: '#4fd6ff' }}
>
<span>Player</span>
</Trackers.Item>
// ❌ Bad: Setting transform yourself overwrites the position applied by Trackers component.
<Trackers.Item
id="enemy"
style={{ transform: 'rotate(15deg)' }}
/>

In some cases, positions of the items are updated from the UI using JavaScript. For that reason, you may need to call updateTrackers every frame to reflect the changes in the UI. Make sure to call it with multiple items batched together in a single array for efficiency if you have several items to update.

import { onCleanup, onMount } from 'solid-js';
import Trackers, { updateTrackers } from '@components/Performance/Trackers/Trackers';
const App = () => {
let frame: number;
const tick = () => {
updateTrackers('minimap', [
{ id: 'blip-1', x: Math.random() * 180, y: Math.random() * 180 },
{ id: 'blip-2', x: Math.random() * 180, y: Math.random() * 180 },
]);
frame = requestAnimationFrame(tick);
};
onMount(() => { frame = requestAnimationFrame(tick); });
onCleanup(() => cancelAnimationFrame(frame));
return (
<Trackers id="minimap" style={{ width: '200px', height: '200px' }}>
<Trackers.Item id="blip-1" style={{ width: '10px', height: '10px', background: 'red' }} />
<Trackers.Item id="blip-2" style={{ width: '10px', height: '10px', background: 'lime' }} />
</Trackers>
);
};
<>
<App />
</>

If you are using an engine and you want to control the position of a tracked item, you can emit events from the engine to the UI directly. The Trackers component internally listens for these events via engine.on('trackers:<id>', ...) and update the items accordingly.

Here is an example:

struct TrackerUpdate
{
const char* id;
float x;
float y;
};
class GameViewListener : public cohtml::IViewListener
{
public:
virtual void OnDOMBuilt() override
{
// Safe to trigger events now that the DOM is ready and JS handlers are active.
std::vector<TrackerUpdate> updates = {
{ "blip-1", 120.f, 80.f },
{ "blip-2", 140.f, 92.f },
};
m_View->TriggerEvent("trackers:minimap", updates);
}
private:
cohtml::View* m_View;
};

More on how to emit engine events can be found in the Gameface documentation .

Set hide: true to hide an item, and hide: false (or just leave it out) to bring it back.

// Hide it
updateTrackers('minimap', { id: 'blip-1', hide: true });
// Show it again
updateTrackers('minimap', { id: 'blip-1', hide: false });

scale and rotate take a signal getter, so the item reacts whenever you update the signal - no need to go through updateTrackers for these.

import { createSignal } from 'solid-js';
import Trackers from '@components/Performance/Trackers/Trackers';
const App = () => {
const [scale, setScale] = createSignal(1);
const [rotate, setRotate] = createSignal(0);
return (
<>
<button onClick={() => setScale((s) => s + 0.2)}>Scale up</button>
<button onClick={() => setRotate((r) => r + 15)}>Rotate</button>
<Trackers id="minimap" style={{ width: '200px', height: '200px' }}>
<Trackers.Item
id="blip-1"
scale={scale}
rotate={rotate}
style={{ width: '20px', height: '20px', background: '#4fd6ff' }}
/>
</Trackers>
</>
);
};
<>
<App />
</>