Add component to entities created at runtime

Hi all,

I’m new to 8th Wall Studio, and I’m trying to create a simple spawner. However, I’m having trouble attaching components to new entities created at runtime. I’ve looked through the documentation and forums but couldn’t find anything related to my issue.

I have a custom component that moves an entity. When I attach it to an entity in the Studio, it works as expected.

The issue arises with a spawner that creates a new entity at runtime.
The entity is created, and I can see it in the hierarchy, but none of the components seem to be attached.
To create and configure the new entity, I’m using the following code:

      const newEid = world.createEntity()
      world.setParent(newEid, data.parentEntity) //data.parentEntity exists from the schema
      world.setPosition(newEid, 1.45, 0.68, 15)
      world.setScale(newEid, 1, 1, 1)
      ecs.BoxGeometry.set(world, newEid, {width: 1, height: 1, depth: 1})
      ecs.Material.set(world, newEid, {
        r: 0,
        g: 0,
        b: 255,
      })
      soldier.set(world, newEid) //custom component, it should move the box

      console.log(newEid)  // some id
      console.log(world.getParent(newEid))  // 0n
      console.log(Array.from(world.getChildren(data.parentEntity)))  // newEid not here

The new entity doesn’t seem to have any of the components. When I inspect it in the Studio, its transform shows default values, and it doesn’t appear to be attached to the correct parent.

Do you have any suggestion, or is there something I misunderstood or missed?

Thanks a lot

Full code below

Spawner

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

import {soldier} from './soldier'

const deployButton = ecs.registerComponent({
  name: 'deploy-button',
  schema: {
    entityToSpawn: ecs.eid,
    parentEntity: ecs.eid,
  },
  stateMachine: ({world, eid, schemaAttribute, dataAttribute}) => {
    const spawnState = ecs.defineState('spawn').onEnter(() => {
      const data = schemaAttribute.acquire(eid)
      const newEid = world.createEntity()

      world.setParent(newEid, data.parentEntity)
      world.setPosition(newEid, 1.45, 0.68, 15)
      world.setScale(newEid, 1, 1, 1)
      ecs.BoxGeometry.set(world, newEid, {width: 1, height: 1, depth: 1})
      ecs.Material.set(world, newEid, {
        r: 0,
        g: 0,
        b: 255,
      })
      soldier.set(world, newEid)

      console.log(newEid)  // some id
      console.log(world.getParent(newEid))  // 0n
      console.log(Array.from(world.getChildren(data.parentEntity)))  // newEid not here
    }).onExit(() => {
      console.log('exit spawn')
    })

    const toSpawn = ecs.defineTrigger()

    const defaultState = ecs.defineState('default').initial().onEnter(() => {
      console.log('entered default')
    }).onTick(() => {
      if (world.input.getAction('deploy')) {
        toSpawn.trigger()
      }
    })
      .onTrigger(toSpawn, spawnState)
      .onExit(() => {
        console.log('exit default')
      })
  },
})

export {deployButton}

Custom component

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

const soldier = ecs.registerComponent({
  name: 'soldier',
  schema: {
    walkingSpeed: ecs.f32,
  },
  schemaDefaults: {
    walkingSpeed: 0,
  },
  data: {
    walkingSpeed: ecs.f32,
  },
  stateMachine: ({world, eid, dataAttribute}) => {
    const moveSoldier = () => {
      const data = dataAttribute.acquire(eid)
      data.walkingSpeed -= 0.4
      ecs.physics.applyForce(world, eid, 0, 0, data.walkingSpeed)
      dataAttribute.commit(eid)
    }

    const walkingState = ecs.defineState('walking').onEnter(() => {
      console.log('i\'m walking here')
    })
      .onTick(() => {
        moveSoldier()
      })
      .onExit(() => {
        console.log('Not walking naymore')
      })

    const startWalking = ecs.defineTrigger()

    ecs.defineState('default').initial()
      .onTrigger(startWalking, walkingState)
      .onTick(() => {
        startWalking.trigger()
      })
  },
})

export {soldier}

That looks correct! However, I’d suggest adding an empty schema definition at the end of your set function. The issue might be that it’s not setting properly because the schema definition is undefined.

For example:

soldier.set(world, newEid, {})

Morning

thanks a lot for your answer, I tried to set the schema as empty, but still not luck :frowning:

I updated the code to try catch and everything seems to be working fine, no properties are caught

    const spawnState = ecs.defineState('spawn').onEnter(() => {
      const data = schemaAttribute.acquire(eid)
      const newEid = world.createEntity()

      if (data.parentEntity) {  // Check if `parentEntity` is defined
        world.setParent(newEid, data.parentEntity)
        console.log('Set parent entity successfully.')
      } else {
        console.warn('Parent entity is not defined or is invalid.')
      }

      try {
        world.setPosition(newEid, 1.45, 0.68, 15)
        console.log('Position set for', newEid)
      } catch (error) {
        console.error('Failed to set position:', error)
      }

      try {
        world.setScale(newEid, 1, 1, 1)
        console.log('set scale for', newEid)
      } catch (error) {
        console.error('Failed to set scale', error)
      }

      try {
        ecs.BoxGeometry.set(world, newEid, {width: 1, height: 1, depth: 1})
        console.log('set box geometry for', newEid)
      } catch (error) {
        console.error('Failed to set geometry', error)
      }

      try {
        ecs.Material.set(world, newEid, {
          r: 0,
          g: 0,
          b: 255,
        })

        console.log('set material for', newEid)
      } catch (error) {
        console.error('Failed to set material', error)
      }

      try {
        soldier.set(world, newEid, {})
        console.log('set soldier for', newEid)
      } catch (error) {
        console.error('Failed to set soldier', error)
      }

      console.log(newEid)  // some id
      console.log(world.getParent(newEid))  // 0n
      console.log(Array.from(world.getChildren(data.parentEntity)))  // newEid not here
    })

In the viewport, I can see the entity, but it doesn’t have any component, just the default transform, I attached a screengrab to explain what is happening

I also tried to clone the project but it has the same result (likely)

:confounded: :confounded:

@christoph_nia Any ideas?

Hey

I did some testing, I rewrote the spawner to not use the stateMachine and it seems to behave as expected.

The code below spawns a blue cube, it also attach the script (this example fails to move as I didn’t attach the collider, but it is expected)

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

function spawn(world) {
  const newEid = world.createEntity()
  world.setPosition(newEid, 1.45, 0.68, 15)
  ecs.BoxGeometry.set(world, newEid, {width: 1, height: 1, depth: 1})
  try {
    ecs.Material.set(world, newEid, {
      r: 0,
      g: 0,
      b: 255,
    })
    soldier.set(world, newEid, {})

    console.log('set material for', newEid)
  } catch (error) {
    console.error('Failed to set material', error)
  }
}

ecs.registerComponent({
  name: 'deployer',
  schema: {
    parentEntity: ecs.eid,
  },
  schemaDefaults: {
    // Add defaults for the schema fields.
  },
  data: {
    // Add data that cannot be configured outside of the component.
  },
  add: (world, component) => {
    // Runs when the component is added to the world.
  },
  tick: (world, component) => {
    // Runs every frame.
    if (world.input.getAction('deploy')) {
      spawn(world)
    }
  },
  remove: (world, component) => {
    // Runs when the component is removed from the world.
  },
})

Maybe I am using the stateMachine in the wrong way?
Could it be some async/lifetime cycle stuff?
(it is always some async js stuff :sweat_smile: :sweat_smile: :sweat_smile: )

Thanks

In the onEnter call I’m seeing const data = schemaAttribute.acquire(eid) but no matching call to commit. That would definitely result in unexpected behavior.

Thanks a lot
that was it :bomb:
I didn’t notice that acquire had different behavior than get.

I replaced schemaAttribute.acquire to schemaAttribute.get and it seems to be working fine (I also added the Collider :sweat_smile: )

Thanks a lot for your help

1 Like

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