Appearance
Attach GUI to Sprites Guide
This guide explains how to use the attachToSprite option to attach GUI components directly to sprites in the game world.
Overview
By default, GUI components are displayed as fixed overlays on the screen. However, you can attach GUI components to sprites so they follow the sprite's position in the game world. This is useful for:
- Tooltips that appear when hovering over a sprite
- Name tags above characters
- Interactive UI elements attached to specific sprites
- Contextual menus that follow a sprite
- Status indicators that move with the sprite
Basic Usage
To attach a GUI component to sprites, set attachToSprite: true in your GUI configuration:
typescript
import { defineModule, RpgClient } from '@rpgjs/client'
import TooltipComponent from './components/tooltip.ce'
defineModule<RpgClient>({
gui: [
{
id: "my-tooltip",
component: TooltipComponent,
attachToSprite: true
}
]
})Creating an Attached GUI Component
Attached GUI components are Canvas Engine components (.ce files) that receive the sprite object as a prop. They are rendered within the sprite's container, so they automatically follow the sprite's position.
Example: Simple Tooltip Component
javascript
<!-- tooltip.ce -->
<DOMContainer>
<input type="text" placeholder="Enter text..." />
</DOMContainer>The component receives the sprite object as a prop automatically, allowing you to access sprite properties:
javascript
<!-- tooltip.ce -->
<DOMContainer>
<div style="background: rgba(0,0,0,0.8); color: white; padding: 8px; border-radius: 4px;">
<p>Player: {object.name()}</p>
<p>HP: {object.hp()} / {object.param.maxHp()}</p>
</div>
</DOMContainer>
<script>
const { object } = defineProps();
</script>For more information about DOMContainer, see the Canvas Engine documentation
Controlling Display from Server Side
Attached GUIs are controlled from the server side using the showAttachedGui() and hideAttachedGui() methods on the player object.
Show Attached GUI
Display the attached GUI for a player:
typescript
// Show the attached GUI for this player
player.showAttachedGui()
// Show the attached GUI for other players
player.showAttachedGui([otherPlayer1, otherPlayer2])
// Show for multiple players
player.showAttachedGui([player1, player2, player3])Hide Attached GUI
Hide the attached GUI for a player:
typescript
// Hide the attached GUI for this player
player.hideAttachedGui()
// Hide the attached GUI for other players
player.hideAttachedGui([otherPlayer1, otherPlayer2])Example: Show Tooltip on Hover
typescript
// server.ts
import { RpgPlayer, RpgMap } from '@rpgjs/server'
export default {
player: {
onJoinMap(player: RpgPlayer, map: RpgMap) {
// Show tooltip when player joins the map
player.gui('my-tooltip').open()
player.showAttachedGui()
},
onLeaveMap(player: RpgPlayer, map: RpgMap) {
// Hide tooltip when player leaves
player.hideAttachedGui()
}
}
}Component Props
Attached GUI components receive the following props:
object: The sprite object (RpgClientObject) - contains all sprite properties like position, health, name, etc.onFinish: Callback function when the GUI finishes its actiononInteraction: Callback function for GUI interactions
Example: Using Component Props
javascript
<!-- interactive-tooltip.ce -->
<DOMContainer>
<div style="background: rgba(0,0,0,0.9); color: white; padding: 10px; border-radius: 4px;">
<button click={() => onInteraction('action', { type: 'heal' })}>
Heal
</button>
<button click={() => onFinish({ result: 'closed' })}>
Close
</button>
</div>
</DOMContainer>
<script>
const { object, onFinish, onInteraction } = defineProps();
</script>Example: Using Reactive Signals with Form Elements
DOMContainer supports reactive two-way data binding for form elements using signals:
javascript
<!-- tooltip-with-input.ce -->
<script>
import { signal } from 'canvasengine'
const { object } = defineProps();
const inputValue = signal('')
</script>
<DOMContainer>
<div style="background: rgba(0,0,0,0.8); color: white; padding: 8px; border-radius: 4px;">
<p>Player: {object.name()}</p>
<input
type="text"
placeholder="Enter message..."
value={inputValue}
/>
<p>You typed: {inputValue()}</p>
</div>
</DOMContainer>Example: Using Event Handlers
DOMContainer supports all standard DOM events. Use the event name directly (without "on" prefix):
javascript
<!-- interactive-tooltip.ce -->
<script>
const { object, onFinish, onInteraction } = defineProps();
const handleMouseOver = (event) => {
console.log('Mouse over tooltip', event)
}
const handleMouseOut = (event) => {
console.log('Mouse out of tooltip', event)
}
</script>
<DOMContainer>
<div
style="background: rgba(0,0,0,0.8); color: white; padding: 8px; border-radius: 4px;"
mouseover={handleMouseOver}
mouseout={handleMouseOut}
>
<p>Player: {object.name()}</p>
<button click={() => onInteraction('action', { type: 'heal' })}>
Heal
</button>
</div>
</DOMContainer>Example: Using CSS Classes
You can apply CSS classes using different formats:
javascript
<!-- styled-tooltip.ce -->
<script>
import { signal } from 'canvasengine'
const { object } = defineProps();
const isActive = signal(false)
</script>
<DOMContainer>
<!-- String format -->
<div class="tooltip primary-theme">
<p>Player: {object.name()}</p>
</div>
<!-- Array format -->
<div class={['tooltip', 'primary-theme', 'active']}>
<p>Player: {object.name()}</p>
</div>
<!-- Object format (conditional classes) -->
<div class={{
'tooltip': true,
'active': isActive(),
'inactive': !isActive()
}}>
<p>Player: {object.name()}</p>
</div>
</DOMContainer>Example: Using Inline Styles
You can apply styles using string or object format:
javascript
<!-- styled-tooltip.ce -->
<DOMContainer>
<!-- String format -->
<div style="background-color: rgba(0,0,0,0.8); color: white; padding: 8px;">
<p>Player: {object.name()}</p>
</div>
<!-- Object format -->
<div style={{
backgroundColor: 'rgba(0,0,0,0.8)',
color: 'white',
padding: '8px',
borderRadius: '4px',
fontSize: '14px'
}}>
<p>Player: {object.name()}</p>
</div>
</DOMContainer>
<script>
const { object } = defineProps();
</script>Example: Using Forms with Automatic Data Collection
DOMContainer automatically collects form data when a form is submitted:
javascript
<!-- form-tooltip.ce -->
<script>
import { signal } from 'canvasengine'
const { object, onInteraction } = defineProps();
const handleSubmit = (event, formData) => {
console.log('Form submitted with data:', formData)
// Example formData: { message: 'Hello', priority: 'high' }
onInteraction('form-submit', formData)
}
</script>
<DOMContainer>
<div style="background: rgba(0,0,0,0.8); color: white; padding: 10px; border-radius: 4px;">
<p>Player: {object.name()}</p>
<form submit={handleSubmit}>
<input name="message" type="text" placeholder="Enter message..." />
<select name="priority">
<option value="low">Low</option>
<option value="high">High</option>
</select>
<button type="submit">Send</button>
</form>
</div>
</DOMContainer>Supported Events
DOMContainer supports all standard DOM events. Use the event name directly (without "on" prefix):
- Mouse events:
click,mouseover,mouseout,mouseenter,mouseleave,mousemove,mouseup,mousedown - Touch events:
touchstart,touchend,touchmove,touchcancel - Keyboard events:
keydown,keyup,keypress - Form events:
submit,reset,change,input,focus,blur - Drag events:
drag,dragend,dragenter,dragleave,dragover,drop,dragstart - Other events:
wheel,scroll,resize,contextmenu,select
For more details, see the Canvas Engine DOMContainer documentation.