Skip to main content

Leaderboard

Leaderboards can be a way to encourage players to compete with each other and keep playing to try to beat their high score.

When we want to make leaderboards in Easel, we use accumulators. Accumulators are special properties that retain their value even when the player exits the game. They can be sorted and shown to other players, so they are perfect for keeping track of high scores. Let's use an accumulator to create a leaderboard for Astroblast!

Creating an accumulator

First, we will create an accumulator called BestScore to keep track of the best score the player has achieved across all their play sessions.

We will also create a property called HasExceededBestScore to keep track of whether the player has beaten their best score in the current play session, so we can show a special message when they do.

Select your scores.easel file. Insert the following highlighted code into it:

pub prop owner.Score = 0

pub accumulator owner.BestScore = 0
pub prop owner.HasExceededBestScore = false

pub fn owner.ScoreSystem() {
// ...
}

Updating the accumulator

Now that we have created our BestScore accumulator, we need to assign the score to it.

In some games, you would only assign the score at the end of a round, but in our case, we will continually write to BestScore so even if the player dies mid-round and exits the game, their best score will still be saved.

Select your scores.easel file. Insert the following highlighted code into it:

pub prop owner.Score = 0

pub accumulator owner.BestScore = 0
pub prop owner.HasExceededBestScore = false

pub fn owner.ScoreSystem() {
TopContent {
with Score {
if Score > 0 {
H1 { %(Score) }
} else {
InsetPanel {
P {
Span(bold=true, color=#0f0) { "Arrow Keys" }
" to move, "
Span(bold=true, color=#0f0) { "Click" }
" to fire"
}
}
}
}
}

with Score {
if Score > BestScore {
BestScore(showOnLeaderboard=true) = Score
HasExceededBestScore = true
}
}
}

Leaderboard page

Now we will create a leaderboard page to display all the players' best scores.

Create a new file called leaderboard.easel. Copy-and-paste the following code into it:

pub page fn owner.LeaderboardPage() {
Content {
H1 { "High Scores" }
LeaderboardPanel
}
}

prop World.FetchedLeaderboard
pub fn World.LeaderboardPanel([ui]) {
once FetchLeaderboard([&BestScore]) leaderboard {
FetchedLeaderboard = leaderboard
}

with FetchedLeaderboard {
if FetchedLeaderboard {
for use index, player in FetchedLeaderboard %%{
ShinyPanel(backgroundColor=#b0f, vPadding=0.4) {
HStack {
P(fontSize=0.75, color=#eee, width=1.5, align=Align:Center) {
%(index+1)
}
P(fontSize=1.2, bold=true) {
player.PlayerNameDisplay(color=#fff)
}

Blank(expand=true)

P {
%(player.BestScore)
" points"
}
}
}
}

if FetchedLeaderboard.IsEmpty {
P { "No one on the leaderboard... yet" }
}
} else {
P { "Loading..." }
}
}
}

Now we need to link to this leaderboard page from the homepage, so people can find it easily.

Select home.easel. Insert the following highlighted code at the top of your HomePage function:

pub page fn owner.HomePage {
Toolbar {
FlatButton(LeaderboardPage) {
Icon("fa-solid fa-trophy")
" Leaderboard"
}
}

ContentScreen {
Standout { %(ProjectName) }

VStack(gap=0.3) {
P(fontSize=0.8) { "Choose Your Name" }
PlayerNameEditor(width=25)
}

RaisedButton(Main, backgroundColor=Color:Primary, width=25, fontSize=1.5) { "Play" }
}
}

New high score message

When the player dies, we show a "Game Over" dialog. We are going to incorporate the high score into the "Game Over" dialog in three ways:

  • We will show the player's score on the dialog, so they can see how well they did.
  • If the score was better than their previous high score, we will indicate that with a congratulatory message.
  • Finally, we will add a button to the dialog that takes them straight to the leaderboard page, so they can see how their score compares to other players' scores.

Select main.easel. Insert the following highlighted code into your GameOverDialog function:

fn dialog.GameOverDialog([owner]) {
OverlayContent {
VStack(align=Align:Center) {
H1(fontSize=5) { FlyInWords("Game Over") }

InsetPanel(align=Align:Center, animate=Animate:FlyFromBottom) {
P {
"Score: "
Span(bold=true) {%(Score) }
}

if HasExceededBestScore {
P(color=#e580ff, bold=true) {
"New high score!"
}
}
}

Blank(height=0.5)

HStack(align=Align:Center, animate=Animate:FlyFromBottom) {
RaisedButton(Main, backgroundColor=Color:Primary, expand=true) { "Play Again" }
RaisedButton(LeaderboardPage, backgroundColor=Color:Secondary, expand=true) { "High Scores" }
}
}
}
}

Click Preview and play the game. Go blow up some asteroids to get some score, then to test everything works, keep crashing into asteroids until you see the game over dialog.

You should see your score displayed on the dialog, and you might even have "New high score!" next to it. Try clicking the new "High Scores" button to go to the leaderboard page.

Recap

In this chapter, we created a leaderboard for our game using an Accumulator. This allowed us to persistently store the player's best score across play sessions, and show it on a leaderboard page for all players to see.