Skip to main content

User Interfaces

Creating GUIs (HUDs, health bars, etc)

import { UIBehavior, syncedValue } from "@dreamlab/engine";

/*
UI System Overview:

The UI system in this project allows you to create and manage user interface elements
dynamically within the game using JSX within a UIBehavior.

- **Creating Elements:**
In a UIBehavior, `render()` returns JSX describing all DOM nodes, attributes, styles, and event handlers.

- **Appending to the UI Layer:**
The returned JSX is automatically appended to the associated UI layer. You trigger this by calling `this.rerender()`
in your lifecycle methods (e.g., `onInitialize()` or state changes).

- **Event Handling:**
Attach event listeners directly via JSX props (e.g., `onClick={...}`, `onMouseOver={...}`). This keeps markup and logic together.

- **Example Usage (Death Screen):**
Below is the same “Death Screen” implementation, now fully in JSX. It:
1. Holds a synced `score` value.
2. Renders a full-screen overlay with “Game Over”, the final score, and a “Respawn” button.
3. Cleans itself up by destroying the entity when “Respawn” is clicked.

- **Best Practices:**
- Always call `this.rerender()` after any state change (e.g., updating `score` or toggling visibility)
- File extension must be .tsx!
- Do not use fragments (e.g., <> </>)
*/

import { UIBehavior, syncedValue } from "@dreamlab/engine";

export default class DeathScreenUI extends UIBehavior {
@syncedValue()
score = 0;

override onInitialize(): void {
super.onInitialize();
if (!this.game.isClient()) return;
// Trigger to rerender()
this.rerender();
}

private respawnPlayer(): void {
this.entity.destroy();
}

override render() {
return (
<div>
<div
id="death-screen"
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
color: "white",
background: "rgba(0, 0, 0, 0.85)",
fontFamily: '"Inter", sans-serif',
}}
>
<h1
style={{
fontSize: "3rem",
fontWeight: "bold",
marginBottom: 0,
}}
>
Game Over
</h1>
<p
style={{
fontSize: "1.5rem",
marginBottom: "1rem",
}}
>
Final Score: {this.score.toLocaleString()}
</p>
<button
type="button"
onClick={() => this.respawnPlayer()}
style={{
padding: "1rem 2rem",
fontSize: "1.5rem",
cursor: "pointer",
border: "none",
borderRadius: "0.4rem",
color: "white",
backgroundColor: "#ff6600",
transition: "background-color 0.3s ease",
}}
onMouseOver={(e) => {
const btn = e.currentTarget as HTMLElement;
btn.style.backgroundColor = "#e65c00";
}}
onMouseOut={(e) => {
const btn = e.currentTarget as HTMLElement;
btn.style.backgroundColor = "#ff6600";
}}
>
Respawn
</button>
</div>
</div>
);
}
}