UI
HTML5 is super great at building UI's, there are many popular front end frameworks and toolkits that will deliver highly polished UIs, so we recommend that first.
Please consider html based first but if the UI makes more sense in-canvas look to screen elements.
Screen Elements
In Excalibur, if you want to display something like a HUD element or UI element, you can create an instance of ScreenElement. A screen element has the following semantics that differ from a regular actor:
- They automatically capture pointer events
- They do not participate in collisions
- They appear above all "normal" actors in a scene
- Invoking ScreenElement.contains will check against screen coordinates by default.
Other than that, they are the same as normal actors where you can assign drawings, perform actions, etc.
ts
import * as ex from 'excalibur'import Resources from './resources'class StartButton extends ex.ScreenElement {constructor() {super({x: 50,y: 50,})}onInitialize() {this.graphics.add('idle', Resources.StartButtonBackground)this.graphics.add('hover', Resources.StartButtonHovered)this.on('pointerup', () => {alert("I've been clicked")})this.on('pointerenter', () => {this.graphics.show('hover')})this.on('pointerleave', () => {this.graphics.show('idle')})}}game.add(new StartButton())game.start()
ts
import * as ex from 'excalibur'import Resources from './resources'class StartButton extends ex.ScreenElement {constructor() {super({x: 50,y: 50,})}onInitialize() {this.graphics.add('idle', Resources.StartButtonBackground)this.graphics.add('hover', Resources.StartButtonHovered)this.on('pointerup', () => {alert("I've been clicked")})this.on('pointerenter', () => {this.graphics.show('hover')})this.on('pointerleave', () => {this.graphics.show('idle')})}}game.add(new StartButton())game.start()
HTML-based UI
A screen element is useful because it's an actor, it is rendered within the game engine and can take advantage of all the power Excalibur provides. However, this can also be a drawback -- for performance reasons, you may want to host your game UI outside Excalibur. Since Excalibur is a web-based game engine, HTML & CSS is a natural way to write game UI but it does require you to hook into them through HTML DOM APIs.
The trick is positioning the game UI above Excalibur and reacting to Excalibur events appropriately. With some tweaks to the way you embed Excalibur, this can be accomplished without too much code.
Do not forget to set PointerScope.Canvas, otherwise Excalibur will capture all mouse input and HTML GUI won't work properly.
index.html
html
<!DOCTYPE html><html><head><style type="text/css">#root {/* When this is relative, child elements positioned absolutely willbe relative to this element, not the document providing more accuratepositioning, since the canvas will be at (0, 0) */position: relative;}#root #ui {/* This will make the UI appear on top of the canvas */position: absolute;top: 0;left: 0;}/* These are scene-based UI styles */#ui.MainMenu .button.button--start {position: absolute;top: 50%;left: 50%;background: red;color: white;}</style></head><body><!-- Define a wrapping element, in case you want to customize positions --><div id="root"><!-- Provide your own canvas to Excalibur --><canvas id="game"></canvas><!-- The UI will automatically be drawn above the game due to z-indexing --><div id="ui"><!-- This is where your game UI will go, dynamically created per scene --></div></div><script type="text/javascript" src="game.js"></script></body></html>
html
<!DOCTYPE html><html><head><style type="text/css">#root {/* When this is relative, child elements positioned absolutely willbe relative to this element, not the document providing more accuratepositioning, since the canvas will be at (0, 0) */position: relative;}#root #ui {/* This will make the UI appear on top of the canvas */position: absolute;top: 0;left: 0;}/* These are scene-based UI styles */#ui.MainMenu .button.button--start {position: absolute;top: 50%;left: 50%;background: red;color: white;}</style></head><body><!-- Define a wrapping element, in case you want to customize positions --><div id="root"><!-- Provide your own canvas to Excalibur --><canvas id="game"></canvas><!-- The UI will automatically be drawn above the game due to z-indexing --><div id="ui"><!-- This is where your game UI will go, dynamically created per scene --></div></div><script type="text/javascript" src="game.js"></script></body></html>
game.ts
ts
import * as ex from 'excalibur'// Hold a reference globally to our UI container// This would probably be encapsulated in a UIManager moduleconst ui = document.getElementById('ui')// Create our gameconst game = new ex.Engine({/*** Specify our custom canvas element so Excalibur doesn't make one*/canvasElementId: 'game',/*** Specify pointer scope to ensure that excalibur won't capture the mouse input* meant to be captured by HTML GUI*/pointerScope: ex.Input.PointerScope.Canvas,})/*** Our main menu scene, which will have HTML-based UI*/class MainMenu extends ex.Scene {onActivate() {// Add a CSS class to `ui` that helps indicate which scene is being displayedui.classList.add('MainMenu')// Create a <button /> elementconst btnStart = document.createElement('button')// Style it outside JavaScript for ease of usebtnStart.className = 'button button--start'// Handle the DOM click eventbtnStart.onclick = (e) => {e.preventDefault()// Transition the game to the new scenegame.goToScene('level')}// Append the <button /> to our `ui` containerui.appendChild(btnStart)}onDeactivate() {// Ensure we cleanup the DOM and remove any children when transitioning scenesui.classList.remove('MainMenu')ui.innerHTML = ''}}const level = new ex.Scene()const menu = new MainMenu()game.addScene('menu', menu)game.addScene('level', level)game.goToScene('menu')game.start()
ts
import * as ex from 'excalibur'// Hold a reference globally to our UI container// This would probably be encapsulated in a UIManager moduleconst ui = document.getElementById('ui')// Create our gameconst game = new ex.Engine({/*** Specify our custom canvas element so Excalibur doesn't make one*/canvasElementId: 'game',/*** Specify pointer scope to ensure that excalibur won't capture the mouse input* meant to be captured by HTML GUI*/pointerScope: ex.Input.PointerScope.Canvas,})/*** Our main menu scene, which will have HTML-based UI*/class MainMenu extends ex.Scene {onActivate() {// Add a CSS class to `ui` that helps indicate which scene is being displayedui.classList.add('MainMenu')// Create a <button /> elementconst btnStart = document.createElement('button')// Style it outside JavaScript for ease of usebtnStart.className = 'button button--start'// Handle the DOM click eventbtnStart.onclick = (e) => {e.preventDefault()// Transition the game to the new scenegame.goToScene('level')}// Append the <button /> to our `ui` containerui.appendChild(btnStart)}onDeactivate() {// Ensure we cleanup the DOM and remove any children when transitioning scenesui.classList.remove('MainMenu')ui.innerHTML = ''}}const level = new ex.Scene()const menu = new MainMenu()game.addScene('menu', menu)game.addScene('level', level)game.goToScene('menu')game.start()
Labels
Sometimes you need in game canvas based text, Labels are a special type of actor that help you quickly setup Text graphics and a Font.
You can pass in arguments to the Label constructor or simply set the properties you need after creating an instance of the label.
Since labels are also Actors, they need to be added to a Scene to be drawn and updated on-screen.
ts
// constructorconstlabel = newex .Label ({text : 'Foo',pos :ex .vec (50, 50)});label .font = newex .Font ({family : 'Arial',size : 10,unit :ex .FontUnit .Px , // pixels are the defaulttextAlign :ex .TextAlign .Center })label .color =ex .Color .White ;
ts
// constructorconstlabel = newex .Label ({text : 'Foo',pos :ex .vec (50, 50)});label .font = newex .Font ({family : 'Arial',size : 10,unit :ex .FontUnit .Px , // pixels are the defaulttextAlign :ex .TextAlign .Center })label .color =ex .Color .White ;
Adjusting Fonts
Excalibur has 2 types of fonts SpriteFont which sources text glyphs from a bitmap source. and Font which sources glyphs from a a web font.
You can use the Label.font property to adjust the Font.family, Font.size, Font.unit, Font.textAlign, and Font.baseAlign properties to customize how the label is drawn.
You can also use Label.getTextWidth to retrieve the measured width of the rendered text for helping in calculations.
Web Fonts
The HTML5 Canvas API draws text using CSS syntax. Because of this, Web Fonts are fully supported. To draw a web font, follow the same procedure you use for CSS. Then simply pass in the font string to the Label constructor or set Font.family.
index.html
html
<!DOCTYPE html><html><head><!-- Include the web font per usual --><script src="//google.com/fonts/foobar"></script></head><body><canvas id="game"></canvas><script src="game.js"></script></body></html>
html
<!DOCTYPE html><html><head><!-- Include the web font per usual --><script src="//google.com/fonts/foobar"></script></head><body><canvas id="game"></canvas><script src="game.js"></script></body></html>
game.js
ts
constlabel = newex .Label ();label .font .family = 'Foobar, Arial, Sans-Serif';label .font .size = 10;label .font .unit =ex .FontUnit .Em ;label .text = 'Hello World';game .add (label );game .start ();
ts
constlabel = newex .Label ();label .font .family = 'Foobar, Arial, Sans-Serif';label .font .size = 10;label .font .unit =ex .FontUnit .Em ;label .text = 'Hello World';game .add (label );game .start ();
Performance Implications
It is recommended to use a SpriteFont for labels as the raw Canvas
API for drawing text is slow (fillText
). Too many labels that
do not use sprite fonts will visibly affect the frame rate of your game.
Alternatively, you can always use HTML and CSS to draw UI elements, but currently Excalibur does not provide a way to easily interact with the DOM. Still, this will not affect canvas performance and is a way to lighten your game, if needed.