Building a Memory Matching Game with Phaser 3 and TypeScript

Creating interactive and engaging games can be an incredibly rewarding experience. In this post, I'll dive into the world of game development using Phaser 3, a popular JavaScript game engine.

The Initial Hurdles and Triumphs

Setting up a new development environment can often be a daunting task. However, with a clear roadmap and the right resources at hand, it turned out to be a surprisingly smooth experience.

After installing Node.js and npm, I stumbled upon the Phaser 3 Webpack Project Template. This proved to be the perfect starting point, providing a well-structured foundation for my game development journey. With a sense of excitement and anticipation, I followed the template's instructions to set up my new project.

Welcoming Visual Elements: Assets and Development Environment

With the project structure in place, it was time to breathe life into my game. I gathered a collection of free online assets, specifically a set of pixel-style cards.

The Memory Matching Game: A Voyage into Nostalgia

Creating the game board was my first foray into Phaser 3's capabilities. The elegant use of Phaser.GameObjects.Container allowed me to neatly organize the cards, setting the stage for the challenge ahead.

    for (let i = 0; i < gridRows; i++) {
      for (let j = 0; j < gridCols; j++) {
        let card = this.add.sprite(j * cardWidth, i * cardHeight, 'cardSheet', 14);

        // Set a custom value attribute
        card.value = Phaser.Math.Between(0, 13);
        card.flipped = false;


With the canvas prepared, it was time to infuse life into the cards. The logic for flipping them, animating the transitions, and handling interactions became my canvas. Each line of code felt like a brushstroke, painting a vivid picture of interactivity.

    cards.forEach(card => {      
      card.on('pointerdown', () => {        
        // Set the users active card to the card they clicked
        card.flipped = true;

Navigating the Puzzling Logic of Matching Pairs

As the game took shape, I encountered my first major challenge - ensuring that pairs of matching cards remained face-up. This seemingly simple task was a puzzle in itself. The dance between logic and interactivity pushed my problem-solving skills to the limit.

    const cardValue = card.value;
    // Set the new frame of        
    // the card from the sprite sheet        
    if (isMatched == false) {          
    if (activeCard.value == cardValue) {          
    if (activeCard.value != cardValue) {          

To start, I created a flipCard function to handle card interactions. As I progressed, I encountered a minor hiccup when implementing setTimeout for card flipping. To resolve this, I stored a new reference to lastClickedCard before applying it within the timeout function. This adjustment allowed for smooth execution of the card flipping mechanic. Here's an example of the revised code snippet:

    if (lastValue === currentValue) {
      // Cards match, keep them face-up  

    } else {
      // Store a reference to lastClickedCard  
      const lastCard = lastClickedCard; 
      setTimeout(() => {
        card.setData('flipped', false);
        lastCard.setData('flipped', false);
      }, 1000);

This experience showcases how diligent debugging and minor adjustments can lead to significant progress in game development!

Custom Fonts and Art Assets: Adding a Personal Touch

One of the key elements in giving my game a unique look and feel was the integration of a custom font. I wanted to match the cards, so I decided to use a font reminiscent of classic pixelated games. After obtaining the font file, I loaded it dynamically using JavaScript, ensuring it was applied from the very start of the game.

To complement the font, I also wanted to add some visual flair. I turned to Figma, a powerful design tool, to create custom art assets. Figma's intuitive interface allowed me to craft pixel-perfect graphics that perfectly matched the aesthetic I was aiming for. Additionally, I leveraged an art UI pack from Kobliznik, which provided a fantastic base for my game's visual elements.

A screenshot of the Pixel Pairs main menu screen, with a start button, title, and a series of cards.

With these assets, my game started to take shape, capturing the essence of the pixelated nostalgia I was aiming for.

Spicing Things Up: Randomization and a Restart Button

To add an extra layer of challenge and replayability to the game, I introduced an element of unpredictability. Now, when the game starts, the cards are randomly arranged, ensuring that no two playthroughs are quite the same. This not only keeps players engaged but also adds an exciting twist to the familiar memory matching gameplay.

In response to feedback from playtesters, I decided to implement a "Restart" button. This simple yet invaluable addition allows players to quickly reset the game and try their hand at matching once more. It's a small feature, but it greatly improves the overall user experience, making it more convenient and enjoyable for players to dive back into the game.

A screenshot of the game screen before it was updated, with the score bleeding over the edge and the cards in the top left.

In the card-matching game Super Mario Bros. 3, I noticed that they used a 6x3 grid. I opted to do the same for my game. When I updated that, you can see the elements ended up all over the place. I quickly fixed that!

A depiction of the finalized game screen. It has a score, restart button, and some cards face up and down.

Now, the game has a solid core game loop but still needs a win condition. All-in-all, this was a delightful experience and really helped me learn Phaser!

Click here to play the game!

And click here to view the source code!

Check out another post!

©2024 John Bentley Creative