Error with changing state on a component on multiple entities

I am running into what I think is a bug right now. I am adding in the UI for starting the game and mobile phone controls. I have a custom component on multiple entities that I am using to move the terrain and Items on Tick. When I start the game I send an event out that should change the state of the component on both entities so that movement starts but it is currently only working on one at a time. Here are recordings from my phone, Desktop and the simulator. None of them have both working. Here is also the code I am using. Essentially on click i am using this:

world.events.addListener(schema.startButton,
          ecs.input.SCREEN_TOUCH_START, () => {
            console.log('click')
            world.events.dispatch(eid, 'Start Game')
          })

and then this is the Movement component that is on the object container and the terrain container. both of those are parents to the entities I want to move.

// This is a component file. You can use this file to define a custom component for your project.
// This component will appear as a custom component in the editor.

import * as ecs from '@8thwall/ecs'  // This is how you access the ecs library.

ecs.registerComponent({
  name: 'MoveEntities',
  schema: {
    // Add data that can be configured on the component.
    objectMoveSpeed: ecs.f32,  // this is the move speed of the player going forward
    endingSpot: ecs.f32,  // this is the z position when the object gets to the end move spot
  },
  schemaDefaults: {
    // Add defaults for the schema fields.
    objectMoveSpeed: 10,
    endingSpot: -80,
  },
  data: {
    // Add data that cannot be configured outside of the component.
    gameActive: ecs.boolean,
    actualForwardMoveSpeed: ecs.f32,  // So users dont have to input a decimal for move speed
  },
  stateMachine: ({world, eid, schemaAttribute, dataAttribute}) => {
    const schema = schemaAttribute.cursor(eid)
    const data = dataAttribute.cursor(eid)
    ecs.defineState('preGame').initial()
      .onEvent('Start Game', 'inGame', {target: world.events.globalId})
    ecs.defineState('inGame')
      .onEvent('Game Over', 'gameOver', {target: world.events.globalId})
      .onEnter(() => {
        data.gameActive = true
      })
      .onExit(() => {
      })
    ecs.defineState('gameOver')
      .onEvent('Restart Game', 'preGame', {target: world.events.globalId})
      .onEnter(() => {
        data.gameActive = false
      })
  },
  add: (world, component) => {
    // Runs when the component is added to the world.
    const {eid, dataAttribute, schemaAttribute} = component
    component.dataAttribute.cursor(component.eid).gameActive = false
    dataAttribute.cursor(eid).actualForwardMoveSpeed =
      schemaAttribute.cursor(eid).objectMoveSpeed / 1000
  },
  tick: (world, component) => {
    // Runs every frame.
    const {eid, dataAttribute, schemaAttribute} = component
    const data = dataAttribute.cursor(eid)
    const schema = schemaAttribute.cursor(eid)
    const {delta} = world.time
    if (data.gameActive) {
      [...world.getChildren(eid)].forEach((element) => {
        const objectPosition = ecs.Position.get(world, element)
        const newX = objectPosition.x
        const newY = objectPosition.y
        let newZ = objectPosition.z
        newZ = objectPosition.z - (data.actualForwardMoveSpeed * delta)
        world.setPosition(element, newX, newY, newZ)
        if (newZ < schema.endingSpot) {
          world.events.dispatch(element, 'end')
        }
      })
    }
  },
})

Here are also video of the code running on my mobile device, desktop, and in the simulator:



I duplicated the MoveEntities Component and named it MoveEntitiesToo and put that on one of the containers that I have that moves all of its children and now everything is working as intended. It seems to only break when I use the same component on multiple entities.

What is eid in this case? Have you tried dispatching and listening on the global eid (world.events.globalId)?

The eid in this case is just the UI container that this script is sitting on.
I haven’t dispatched it globally but I am listening globally with

.onEvent('Start Game', 'inGame', {target: world.events.globalId})

just tested it out and it didn’t seem to change anything. I am still getting the same problem.

I’m not able to reproduce this specific issue with a simple test (an animation component attached to two cubes, using the global eid to dispatch/listen on):

ecs.registerComponent({
  name: 'start-button',
  add: (world, component) => {
    world.events.addListener(world.events.globalId,
      ecs.input.SCREEN_TOUCH_START, () => {
        world.events.dispatch(world.events.globalId, 'start')
      })
  },
})
ecs.registerComponent({
  name: 'animation',
  stateMachine: ({world, eid}) => {
    ecs.defineState('start-state')
      .initial()
      .onEvent('start', 'next-state', {target: world.events.globalId})

    ecs.defineState('next-state')
      .onEnter(() => {
        ecs.PositionAnimation.set(world, eid, {autoFrom: true, to: {x: 0, y: 0, z: 0}})
      })
  },
})

If you are able to share the project with the support workspace we can help debug in more detail.

In the first version of the code, at the beginning of stateMachine, const data = dataAttribute.cursor(eid) is declared and then used later in onEnter which is not allowed.

This is one of the things called out here: Common issues and best practices | 8th Wall

Here’s a version with some fixes applied:

ecs.registerComponent({
  name: 'MoveEntities',
  schema: {
    // Add data that can be configured on the component.
    objectMoveSpeed: ecs.f32,  // this is the move speed of the player going forward
    endingSpot: ecs.f32,  // this is the z position when the object gets to the end move spot
  },
  schemaDefaults: {
    // Add defaults for the schema fields.
    objectMoveSpeed: 10,
    endingSpot: -80,
  },
  data: {
    // Add data that cannot be configured outside of the component.
    gameActive: ecs.boolean,
    actualForwardMoveSpeed: ecs.f32,  // So users dont have to input a decimal for move speed
  },
  stateMachine: ({world, eid, dataAttribute}) => {
    ecs.defineState('preGame').initial()
      .onEvent('Start Game', 'inGame', {target: world.events.globalId})
    ecs.defineState('inGame')
      .onEvent('Game Over', 'gameOver', {target: world.events.globalId})
      .onEnter(() => {
        dataAttribute.cursor(eid).gameActive = true
      })
      .onExit(() => {
      })
    ecs.defineState('gameOver')
      .onEvent('Restart Game', 'preGame', {target: world.events.globalId})
      .onEnter(() => {
        dataAttribute.cursor(eid).gameActive = false
      })
  },
  add: (world, component) => {
    // Runs when the component is added to the world.
    const {data, schema} = component
    data.gameActive = false
    data.actualForwardMoveSpeed = schema.objectMoveSpeed / 1000
  },
  tick: (world, component) => {
    // Runs every frame.
    const {eid, data, schema} = component
    const {delta} = world.time
    if (data.gameActive) {
      [...world.getChildren(eid)].forEach((element) => {
        const objectPosition = ecs.Position.get(world, element)
        const newX = objectPosition.x
        const newY = objectPosition.y
        let newZ = objectPosition.z
        newZ = objectPosition.z - (data.actualForwardMoveSpeed * delta)
        world.setPosition(element, newX, newY, newZ)
        if (newZ < schema.endingSpot) {
          world.events.dispatch(element, 'end')
        }
      })
    }
  },
})
2 Likes

That worked, Thank you! That is interesting that causes so many problems.

This topic was automatically closed 4 days after the last reply. New replies are no longer allowed.