Skip to content
Docs
Games

Players, teams & actions

React to players joining and entering matches, give them discs, declare actions and permissions.

This page covers the people in your game: how they come and go, how they control their discs, what buttons they can press, and how a room can limit what each of them may do.

In the room, or in the match

A player can be in the room without playing. They might be watching, picking a team, or waiting for the next match. So disko keeps two separate ideas:

  • Connected: the player is in the room. player-join and player-leave mark this.

  • Participating: the player is in the running match and can control a disc. player-enter and player-exit mark this.

Everyone who is connected but not participating is a spectator.

A player joins the room as a spectator. player.play() makes them a participant in the match (player-enter); player.spectate() or the match ending makes them a spectator again (player-exit). Leaving the room fires player-leave.

player.play() asks to make a player a participant, and player.spectate() moves them back. game.players lists every connected player, spectators included. You always get the same Disko.Player object for the same player, and player.id stays the same for as long as they're connected.

EventWhen
player-joina player connected to the room
player-leavea player left
player-entera player became a participant of the match
player-exita player went back to spectating

Teams are yours

disko doesn't have teams built in, because every game wants something different. Keep teams in your own data, for example a Map from player.id to a team, as the template does.

Discs and movement

To let a player move, give them a disc. player.control(disc) connects the player's movement keys to that disc, and player.release() disconnects them. disko applies the movement itself, smoothly, on every screen. setAcceleration and setDamping tune how quick and how slippery it feels.

This assigns each new player a team and gives them a disc on their team's side when they enter the match:

src/players.ts

Actions

Movement isn't the only input. An action is anything else a player can do with a key, such as kicking.

You group actions into an action schema and give a schema to each player with player.useActionSchema. Different players can have different schemas: a goalkeeper might have a dive that others don't.

Schemas are created in top-level code. An action can have a native behavior, such as kick, that disko already knows how to perform. The player's browser can then show the kick instantly, before the room confirms it, so it feels responsive even on a slow connection.

src/players.ts

Four events let you follow and shape actions:

  • action-press and action-release report the key going down and up.

  • before-action runs before a native behavior and can cancel() it.

  • action reports what happened, including which discs a kick hit.

src/players.ts

Chat and room events

game.send posts a message in the room chat, as plain text or styled spans.

game.emit(name, payload) tells the room about something in your game, such as a goal. The room receives it as the game:<name> event and can react, for example by keeping statistics. See Events, chat & lifecycle.

Permissions

Many games have actions only some players should take: starting the match, picking teams, changing settings. But who should be allowed depends on the room, not the game. One room has admins, another lets everyone do everything.

So the work is split:

  • Your game declares what can be restricted, with a sensible default.

  • The room decides who holds each permission, using its own idea of roles.

The game declares a permission, for example pick-teams with the default restricted. The room may decide who holds it, for example its admins. When the game asks game.can(player, pickTeams), the room's decision wins; if the room decided nothing, the game's default applies.

Your game never needs to know what an "admin" is, and it works in a room that decides nothing at all.

Declaring permissions

Declare permissions in top-level code with game.permission(name, { default, label }), up to 64 per game. The default is who holds it when the room decides nothing:

DefaultWho holds it
"everyone"every player: a normal player ability
"restricted"no normal player; only the room grants it, for example to its admins
"owner"the player signed in with the room owner's account, if connected
src/permissions.ts

Using them in the Menu and HUD

The simplest use is to gate parts of the UI with requires:

  • On a request, ui.request({ …, requires }): players without the permission see the control disabled and can't send the request. The room checks this before the request ever reaches your game.

  • On a node, requires: permission hides the node and everything inside it from players without the permission. requires: { permission, denied: "disable" } shows it disabled instead.

ui.playerName({ player }) shows a player's name in the room's color with its badge, so players can see who's an admin:

src/permissions.ts

Checking in code

game.can(player, permission) answers immediately. Within one handler, every call sees the same answers, so your logic stays consistent. If the room hasn't answered yet, game.can says no, which is the safe choice.

A game can also change its own default for one player with permission.setDefault(player, allowed), for example to make the first player a captain. The room's decision still wins. The change applies after the current handler finishes, and permission-change reports every change to what game.can returns:

src/permissions.ts

Native actions such as kicks aren't permission-checked: to restrict them, give a player a different action schema. For the room's side of permissions, see Permissions & moderation.