How can I add a character's HP or a visualization of the character's name, for example?

Good day to all. Tell me, with what can I add a character’s HP or a character’s name, do I need visualization? Tell me in what direction to think, read, watch? GUI?

Do you mean that you need to determine where exactly to render the player’s health bar so that the 3D world aligns with the 2D GUI overlay on the scene? If I understood you correctly, you’re on the right track. Here’s a function from my game that determines how to display the health bar directly above the unit.

getCoordinatesByMesh(mesh: AbstractMesh) {
    const canvasRect = this.player.scene.getEngine().getRenderingCanvasClientRect()
    if (!canvasRect) return

    // to 2D
    const projectedPosition = Vector3.Project(
      mesh.position.clone().addInPlace(new Vector3(0, mesh.scaling.y * 1.9, 0)),
      Matrix.Identity(),
      this.player.scene.getTransformMatrix(),
      this.player.scene.activeCamera!.viewport.toGlobal(
        this.player.scene.getEngine().getRenderWidth(),
        this.player.scene.getEngine().getRenderHeight(),
      ),
    )

    let x = (projectedPosition.x / canvasRect.width) * canvasRect.width
    let y = (projectedPosition.y / canvasRect.height) * canvasRect.height

    const cameraForward = this.player.scene.activeCamera!.getForwardRay().direction
    const toObject = mesh.position.subtract(this.player.scene.activeCamera!.position).normalize()
    const angle = Vector3.Dot(cameraForward, toObject)
    if (angle < 0) {
      x = -100 // offset on left screen part
      y = -100
    }

    return { x: x, y: y }
  }

The health bar itself is implemented in that way.

 createHealthBar(mesh: AbstractMesh) {
    const healthBarPanel = new GUI.StackPanel(`${mesh.id}_healthBarPanel`)
    healthBarPanel.widthInPixels = 55
    healthBarPanel.heightInPixels = 7
    healthBarPanel.isVertical = true
    healthBarPanel.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP
    healthBarPanel.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT
    healthBarPanel.background = 'rgba(0,0,0,0.5)'

    const healthBar = new GUI.Rectangle(`${mesh.id}_healthBar`)
    healthBar.widthInPixels = 55
    healthBar.heightInPixels = 7
    healthBar.color = 'rgba(101,9,9,0.8)'
    healthBar.background = 'rgba(158,13,13,0.8)'
    healthBar.thickness = 1
    healthBar.setPaddingInPixels(2, 2, 2, 2)
    healthBar.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT

    healthBarPanel.addControl(healthBar)
    this.advancedTexture.addControl(healthBarPanel)
    this.healthBars[mesh.id] = { panel: healthBarPanel, healthBar }
  }

And somewhere in render loop or object update function:

 updateHealthBars() {
    this.units.forEach((mesh) => {
      const healthBarData = this.healthBars[mesh.id]
      const coords = this.getCoordinatesByMesh(mesh)
      if (healthBarData && coords) {
        const { panel, healthBar } = healthBarData

        const isVisible = coords.y >= 0

        if (isVisible) {
          panel.isVisible = true
          healthBar.isVisible = true
          if (panel.widthInPixels && panel.heightInPixels) {
            panel.leftInPixels = coords.x - panel.widthInPixels / 2
            panel.topInPixels = coords.y - panel.heightInPixels / 2
          }
          const health = mesh.metadata?.health || 0
          const maxHealth = mesh.metadata?.maxHealth || 120
          healthBar.width = `${(health / maxHealth) * 100}%`
          if (health === 0) {
            healthBar.dispose()
            panel.dispose()
          }
        } else {
          panel.isVisible = false
          healthBar.isVisible = false
        }
      }
    })
  }

Sorry for not providing a Playground; I wanted to respond quickly and am showing you snippets of my in-game code instead.

3 Likes

thank you very much for the answer, especially for the quick one!)) yes, I need the player’s health bar to move with the player. I’ll study your code now, thank you

The result of my bar looks like this, but you can further customize the sizes and colors to make it different from mine:

1 Like

looks very cool

A simple way to project the position of a 2d button into 3d mesh

var viewVector3 = Vector3.Zero();
scene.onAfterRenderObservable.add(() => {
viewVector3 = Vector3.Project(
mesh.absolutePosition,
Matrix.Identity(),
scene.getTransformMatrix(),
Camera.viewport.toGlobal(window.innerWidth, window.innerHeight));
});

<Button style={{ transform: translate(-50%, -50%) translate3d(${viewVector3.x}px, ${viewVector3.y}px ,0) }} >

Also, in some cases, the resolution ratio changes strangely when resizing from desktop to mobile or tablet size, so window.innerWidth is used instead of scene.getEngine().getRenderWidth().

1 Like

Thank you