Button struggles

Hi all,
I am trying to set up two UI buttons to call an animation on a .glb and to go to a website. Iā€™ve looked through the simple button project and the tutorials but struggling to make progress. Not least because I canā€™t see the buttons to test them. It seems like 8th wall suggests ā€˜establishingā€™ the buttons outside of the a-scene. But I saw advice that says to include them after camera. Any thoughts?

head.html

<meta name="8thwall:renderer" content="aframe:1.3.0" />
<meta name="8thwall:package" content="@8thwall.xrextras" />
<meta name="8thwall:package" content="@8thwall.landing-page" />

<!-- Other external scripts and meta tags can also be added. -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<script src="//cdn.8thwall.com/web/aframe/aframe-extras-6.1.1.min.js"></script>

app.js

// Import necessary styles
import './main.css'

// AFRAME component to open a link, using 'lbutton' as the component name
AFRAME.registerComponent('lbutton', {
  init() {
    const btn = document.getElementById('link-button')
    if (btn) {
      btn.addEventListener('click', () => {
        window.open('https://www.bbc.co.uk/', '_blank')
      })
    } else {
      console.error('Link button not found')
    }
  },
})

// AFRAME component to play animation, renamed to 'abutton'
AFRAME.registerComponent('abutton', {
  init() {
    const btn = document.getElementById('animation-button')
    if (btn) {
      btn.addEventListener('click', () => {
        const thing = document.getElementById('windmill')
        if (thing) {
          // Assuming A-Frame and the animation-mixer component
          // Swap '*' with 'Cylinder.003Action' to target a specific animation
          thing.setAttribute('animation-mixer', {clip: 'Cylinder.003Action', loop: 'once'})
        } else {
          console.error('windmill model not found')
        }
      })
    } else {
      console.error('Animation button not found')
    }
  },
})

body.html

<!-- Copyright (c) 2022 8th Wall, Inc. -->
<!-- body.html is optional; elements will be added to your html body after app.js is loaded. -->
<button id="link-button">Open Link</button>
<button id="animation-button">Play Animation</button>
<a-scene
  xrextras-gesture-detector
  landing-page
  xrextras-loading
  xrextras-runtime-error
  renderer="colorManagement:true; webgl2: true;"
  xrweb="disableWorldTracking: true">
  <a-assets>
    <a-asset-item id="jelly-glb" src="assets/jellyfish-model.glb"></a-asset-item>
    <a-asset-item id="ooh-glb" src="assets/OA3DlogoAniV31.glb"></a-asset-item>
    <a-asset-item id="windmill" src="assets/windmill.glb"></a-asset-item>
    <img id="jelly-thumb" src="assets/video-thumbnail.jpg" />
    <video
      id="jelly-video"
      autoplay
      muted
      crossorigin="anonymous"
      loop="true"
      src="assets/jellyfish-video.mp4"></video>
  </a-assets>

  <a-camera position="0 4 10" raycaster="objects: .cantap" cursor="fuse: false; rayOrigin: mouse;">
  </a-camera>

  <a-light type="directional" intensity="0.5" position="1 1 1"></a-light>

  <a-light type="ambient" intensity="0.7"></a-light>

  <!-- Note: "name:" must be set to the name of the image target uploaded to the 8th Wall Console -->
  <xrextras-named-image-target name="video-target">
    <a-entity
      gltf-model="#ooh-glb"
      animation-mixer
      animation="Clip:Scene"
      rotation="90 0 0"
      scale="3 3 3"
      position="-0.35 -0.35 0"></a-entity>
    <a-plane
      position="0 0 0.5"
      rotation="0 0 90"
      scale="0.5 0.5 0.5"
      width="1"
      height="1"
      color="#eeeae2"
      material="opacity: 0.2; side: double;">
    </a-plane>
    <a-entity
      gltf-model="#windmill"
      rotation="90 0 0"
      scale="0.1 0.1 0.1"
      position="-1 1 0"></a-entity>
  </xrextras-named-image-target>

  <!-- Note: "name:" must be set to the name of the image target uploaded to the 8th Wall Console -->
  <xrextras-named-image-target name="model-target">
    <!-- Add a child entity that can be rotated independently of the image target. -->
    <a-entity
      gltf-model="#ooh-glb"
      animation-mixer
      animation="Clip:Scene"
      rotation="90 0 0"
      scale="3 3 3"
      position="-0.35 -0.35 0"></a-entity>
    <button id="startAnimationButton" position="0 0 0.5">Lift the stone</button>
    <a-plane
      position="0 0 0.5"
      rotation="0 0 90"
      scale="0.5 0.5 0.5"
      width="1"
      height="1"
      color="#eeeae2"
      material="opacity: 0.2; side: double;">
    </a-plane>
  </xrextras-named-image-target>
</a-scene>

main.css

#link-button {
    /* Example styling */
    padding: 10px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    font-size: 16px;
    cursor: pointer;
}

#animation-button {
    /* Example styling */
    padding: 10px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    font-size: 16px;
    cursor: pointer;
}

Hi @Richard_de_Mowbray,

Personally I always define my UI elements above/outside of the a-scene, so your approach there is just fine.

At first glance I think your AFrame components look great, however, and while you have defined them, youā€™ll need to actually add them to a component in your scene for them to be activated.

