Skip to main content

How to: Make a Team Selector

A common feature in team-based games is a team selector interface which allows players to choose which team they want to be on. There are many ways to implement a team selector, but here is the simplest example which shows the basic idea from which you can build on.

In this example, there are two teams. Players are assigned to one as soon as they join, but they are immediately presented with a team selector interface which allows them to switch teams if they want to. On the team selector interface, there is a "Start Game" button that anyone can click to start the game.

This example is split across three files: main.easel, hero.easel, and teamSelector.easel.

main.easel

pub const Team1 = Spawn
pub const Team2 = Spawn
pub const Teams = [Team1, Team2] // put the teams in an array for easy access

pub game fn World.Main(maxHumanPlayers=10) {
SpawnEachPlayer owner {
Team = Teams.MinBy(|team| CountPlayers(team=))
TeamSelectorSystem

await GameCommenced

Subspawn hero {
Hero
}
}
}
  • The two teams, Team1 and Team2 are declared up front and assigned to global constants so they are easy to access.
  • A Teams array is declared to hold the teams. This lets the team selector interface easily loop through the teams to display them.
  • The await GameCommenced line is used to delay the spawning of the hero until after the game has started. This is triggered later in teamSelector.easel.

hero.easel

In hero.easel:

pub tangible category Category:Hero

pub fn hero.Hero([owner]) {
use body=hero, radius=1, shape=Equilateral(numPoints=3)
Body(pos=20*RandomVector)
PolygonSprite(ownerColor=true)
PolygonCollider(category=Category:Hero)
}
  • The [owner] parameter is taken from context. This tells the Hero which player owns it.
  • The ownerColor=true parameter is used to tint the hero in the player's color.
  • This is a very simple example of a hero that the player can control. It will certainly be much more complicated in your game!

teamSelector.easel

pub signal World.TeamsChanged
pub fn owner.TeamSelectorSystem() {
Subspawn {
OverlayContent {
H1 { "Choose Your Team" }
HStack {
with TeamsChanged {
await Paint // in case two players change team in the same tick, only rerender once

for index, team in Teams %%{
Panel(width=30, minHeight=20) {
H2 { "Team " + (index+1) }

if team == Team {
RaisedButton(backgroundColor=Color:Primary, opacity=0.5) { "Joined" }
} else {
RaisedButton(PressIntent<joinTeam>(index), backgroundColor=Color:Primary) { "Join" }
}

for player in QueryPlayers(team=) %%{
P { %(player.PlayerName) }
}
}
}
}
}

RaisedButton(PressIntent<startGame>, backgroundColor=Color:Primary) {
"Start Game"
}
}

on Pressed<joinTeam> index {
Team = Teams[index]
TeamsChanged
}

on Pressed<startGame> {
CommenceGame
}

once AfterGameCommenced { Expire }
}
}
  • The TeamSelectorSystem is responsible for displaying the team selector interface and handling its logic. It contains everything needed to make the team selector work.
  • The entire team selector interface exists under a Subspawn, which makes it easy to clean up when its done. Simply calling Expire removes the entire interface and all of its subcomponents from the game.
  • One TeamSelectorSystem is spawned for each owner. This allows the interface to be specialized for each player, rather than displaying the same for everyone. This way a player can see a greyed-out "Joined" button on the team they are on, rather than the usual "Join" button.
  • When the player clicks the "Join" button, the player's team gets updated to the chosen team. The index of the team is used to recover the team entity from the Teams array. This is important because the team entity cannot be sent across the network (because Entities are not Sendable), so we have to use the index instead.
  • Whenever the player changes teams, a TeamsChanged signal is emitted to trigger the interface to update for all players. This is a common pattern in Easel - when you want to detect change within a collection of entities, it is easiest to make a signal that represents "something in this collection has changed".
  • When the "Start Game" button is clicked, the CommenceGame function is called to signal that the game should start. main.easel is listening for this signal and will start the game when it receives it.
tip

When you are making an Easel game, you should think of Entities and Systems. A System is a design pattern where you collect together all the code related to a particular feature in one place, and then "install" it onto the entity by calling its function like TeamSelectorSystem. The system then takes care of all the logic related to that feature, and cleans itself up when its done.

Next Steps

This is an example of a team selector user interface, but the best team selector is one that is integrated into the game itself. For example, on a tennis court, the players would move to the left or right side of the arena to choose their team. Whenever they cross the center line, switch their Team parameter to the other team. This will cause the colors to change immediately and they will recognize that they are now on the other team. This is more fun, and allows the players to learn the controls and get used to the game while waiting for the game to start.