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.
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.
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;
cards.push(card);
}
}
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.setInteractive();
card.on('pointerdown', () => {
// Set the users active card to the card they clicked
card.setFrame(card.value);
card.flipped = true;
}
}
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
card.setFrame(cardValue);
if (isMatched == false) {
card.setFrame(cardValue)
}
if (activeCard.value == cardValue) {
card.setFrame(cardValue);
score++;
}
if (activeCard.value != cardValue) {
card.setFrame(14);
}
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
score++;
updateScoreDisplay();
} else {
// Store a reference to lastClickedCard
const lastCard = lastClickedCard;
setTimeout(() => {
card.setFrame(0);
card.setData('flipped', false);
lastCard.setFrame(0);
lastCard.setData('flipped', false);
}, 1000);
}
This experience showcases how diligent debugging and minor adjustments can lead to significant progress in game development!
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.
With these assets, my game started to take shape, capturing the essence of the pixelated nostalgia I was aiming for.
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.
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!
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!