For example, you can add them to your a-scene:

<a-scene
  lbutton
  abutton
  xrextras-gesture-detector
  landing-page
  xrextras-loading
  xrextras-runtime-error
  renderer="colorManagement:true; webgl2: true;"
  xrweb="disableWorldTracking: true">

You might also need to adjust the z-index, and perhaps set position: absolute so these buttons appear on top of your scene and in the right position, example:

You can pull up the Safari or Chrome debugger to inspect elements on your page to understand further why they arenā€™t visible.

I wsuggest using this sample project as a reference - it contains a button and when you click it sets animation-mixer attributes:

Hope this helps!

1 Like

Really helpful, thanks Tony. So add the position info in the .css like this
`#link-button {
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
}

#animation-button {
position: absolute;
top: 100px;
left: 20px;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
}

` or in the body.html like in the animation-mixer session? I see my animation button now, but thereā€™s no functionality atm. Stillā€¦progress made! :muscle:

are you getting a console.log when the button is clicked? please verify this first.

Secondly, can you verify your animation name? or see if any animations play with *

thing.setAttribute('animation-mixer', {clip: '*', loop: 'repeat'})

1 Like

Thanks for your help Ian. It doesnā€™t make any difference when I call all animations (*). I didnā€™t see a console.log when the button is clicked. But when I added a console.log below in my code I did. codeā€¦AFRAME.registerComponent('abutton', { init() { const btn = document.getElementById('animation-button') if (btn) { btn.addEventListener('click', () => { console.log('Animation button clicked!') const thing = document.getElementById('windmill') if (thing) { // Assuming A-Frame and the animation-mixer component // Swap '*' with 'Cylinder.003Action' to target a specific animation thing.setAttribute('animation-mixer', {clip: '*', loop: 'once'}) } else { console.error('Owindmill model not found') } }) } else { console.error('Animation button not found') } }, })

After reviewing your code it looks like you are grabing a model in your <a-assets> with the id of windmill

This isnā€™t the entity you want to grab as this is what is being loaded into your scene. You need to grab the entity of the gltf model wihtin your actual scene.

So, where you load the windmill model like so

  <a-entity
      gltf-model="#windmill"
      rotation="90 0 0"
      scale="0.1 0.1 0.1"
      position="-1 1 0"></a-entity>

you need to give that entity an ID. Something like this

  <a-entity
      id="#windmill-model"
      gltf-model="#windmill"
      rotation="90 0 0"
      scale="0.1 0.1 0.1"
      position="-1 1 0"></a-entity>

Then get the entity 3D model by id

const thing = document.getElementById('windmill-model')

And this should work correctly. So you were essentially just grabbing the model and not the one that you actually were placing into your A-frame scene.

You can see this on line 53 in the sample proeject here:

Thanks so much Ian, really appreciate your and the teams patience with my ā€˜greennessā€™. I just had to add a # to the id and it worked perfectly.

const thing = document.getElementById('#windmill-model')
1 Like

Hi, Iā€™ve been trying to get my UI buttons to appear once the image target is located. Are there any sample projects for that? I currently have this code in app.js

window.addEventListener('load', () => {
  const videoTarget = document.querySelector('xrextras-named-image-target[name="video-target"]')
  const animationButton = document.getElementById('animation-button')
  const linkButton = document.getElementById('link-button')

  // Listen for the image target being found
  videoTarget.addEventListener('xrimagefound', () => {
    console.log('Video target found')
    animationButton.classList.add('block')
    linkButton.classList.add('block')
    console.log('Video target found - buttons shown')
  })

  // Optional: Listen for the image target being lost
  videoTarget.addEventListener('xrimagelost', () => {
    animationButton.classList.add('hidden')
    linkButton.classList.add('hidden')
    console.log('Video target lost - buttons hidden')
  })
})

Are your cosnole logs registering? You want to write your logic into a custom component then add it to your image target.

Something like this

const customImageTargetComponent = () => ({
  schema: {
    name: {type: 'string'},
  },
  init() {
    const {object3D} = this.el
    const {name} = this.data
    object3D.visible = false

    const imageFound = ({detail}) => {
      if (name !== detail.name) {
        return
      }

      object3D.position.copy(detail.position)
      object3D.quaternion.copy(detail.rotation)
      object3D.scale.set(detail.scale, detail.scale, detail.scale)

      object3D.visible = true
    }

    const imageUpdate = ({detail}) => {
      if (name !== detail.name) {
        return
      }
      object3D.position.copy(detail.position)
      object3D.quaternion.copy(detail.rotation)
      object3D.scale.set(detail.scale, detail.scale, detail.scale)
    }

    const imageLost = (e) => {
      object3D.visible = false
    }

    this.el.sceneEl.addEventListener('xrimagefound', imageFound)
    this.el.sceneEl.addEventListener('xrimageupdated', imageUpdate)
    this.el.sceneEl.addEventListener('xrimagelost', imageLost)
  },
})

export {customImageTargetComponent}

This tutorial may be helpful which explains the image target events

You want to essentially show the button UI on xrimagefound and hide it on xrimagelost

1 Like

you can reference this sample project (for the custom image target component part)

Then register the component in app.js (line 8)

Then finally add it into your a-scene (line 32)

1 Like