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,
Team1andTeam2are declared up front and assigned to global constants so they are easy to access. - A
Teamsarray is declared to hold the teams. This lets the team selector interface easily loop through the teams to display them. - The
await GameCommencedline is used to delay the spawning of the hero until after the game has started. This is triggered later inteamSelector.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 theHerowhich player owns it. - The
ownerColor=trueparameter 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
TeamSelectorSystemis 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 callingExpireremoves the entire interface and all of its subcomponents from the game. - One
TeamSelectorSystemis spawned for eachowner. 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
indexof the team is used to recover the team entity from theTeamsarray. This is important because the team entity cannot be sent across the network (because Entities are not Sendable), so we have to use theindexinstead. - Whenever the player changes teams, a
TeamsChangedsignal 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
CommenceGamefunction is called to signal that the game should start.main.easelis listening for this signal and will start the game when it receives it.
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.