Cloning a entity with added components/children?

Using the Studio World Effects template as a base (with the ability to tap and place all of the Cactus) - I’ve added a script/component to the model plus some children models. When tapping on the ground to clone the model, the script and children don’t seem to be placed with it - just the model.

Hi Mitch,

That’s not a built in capability of the sample project, however it’s a great suggestion.

You’ll have to modify the tap-place component to also include custom components you want to clone and manually construct the hierarchy of children by creating new entities and setting the parent to be the newly placed entity.

Hi Mitch!

It was great talking to you today. Here’s some follow ups from our Zoom meeting:

garden-manager.ts

import * as ecs from '@8thwall/ecs'
import {Plant} from './plant'

ecs.registerComponent({
  name: 'garden-manager',
  schema: {
  },
  schemaDefaults: {
  },
  data: {
  },
  add: (world, component) => {
    world.events.addListener(world.events.globalId, ecs.input.SCREEN_TOUCH_START, (e) => {
      const entity = world.createEntity()

      Plant.set(world, entity, {
        plantType: 0,
      })

      world.setPosition(entity, 0, 0, 0)
    })
  },
  tick: (world, component) => {
  },
  remove: (world, component) => {
  },
})

plant.ts

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

const Plant = ecs.registerComponent({
  name: 'plant',
  schema: {
    button: ecs.eid,
    plantType: ecs.i32,
  },
  schemaDefaults: {
  },
  data: {
  },
  stateMachine: ({world, eid, schemaAttribute}) => {
    const {button} = schemaAttribute.get(eid)

    ecs.defineState('stage_0')
      .initial()
      .onEnter(() => {
        console.log('stage_0')
        ecs.GltfModel.mutate(world, eid, (cursor) => {
          cursor.animationClip = 'idle'
        })

        const button = world.createEntity()
        
      })
      .onEvent(ecs.input.SCREEN_TOUCH_START, 'stage_1', {
        target: button,
      })

    ecs.defineState('stage_1')
      .onEnter(() => {
        console.log('stage_1')
        ecs.GltfModel.mutate(world, eid, (cursor) => {
          cursor.animationClip = 'walk'
        })
      })
  },
  add: (world, component) => {
    // Check plant type
    if (component.schema.plantType <= 0) {
      ecs.GltfModel.mutate(world, component.eid, (cursor) => {
        cursor.url = 'assets/doty.glb'
      })
    }
  },
  tick: (world, component) => {
  },
  remove: (world, component) => {
  },
})

export {Plant}

In the next post I’ll update it to create the buttons on add in the Plant component.

Here’s the updated code that creates the button at runtime and hooks it up.

plant.ts

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

let growButton = null

const Plant = ecs.registerComponent({
  name: 'plant',
  schema: {
    plantType: ecs.i32,
  },
  schemaDefaults: {
  },
  data: {
  },
  stateMachine: ({world, eid, schemaAttribute}) => {
    const {plantType} = schemaAttribute.get(eid)

    ecs.defineState('stage_0')
      .initial()
      .onEnter(() => {
        console.log('stage_0')

        // Check plant type and set the model.
        if (plantType <= 0) {
          ecs.GltfModel.set(world, eid, {
            url: 'assets/doty.glb',
            animationClip: 'idle',
          })

          // REMOVE THIS: I set it because doty.glb is very small at normal scale
          world.setScale(eid, 5, 5, 5)
        }

        growButton = world.createEntity()
        world.setPosition(growButton, 0, 1, 0)
        world.setParent(growButton, eid)
        ecs.Ui.set(world, growButton, {
          type: '3d',
          image: 'assets/3_Monk.png',
        })
      })
      .onEvent(ecs.input.SCREEN_TOUCH_START, 'stage_1', {
        target: growButton,
      })

    ecs.defineState('stage_1')
      .onEnter(() => {
        console.log('stage_1')
        ecs.GltfModel.mutate(world, eid, (cursor) => {
          cursor.animationClip = 'walk'
        })
      })
  },
  add: (world, component) => {
  },
  tick: (world, component) => {
  },
  remove: (world, component) => {
  },
})

export {Plant}

garden-manager.ts

import * as ecs from '@8thwall/ecs'
import {Plant} from './plant'

ecs.registerComponent({
  name: 'garden-manager',
  schema: {
  },
  schemaDefaults: {
  },
  data: {
  },
  add: (world, component) => {
    // Replace this with your tap to place functionality.
    const entity = world.createEntity()

    Plant.set(world, entity, {
      plantType: 0,
    })

    world.setPosition(entity, Math.random(), 0, Math.random())
  },
  tick: (world, component) => {
  },
  remove: (world, component) => {
  },
})

Thanks so much George! I’m having a bit of trouble getting the button sorted. I’ve used your code (but replacing the source of the image to another thats in my assets) and I can’t manage to see the button at all. I’ve tried scaling the button, setting the position to different values, even playing around with its rotation but can’t seem to find it. I can trigger the action by just tapping on the model however?

Hey Mitch!

I’d be happy to help you get it working. Can you remind me of the project name and URL / Make sure it’s shared with the support workspace?

Thanks George.

The workspace is madcarnisam and the project is GardensGlow (8th Wall). I’m trying to get that shared with support now.

.onEvent(ecs.input.SCREEN_TOUCH_START, 'stage_1', {
        target: growButton,
      })

When the target is the growButton, it seems to work for the most part by clicking on the model (though once I get to the second placing, it no longer works). If I were to replace the target with eid then it continues to work past the second placing (however, this is still just clicking on the model).

I’d also want to know how I would best offset the button appearing (once I’ve entered a new state, it appears after x seconds).

No problem.

I would try adding a secondary .onEvent where the event is “click” and see if that works. I’ll be able to try it myself once you get it shared.

Thanks! Have shared that with support now. It’s more so not being able to see the button graphic anywhere and I’m not sure what’s wrong haha!

Hi Mitch,

Can you make sure you’ve landed your changes? I’m not seeing the graphic in your assets folder or any UI elements in your scene.

Apologies - have landed now!

Hey Mitch!

I’ve fixed the issues in your project, and I’m landing the changes now. The “Grow” button is now functioning correctly—it shows up as expected, and clicking it successfully grows the plant.

Here’s a breakdown of the key issues I addressed:

The button entity’s rotation was set to an invalid value because a quaternion was being set as if it were an Euler rotation. I corrected this with the following code:

const rot = ecs.math.quat.yDegrees(180)

world.setQuaternion(growButton, rot.x, rot.y, rot.z, rot.w)

The event listener for touch events on the “Grow” button was operating in the global space. This happened because it was attached before the eid of the grow button was properly resolved.

I updated the logic to ensure the event listener works globally but filters for events targeting the “Grow” button specifically:

.onEvent(ecs.input.SCREEN_TOUCH_START, 'stage_2', {
// @ts-ignore
where: event => event.data.target === growButton,
})

That’s great - thanks so much! One last quick thing - how would I go about enabling/disabling this button after x amount of time once its entered a new state? Are there any built in ‘invoke’ type functions where you can play a function to play after x seconds? or would it be more of a setting a timer on the tick and once the timer has elapsed then do the function?

Or if you want to do it as a state:

:slight_smile: