サンフランシスコで開催されるQCon 2024にお出かけですか? ミーティングを予約する!

そう、私たちはMomentoで、リスをテーマにした『Flappy Bird』の多人数参加型レプリカを作ったのだ。

Flappy MoをMomento CacheとTopicsで1日以内に構築した方法をご覧ください。

アレン・ヘルトン
著者

Share

I recently made a browser-based game called FlappyMo. It’s a version of a game that made waves in 2014 called Flappy Bird. It’s a simple arcade-style game where you make a bird jump between scrolling pipes on the screen. My version replaces the bird with our company mascot, Mo, and adds a multiplayer element to it so you can compete against your friends in real time.

Naturally, I used Momento to build the interactive components of the game. Since Momento Cache と Topics are fully functional in the browser, I took advantage of both of them to quickly and easily tackle what normally would be a weeks-long effort in just a few hours. To take a traditionally single-player game and make it multiplayer, I needed to add two capabilities: player tracking と session synchronization.

Before we dive into those, there are a couple of other features I added to make the player experience more meaningful. Of course, we needed some form of authentication. The game is open with no sign-up necessary, but users still need to be uniquely identified. In addition to auth, the game needed to start for everybody at the same time. This allows players to see each other on screen and compete head-to-head. So I added a remote start function that triggers the game to start for all players simultaneously. This leaves us with an architecture that looks something like this:

Flappy Mo Architecture Diagram

Let’s go over the different pieces of the system and how they all play together to make a fun Flappy Mo experience.

Authentication

When a user lands on the game website for the first time, they are prompted to input a username. After they enter a good one and hit Submit, an API call is made to a server-side component of the game to login.

Since we don’t technically have users in the system, instead of verifying a username and password, we’re issuing a temporary auth token with a limited set of permissions. The auth token is valid for an hour and is cached in Momento to prevent issuing another token before it expires.

Any Momento client that uses this token is allowed to publish and subscribe to the flappy-mo topic and read from the players cache Set item (more on this in a minute). In addition, we embed the player’s username into the token so we can uniquely identify any action they perform. 

Unique token identifier diagram

This token is passed back to the player and used to initialize the Momento Cache and Topic clients in the browser session.

Player tracking

After an auth token is issued and we’ve landed on the game page, we need to tell the server we’re here and ready for action. To do this, the game sends a beacon to a server-side component to register the username with the player list on initial load of the page. Conversely, when the page is unloaded, either by pressing the back button or closing the tab, another beacon is sent to remove the username from the list.

closing a tab and remove player diagram

A beacon is an asynchronous and non-blocking call to a web server. This is used in our player tracking mechanism instead of a traditional API call because actions taken during an unload event need to be non-blocking to prevent a degraded user experience when navigating away. Beacons are delivered on a “best effort” approach, meaning it’s possible to send the event more than once. To handle the situation where a login beacon is sent more than once, we use a set cache item.

A set is an unordered collection of unique elements. So no matter how many times we try to add an individual username to our player set, it will only appear one time. This allows our beacon handler to be idempotent, meaning it will handle duplicate requests by having the same effect on the system.

Remote start

For a multiplayer arcade-style game to be fun for players, everybody needs to start playing at the same time. This lets you see how far your friends get because you’ll be at the same place in the puzzle at all times.

To do this, I trigger an AWS Lambda function on a cron schedule every 30 seconds. This Lambda function will generate random values for the next game. Gravity strength, jump height, pipe spacing, and game speed are all chosen at random to keep the rounds constantly changing and fun.

Once the game details have been randomized, they are published via Momento Topics. All open instances of the game are subscribed to the topic and receive the message in real time. Each game will update the round details with the values from the message and start a 3-second countdown.

Session sync

Up to this point, we’ve managed the game players and got everyone playing at the same time. Now we need to update all games in real time as players jump around on screen. 

Movement for each player is calculated in the browser, so whenever a player presses the jump button, we publish their new velocity and y coordinate via Momento Topics. All game instances receive this message immediately and update the location of the player who sent the movement event. 

Since the username is embedded in the auth token used to publish the message, the user id is provided in the event by default – allowing us to safely and securely modify player movement implicitly.

await topics.subscribe('flappy-mo', 'player-moved', {
  onItem: async (event) => updatePlayer({
    message: event.value(), 
    username: event.tokenId()
  })
});

The username is automatically included in the event, so we don’t need to provide it when publishing the updated coordinates. Publishing is a simple one-liner:


topics.publish('flappy-mo', 'player-moved', JSON.stringify({ 
  velocity: player.velocity, 
  y: player.y
}));

No backend architecture or WebSocket connections to manage. Keeping state synced across all the active games is literally the two code blocks above. AND it was done completely in the browser. That’s it!

Gameplay

At this point we should probably talk about the game. When the game countdown is triggered by the remote start functionality, a couple things happen.

First, the player array is refreshed from the cache. We load the players cache set directly in the browser and update the list we have locally by adding any new usernames and removing the ones who aren’t there anymore.

I love this pattern where the cache set is modified on the server-side with the beacon, but read directly on the client. Shared resources between the client and server make development amazingly simple

After we update the player list, the coordinates are reset back to the starting values for every player. This makes it so every player starts at the same place at the same time – a nice fair game!

Gif of Flappy Mo in action

As mentioned earlier, all player movement is calculated in the browser. When the animation loop triggers, it does some math for each player, taking into consideration the round-specific gravity, pipe spacing, and game speed to move them accordingly. If the game detects a collision between a player and one of the pipes, it sets their active state to false and hides them from the screen until the next round begins. This process continues until everybody hits a pipe, then the game resets when the next remote start is triggered.

Ready to play?

Play FlappyMo Now Graphic

Now that you know how it’s built, it’s time to try it out! The game can be found here. Remember, rounds start every 30 seconds, so you probably won’t be able to start immediately when you land on the page.

To play, either click your mouse or press the spacebar to jump. If you’re on mobile, simply tap the screen. This is a fun yet incredibly frustrating game, so take your time and hope the dice roll in your favor for the random game settings!

If you’d like to look at the source code, here is the code in GitHub. The user interface is written in Next.js and the remote start function is a Node.js Lambda function. Feel free to fork it or make any modifications you’d like!

Momento drastically simplifies game development by connecting your browser sessions together and providing you with shared resources between your server-side implementation and clients. Easily build in-game chat, massive-scale leaderboards, rate limiting controls, and much more with Momento’s extensive set of SDKs. Cross-platform development is a snap with clients available for browser-based apps, iPhone, Android, and everything in between.

Interested in learning more about game development with Momento? Check out our gaming page! Have a question or want to bounce an idea off the team? Join our Discord!

Happy coding!

Share