Avatars
Every player on Board has an avatar: a small PNG image chosen in the system profile, shown alongside the player’s name in lobby screens, scoreboards, and pause overlays. Avatars are owned by the OS, not your app. When a player picks their avatar in Board’s system settings, every game sees the same image, so you never store or upload avatar art yourself. You ask the SDK for the image that belongs to a player and render it.
This guide covers how each SDK turns a player into a displayable avatar image. The concept is identical across the three SDKs; the surface differs because each one returns its host engine’s native image type and follows its native async convention.
The avatar model
Avatars are addressed by an avatar id that travels with each player. A player object carries the id of the avatar its owner selected, and that id is what the SDK resolves into an image. The default avatar is id 0, available even before any player is added (use it as a placeholder, or for an empty seat).
Image loading is asynchronous because the bytes come from the OS over an IPC bridge, and every SDK caches the decoded result per id. Once an avatar has been fetched, repeated requests for the same id are cheap, so you can call into the avatar system freely from UI code without worrying about redundant work. You do not need to cache the images yourself.
| Concept | Meaning |
|---|---|
| avatar id | Identifies which system avatar a player selected. Carried on the player object. |
| default avatar | Avatar id 0, always available. Use it as a placeholder or for an empty seat. |
| caching | The SDK caches each decoded avatar by id; repeat requests are cheap. |
Avatar ids come from a player. To get the players in the current session, see Player Management. The same ids also appear on the players stored inside a saved game, so you can render avatars on a save-slot screen too: see Save Games.
Per-SDK conventions
The model above is identical across SDKs. Three representation details differ, because each SDK returns the image type and async type its host engine expects:
| Convention | Unity | Godot | Web |
|---|---|---|---|
| Image type returned | Texture2D |
ImageTexture |
PNG data URI (string) |
| Async type | Task<Texture2D> |
await an ImageTexture |
Promise<string> |
| How you resolve a player | Read BoardPlayer.avatar (lazy property) |
await_load_avatar(int(player.avatar_id)) |
avatar.forPlayer(player) |
avatarId field type |
string |
String (pass int(...)) |
string (coerced for you by forPlayer) |
One structural difference is worth calling out before the examples. Unity exposes avatars through the player object: BoardPlayer lazy-loads its own avatar texture and raises an event when it is ready, so there is no public “load avatar id N” call. Godot and Web instead expose a dedicated avatar module you call with an id (Godot) or with a player (Web). The sections below show each SDK’s idiomatic path rather than transliterating one shape onto the others.
Loading a player’s avatar
The most common task is rendering the avatar for a known player. In Unity you read the player’s lazy avatar property and subscribe to its avatarLoaded event, because the texture is null until the first asynchronous load finishes. In Godot you pass the player’s id (an int) to the avatar module and await the texture. In Web you hand the player straight to forPlayer, which coerces the id and resolves a data URI you can assign to an <img>.
using Board.Core;
using Board.Session;
using UnityEngine;
using UnityEngine.UI;
public class PlayerBadge : MonoBehaviour
{
[SerializeField] private RawImage avatarImage;
public void Show(BoardSessionPlayer player)
{
// BoardPlayer.avatar lazy-loads on first access; it's null until ready.
if (player.avatar != null)
{
avatarImage.texture = player.avatar;
}
else
{
// Render when the asynchronous load completes.
// The handler receives the BoardPlayer; read its now-loaded texture.
player.avatarLoaded += p => avatarImage.texture = p.avatar;
// Touching the property kicks off the load.
_ = player.avatar;
}
}
}
import { Board, type BoardPlayer } from "@board.fun/web-sdk";
async function showPlayer(player: BoardPlayer) {
// forPlayer coerces the player's string avatarId to a number for you.
const dataUri = await Board.avatar.forPlayer(player);
const img = document.querySelector<HTMLImageElement>("#avatar")!;
img.src = dataUri; // a data:image/png;base64,... URI
}
@onready var avatar_rect: TextureRect = $TextureRect
func show_player(player: BoardPlayer) -> void:
# avatar_id is a String on BoardPlayer; the loader takes an int.
var tex := await Board.avatar.await_load_avatar(int(player.avatar_id))
if tex != null:
avatar_rect.texture = tex
Rendering every player
To build a lobby or scoreboard, walk the current session players and render each one’s avatar. Unity reads each player’s lazy avatar property; Godot and Web load by id or by player.
using Board.Session;
foreach (var player in BoardSession.players)
{
var badge = SpawnBadge(player.name);
// Reuse the per-player avatar handler from above.
badge.GetComponent<PlayerBadge>().Show(player);
}
import { Board } from "@board.fun/web-sdk";
for (const player of Board.session.getPlayers()) {
const dataUri = await Board.avatar.forPlayer(player);
addBadge(player.name, dataUri);
}
for player in Board.session.get_players(): # Array[BoardPlayer]
var tex := await Board.avatar.await_load_avatar(int(player.avatar_id))
add_badge(player.display_name, tex)
The default avatar
Avatar id 0 is the default. Every SDK exposes a direct way to fetch it, so you can show a placeholder for an empty seat or before any player has joined.
using Board.Core;
using UnityEngine;
// Static helper on BoardPlayer; loads avatar id 0.
Texture2D defaultAvatar = await BoardPlayer.GetDefaultAvatar();
emptySeatImage.texture = defaultAvatar;
import { Board } from "@board.fun/web-sdk";
// Loads the default avatar (id 0) and resolves a data URI.
const dataUri = await Board.avatar.getDefault();
emptySeatImg.src = dataUri;
# Convenience for avatar id 0.
var tex := await Board.avatar.await_default_avatar()
if tex != null:
empty_seat_rect.texture = tex
Loading by avatar id
When you already hold a raw avatar id (for example, from a saved game’s player records), Godot and Web let you load it directly. Godot’s loader takes an int; Web’s loadPNG takes a number.
Unity has no public load-by-id call: avatars are reached through the player object’s avatar property and the static GetDefaultAvatar() for id 0. If you need an avatar for a player you read from a saved game, render it through that player’s lazy avatar property exactly as in the loading section above.
import { Board } from "@board.fun/web-sdk";
// avatarId is a number; resolves a PNG data URI.
const dataUri = await Board.avatar.loadPNG(avatarId);
iconImg.src = dataUri;
# avatarId is an int here (e.g. from your own records).
var tex := await Board.avatar.await_load_avatar(avatar_id)
if tex != null:
icon_rect.texture = tex
Reacting to avatar changes
If a player switches their avatar while your game is running, you should re-render. The SDKs signal this differently.
In Unity the player object raises an avatarLoaded event when its texture finishes loading, and BoardSession.playersChanged fires when the roster itself changes; subscribe to both and re-read the player’s avatar. In Godot, Board.session.players_changed fires on any roster change; after it, re-walk get_players() and reload. The Web SDK has no players-changed event: re-read Board.session.getPlayers() after a player-selector call resolves, then reload avatars for the new roster. (Calling the avatar system again is cheap, since results are cached per id; clear the cache if you specifically want a fresh fetch.)
using Board.Core;
using Board.Session;
void OnEnable()
{
BoardSession.playersChanged += RefreshAvatars;
}
void OnDisable()
{
BoardSession.playersChanged -= RefreshAvatars;
}
void RefreshAvatars()
{
foreach (var player in BoardSession.players)
{
// The avatarLoaded event fires once the new texture is ready.
// The handler receives the BoardPlayer; read its now-loaded texture.
player.avatarLoaded += p => UpdateBadge(p, p.avatar);
if (player.avatar != null)
{
UpdateBadge(player, player.avatar);
}
}
}
import { Board } from "@board.fun/web-sdk";
// No players-changed event: re-read after a selector call resolves.
async function refreshAfterSelector() {
const added = await Board.session.presentAddPlayer();
if (!added) return;
// Drop cached avatars so a switched avatar re-fetches.
Board.avatar.clearCache();
for (const player of Board.session.getPlayers()) {
const dataUri = await Board.avatar.forPlayer(player);
updateBadge(player, dataUri);
}
}
func _ready() -> void:
if not Board.is_on_device:
return
Board.session.players_changed.connect(_refresh_avatars)
func _refresh_avatars() -> void:
# Drop cached textures so switched avatars re-fetch.
Board.avatar.clear_cache()
for player in Board.session.get_players():
var tex := await Board.avatar.await_load_avatar(int(player.avatar_id))
update_badge(player, tex)
See Also
- Player Management — how players are added to and removed from a session, and where
avatarIdcomes from - Profile Switcher — invoking the system profile-switcher overlay
- Save Games — rendering avatars for the players stored in a saved game
- Per-SDK API references: Unity, Godot, Web
</content> </invoke>