Two UI routes
Screen and Element components when the UI must live in the scene; HTML and CSS over the canvas when text, layout and accessibility matter more.
PlayCanvas · UI design & implementation
Interfaces for PlayCanvas projects, in the engine’s own element system or as an HTML layer over the canvas, sized for a link that has to load fast on a phone.
Live · PlayCanvas engine
A PlayCanvas scene with an HTML interface over it. The callouts are DOM elements projected from points on the model every frame.
Drag the item. The labels are HTML, pinned to points on the model.
PlayCanvas engine · loads on demand
Built for this page with the open-source PlayCanvas engine. Primitive geometry, two lights, no downloaded assets.
What we do in PlayCanvas
Native frameworks, used as intended, so your engineers inherit something familiar.
Screen and Element components when the UI must live in the scene; HTML and CSS over the canvas when text, layout and accessibility matter more.
Callouts, markers and tooltips that track a point in 3D space and stay readable as the camera moves.
Mouse, touch, keyboard and gamepad through one action layer, with touch targets sized for a thumb.
Lazy-loaded engine and assets, texture atlases, and UI that does not force a redraw of the scene.
In code
The same component in every runtime: a health bar whose ghost segment waits 450 ms, then drains over 600 ms. It is the bar running in the demo at the top of this page.
// Attached to a Group element under a Screen; fill and ghost are Image elements.
import { Script, math } from 'playcanvas';
export class HealthBar extends Script {
static scriptName = 'healthBar';
/** @attribute @type {import('playcanvas').Entity} */ fill;
/** @attribute @type {import('playcanvas').Entity} */ ghost;
/** @attribute */ ghostDelay = 0.45;
/** @attribute */ ghostTime = 0.6;
target = 1; shown = 1; hold = 0;
setHealth(pct) {
pct = math.clamp(pct, 0, 1);
if (pct < this.target) this.hold = this.ghostDelay;
this.target = pct;
this.fill.element.anchor = [0, 0, pct, 1];
}
update(dt) {
if ((this.hold -= dt) > 0) return;
const step = dt / this.ghostTime;
this.shown += math.clamp(this.target - this.shown, -step, step);
this.ghost.element.anchor = [0, 0, this.shown, 1];
}
}A trimmed sample to show structure and approach. Compare all five runtimes →
Covered as standard
Other runtimes
The design system and motion spec stay the same. Only the implementation changes.
PlayCanvas
Send a screenshot, a recording or a build, with your target platforms and dates. You will get a straight answer on fit and scope.