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.
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:
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()
.
Thank you