UI Button Click Not Triggering State Change in 8thWall Studio ECS

Hi everyone,

I’m working on a small game in 8thWall Studio using the new ECS setup.
My goal is simple:

  • Show the Menu UI when the project starts.

  • When the user clicks the Start Button, switch to the Game UI and hide the menu.

  • Later I’ll also add a Restart button to go back to the menu.

I followed the 8thWall Studio UI guide and set up a uiManager.ts component.
Here’s a simplified version of my code:

import * as ecs from ‘@8thwall/ecs’

ecs.registerComponent({
name: ‘uiManager’,

schema: {
startBtn: ecs.eid,
menuUI: ecs.eid,
gameplayUI: ecs.eid,
},

stateMachine: ({world, eid, schemaAttribute}) => {
const {startBtn, menuUI, gameplayUI} = schemaAttribute.get(eid)

// ===== MENU STATE =====
ecs.defineState('menu')
  .initial()
  .onEnter(() => {
    console.log('📋 Menu state started')

    ecs.ui.set(world, menuUI, {visible: true})
    ecs.ui.set(world, gameplayUI, {visible: false})

    // Attach click listener to the button
    const btnEl = ecs.get(world, startBtn).el
    btnEl.addEventListener('click', () => {
      console.log('🟢 Start button clicked → switching to GAME')
      ecs.enterState(world, 'game')
    })
  })

// ===== GAME STATE =====
ecs.defineState('game')
  .onEnter(() => {
    console.log('🎮 Game state started')
    ecs.ui.set(world, menuUI, {visible: false})
    ecs.ui.set(world, gameplayUI, {visible: true})
  })
  .onEvent('RESTART_GAME', 'menu')

},
})

The problem:

  • The console log works fine → when I click the button, I see Start button clicked.

  • But the state does not actually switch — the UI stays the same.

  • I’ve tried different combinations (ecs.ui.onClick, ecs.dispatch, etc.) but keep running into errors like:

    • TypeError: ecs.ui.onClick is not a function

    • ecs.set is not a function


Question:

  • What is the correct way in 8thWall Studio ECS to listen for a UI button click and switch states?

  • Am I attaching the event in the right place (.onEnter() of the menu state), or should this be handled differently?

Any help or working example would be super appreciated :folded_hands:

This bit is incorrect. This is similar to A-Frame but it won’t work in Studio since the elements are not HTML. You instead need to store a reference to the entity in the component schema and then access it with schemaAttribute.

This YouTube video I recorded will shed some light on to do this as well:

Hi George!

Thanks a lot, it’s confusing haha I will check out the video! It must help!

Unfortunatly, it’s still not working. I don’t know what to do anymore. Would you like to check the full code and maybe some screenshots to see if the problem lays in the scene? I’m trying to solve this for days now. I start to become desparate. It’s just a buttonclick. It should be so easy right?

I hope someone can help!

Land your changes and share the project with the support workspace and I can take a look. :slight_smile:

thank you, I just shared it. It’s called Bedrijfshelden AR Challenge. The idea is that participants scan the QR code to get to the game. They first get an overlay page explaining the game and than press start. This is not working…

After that they should be able to find target images in the building and answer questions that will pop up after tracking. I never got to that point yet, but I guess if I know what I’m doing wrong with the start button, the rest will be a lot easier!

Thanks a lot for your help!

Hi!

I’ve reviewed the code and it looks like some fundamentals are missing. The state machine definitions aren’t quite correct and there’s a number of syntax errors. As a really small example I created this simple component that changes a UI element image to give you an idea how to add listeners in a StateMachine.

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

ecs.registerComponent({
  name: 'Flipbook',
  schema: {
    uiElement: ecs.eid,
  },
  schemaDefaults: {
  },
  data: {
    frame: ecs.i32,
  },
  stateMachine: ({world, eid, schemaAttribute, dataAttribute}) => {
    const frames = [
      '',
      'assets/1.png',
      'assets/2.png',
      'assets/3.png',
    ]

    dataAttribute.set(eid, {
      frame: 0,
    })

    ecs.defineState('default')
      .initial()
      .listen(schemaAttribute.get(eid).uiElement, ecs.input.UI_CLICK, () => {
        dataAttribute.mutate(eid, (c) => {
          c.frame = Math.min(c.frame + 1, frames.length - 1)
        })

        ecs.Ui.set(world, eid, {
          image: `${frames[dataAttribute.get(eid).frame]}`,
        })

        console.log(ecs.Ui.get(world, eid).image)
      })
  },
})

Yes!!! it works! Thank you lots!