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-joinandplayer-leavemark this.Participating: the player is in the running match and can control a disc.
player-enterandplayer-exitmark this.
Everyone who is connected but not participating is a spectator.
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.
| Event | When |
|---|---|
player-join | a player connected to the room |
player-leave | a player left |
player-enter | a player became a participant of the match |
player-exit | a 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:
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.
Four events let you follow and shape actions:
action-pressandaction-releasereport the key going down and up.before-actionruns before a native behavior and cancancel()it.actionreports what happened, including which discs a kick hit.
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.
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:
| Default | Who 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 |
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: permissionhides 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:
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:
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.