Skip to main content

UI

Introduction

Niantic Studio offers a built-in UI system for creating interactive, user-friendly interfaces within your experience.

UI Layout & Components

Studio provides a variety of UI building blocks—from text and images to buttons and frames. Our intuitive (+) option in the Hierarchy panel makes it simple to add and customize UI elements. The UI Element Component includes detailed configuration settings inspired by CSS and Flexbox, letting you adjust everything from borders to background colors. Learn more about Flexbox styling here.

Adding UI Elements

You can introduce UI elements through several methods:

  • Visual Editor: Use the (+) option in the Hierarchy to add presets.
  • Component Method: Attach UI elements as Components on an entity.
  • Scripting: Programmatically add elements using the API.

add-ui-elements.png

3D UI

3D UI elements integrate seamlessly into your 3D scene, allowing for spatially interactive displays. You can adjust these via the inspector or transform gizmo.

ui-element-mode-3d

Interaction

You can add interaction to 3D UI elements using a custom component with an event listener.

Example

import * as ecs from '@8thwall/ecs'

ecs.registerComponent({
name: '3D Button',
schema: {
},
schemaDefaults: {
},
data: {
},
stateMachine: ({world, eid}) => {
ecs.defineState('default')
.initial()
.listen(eid, ecs.input.SCREEN_TOUCH_START, (e) => {
console.log('Interacted!')
})
},
})

Overlay UI

For screen-anchored UI, overlay elements offer absolute positioning. Preview these elements within the Simulator and use percentage-based sizing for responsive design, e.g., width: 100%.

ui-element-mode-2d

Interaction

You can add interaction to Overlay UI elements using a custom component with an event listener.

Example

import * as ecs from '@8thwall/ecs'

ecs.registerComponent({
name: 'Overlay Button',
schema: {
},
schemaDefaults: {
},
data: {
},
stateMachine: ({world, eid}) => {
ecs.defineState('default')
.initial()
.listen(eid, 'click', (e) => {
console.log('Interacted!')
})
},
})

Integration with State Machines

As seen in previous examples on this page, UI can be integrated with state machines to implement features such as basic menu navigation as pictured below.

These buttons built with UI components can be set up to dispatch an event when interacted with.

ecs.registerComponent({
name: 'start-button',
stateMachine: ({world, eid}) => {
ecs.defineState('default')
.initial()
.listen(eid, ecs.input.UI_CLICK, () => {
world.events.dispatch(eid, 'onStartButtonClick')
})
},
})

UI menus constructed in Studio can be tracked and organized by state.

enum GAME_STATES {
GAME_START = 'gameStateStart',
GAME_ACTIVE = 'gameStateActive',
GAME_OVER = 'gameStateOver',
}

enum GAME_MENUS {
START_MENU = 'startMenu',
GAME_OVER_MENU = 'gameOverMenu',
}

To facilitate switching between menus by hiding all other menus and displaying the one menu we want to be active, we can use a function to programmatically hide every non-active menu.

const updateUI = (world, schema, activeMenu = null) => {
Object.values(GAME_MENUS).forEach((menu) => {
if (schema[menu]) {
if (menu === activeMenu) {
ecs.Hidden.remove(world, schema[menu])
} else {
ecs.Hidden.set(world, schema[menu])
}
} else {
console.warn(`Menu ${menu} is not defined in the schema.`)
}
})
}

All the above can be used to manage a state machine for menu navigation that changes depending on button clicks and game states. In the following example, we start a game on clicking the start button, and trigger a game over menu after a set amount of time. From the game over menu, further buttons can restart the game or return to the start menu.

ecs.registerComponent({
name: 'game-controller',
schema: {
startMenu: ecs.eid,
gameOverMenu: ecs.eid,
gameActiveMenu: ecs.eid,
},
data: {
isGameActive: ecs.boolean,
},
stateMachine: ({world, eid, dataAttribute, schemaAttribute}) => {
const gameOver = ecs.defineTrigger()

ecs.defineState(GAME_STATES.GAME_START)
.initial()
.onEnter(() => {
console.log('Entered Game Start')
updateUI(world, schemaAttribute.cursor(eid), GAME_MENUS.START_MENU)
})
.onEvent('onStartButtonClick', GAME_STATES.GAME_ACTIVE, {target: world.events.globalId})

ecs.defineState(GAME_STATES.GAME_ACTIVE)
.onEnter(() => {
console.log('State Change: Entered Game Active')
updateUI(world, schemaAttribute.cursor(eid), 'gameActive')

setTimeout(() => {
console.log('Waiting done. Triggering game over.')
gameOver.trigger()
}, 1000)
})
.onTrigger(gameOver, GAME_STATES.GAME_OVER)

ecs.defineState(GAME_STATES.GAME_OVER)
.onEnter(() => {
dataAttribute.cursor(eid).isGameActive = false
updateUI(world, schemaAttribute.cursor(eid), GAME_MENUS.GAME_OVER_MENU)
})
.onEvent('onRestartButtonClick', GAME_STATES.GAME_ACTIVE, {target: world.events.globalId})
.onEvent('onReturnStartClick', GAME_STATES.GAME_START, {target: world.events.globalId})
},
})

Fonts

Default Fonts

Default fonts are available for use within the UI system.

Nunito

Nunito.png

Akidenz Grotesk

AkidenzGrotesk.png

Baskerville

Baskerville.png

Futura

Futura.png

Gotham

Gotham.png

Helvetica

Helvetica.png

Nanum Pen Script

NanumPenScript.png

Press Start 2P

PressStart2P.png

Times

Times.png

Inconsolata

Inconsolata.png

Custom Fonts

You can also upload custom fonts via TTF files to use in your UI Elements. Add custom font files to your assets to make them available.