Teams
One way to make games more interesting is to add teams. Easel can support any number of teams in any configuration, whether it be a free-for-all, two equal-sized teams, or maybe even a game where most players unite against a single enemy. Teams may even change mid-game.
Declaring teams
To declare a team, simply Spawn an entity to represent the team. There is nothing special about a Team entity - just spawn a normal entity and start using it as a team, and it will be a team.
Fixed Teams
If you have a game with a fixed number of teams, you would typically assign them to global constants so they are easy to access,
for example at the top of your main.easel file:
pub const Team1 = Spawn
pub const Team2 = Spawn
It is sometimes useful to put the teams into an array for easy access:
pub const Teams = [Team1, Team2]
This lets you generalize your code over teams - you can loop through the teams easily, and access them by their index in the array.
By default, the team color is automatically chosen by the engine. See Player Colors to learn how to configure team colors.
Assigning teams
Assign a player to a team by setting their Team property.
Any two players with the same Team property value are considered to be on the same team.
In a normal game, we recommend you assign a player to a team as soon as they join the game, and then provide an interface for them to switch teams if they want to. This is how you would make a player join the team with the fewest players when they join:
pub game fn World.Main(maxHumanPlayers=10) {
SpawnEachPlayer owner {
Team = Teams.MinBy(|team| CountPlayers(team=))
// ...
}
}
See How to: Make a Team Selector for a worked example of how to make an in-game team selector interface that players can use to switch teams while they wait for the game to start.
What Gets Affected By Teams?
Team assignment has a number of effects:
- Players on the same team will be given the same color in-game.
- User interface or graphical functions that take an
audienceparameter can be limited to only be visible to players on the same team. - Querying functions (such as
Query) can return different results depending on a player's team.
Typical Team Workflow
If you have a typical game where there are a fixed number of teams, and the teams are determined at the start of the game, we recommend this workflow:
-
Joining: When the player joins, immediately assign them to a team. You could select the team that currently has the fewest number of players, for example. Do not wait for them to choose because it just creates unnecessary friction.
-
Switching: Display an interface to allow the players to switch teams if they want to. This could be a user interface with two columns and buttons to switch between them, but the best interface is integrated into the game itself. For example, on a basketball 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
Teamparameter to the other team so their colors change immediately and they 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 they are waiting for the game to start. -
Waiting: The game should continuously display an up-to-date message as to why the game cannot start yet. "Waiting for players to join" or "Not enough players on Team 1" for example. This is where you would include a "Play vs AI" button to automatically solve the problem of not having enough players, if you want to support that.
-
Starting: Once all requirements are met, the game should start immediately as soon as one person clicks the "Start Game" button. Or even better, start the game as soon as someone takes an action. For example, when someone grabs the ball and starts running with it, or when someone crosses the starting line, or when the first player throws a fireball. Depending on your game, it may be appropriate for there to be a 3-second countdown before the game truly starts.
Call CommenceGame to signal that the game has started, and have all your systems wait for AfterGameCommenced before they start doing anything.
We recommend you put your team selector inside the game itself. This means the players are choosing teams with the people who they will actually be playing with. Do not put the team selector on the home page, for example, because at that point the players do not yet know who they will be matched with, especially if your game has a large number of players and multiple online lobbies running at once.
Entities and Teams
Your game will likely consist of many entities that are not players, such as minions, towers, heroes, but also all of their projectiles, lasers, bombs, buffs, etc.
To determine which team an entity belongs to, there are two methods.
- If an entity wants to know its own owner, normally you would be passing an
ownervariable down through context from the player that spawned it. You can useowner.Teamto get the team of the owner player. - If an entity wants to know the team of another entity
thatwhich it has just collided with, it can usethat.Teamto get the team of the other entity's owner.
Below is an sketch of how this might look in practice:
// main.easel
pub game fn World.Main(maxHumanPlayers=10) {
SpawnEachPlayer owner { // `owner` is the player being spawned
Subspawn hero {
Hero // `owner` is passed into `Hero` from context
}
}
}
// hero.easel
pub fn hero.Hero([owner]) { // `owner` is received from context
// ... setup your hero here
on ButtonDown(KeySpacebar) {
Subspawn projectile {
Fireball // owner is passed into `Fireball` from context
}
}
}
// fireball.easel
pub fn projectile.Fireball([hero, owner]) { // `owner` is received from context
// ... setup your fireball here
on BeforeCollide that {
if that.Category.Overlaps(Category:Hero) && that.Team != owner.Team {
// Only apply damage to enemies, not allies
that.Health -= 10
}
}
}
In the above example, the that.Team != owner.Team condition checks whether the fireball has collided with an enemy
before applying damage, so that players cannot hurt their teammates.
owner.Teamrefers to the owner of the fireballthat.Teamrefers to the owner of the entity that the fireball has collided with. Notice that we could also writethat.Owner.Teamto get the same result, but theTeamproperty is clever enough to know to look up theOwnerbecause it can seethatis not a player entity.
See the Team property to learn more.
By convention, a variable named owner refers to the player who owns the current entity.
It is normal for functions to take an [owner] parameter from context as it is such a commonly-used variable.
Having owner readily available in every context means you are always ready to easily hook to functions that respond to input like
on ButtonDown or on Pressed.
Querying for Players on a Team
You can use QueryPlayers(team=Team1) function to get a list of all the players on Team1, for example.
Consider using CountPlayers(team=Team1) if you only need to know how many players are on a team, as it is more efficient.
You can combine this with other query criteria, for example
QueryAnyPlayer(team=Team1, isEliminated=false) tells you if there is any player on Team1 that is still uneliminated,
which could be useful as a win condition for a team-based game.
See QueryPlayers to learn more about querying players.
Querying for Entities by Team
You can Query for entities based on their relationship to a particular player
by using the against parameter with an alliance flag. For example:
QueryNearest(filter=Category:Hero, against=Alliance:Enemy)would give you the nearest enemy hero, presuming you had defined a category calledCategory:Heroto represent all the hero entities in the game.QueryWithinRadius(filter=Category:Hero, against=(Alliance:Self | Alliance:Ally))would give you all the heroes within a certain radius that are on the same team as you.
There are four Alliance flags which can be used in the against parameter:
Alliance:Selfif the two entities have the same ownerAlliance:Neutralif at least one of the entities has no ownerAlliance:Allyif the owners have the same teamAlliance:Enemyif the owners are on different teams
You can also use the the Alliance function to calculate the alliance flags between any two entities.