About three years ago I published a HTML5 card management prototype built with Phaser, and since developers are bringing back card games, here I am to update the prototype to Phaser 3, since the original post uses an old Phaser 2 version.
A deck of cards is shuffled and the first card is drawn on the table. Then you can swipe up to make next card appear above current card or swipe down to make next card appear below current card. Then next card takes the place of the current card which leaves the stage, and you can swipe to call the third card, and so on.
It could be useful for one of those games where players try to guess if next card will be higher or lower than current one.
Have a look at the prototype:
Swip up or down to draw next card.
The updated source code is also completely commented, for a clean understanding of the whole process:
// the game itself let game; // global object with game options let gameOptions = { // card width, in pixels cardWidth: 334, // card height, in pixels cardHeight: 440, // card scale. 1 = original size, 0.5 half size and so on cardScale: 0.8 } window.onload = function() { let gameConfig = { type: Phaser.AUTO, backgroundColor: 0x4488aa, scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH, parent: "thegame", width: 750, height: 1334 }, scene: playGame } game = new Phaser.Game(gameConfig); window.focus(); } // two constants for better understanding of "UP" and "DOWN" const UP = -1; const DOWN = 1; class playGame extends Phaser.Scene { constructor() { super("PlayGame"); } preload() { // loading the sprite sheet with all cards this.load.spritesheet("cards", "cards.png", { frameWidth: gameOptions.cardWidth, frameHeight: gameOptions.cardHeight }); } create() { // can we swipe? this.canSwipe = true; // create an array with 52 integers from 0 to 51 this.deck = Phaser.Utils.Array.NumberArray(0, 51); // shuffle the array Phaser.Utils.Array.Shuffle(this.deck); // the two cards in game this.cardsInGame = [this.createCard(0), this.createCard(1)]; // we already have card 0 and 1 in game so next card index is 2 this.nextCardIndex = 2; // a tween to make first card enter into play this.tweens.add({ targets: this.cardsInGame[0], x: game.config.width / 2, duration: 500, ease: "Cubic.easeOut" }); // listener for player input this.input.on("pointerup", this.checkSwipe, this); } // method to create a card, given an index createCard(i) { // the card itself, a sprite created outside the stage, on the left let card = this.add.sprite(- gameOptions.cardWidth * gameOptions.cardScale, game.config.height / 2, "cards", this.deck[i]); // scale the sprite card.setScale(gameOptions.cardScale); // return the card return card; } // method to check if player input was a swipe checkSwipe(e) { // can the player swipe? if(this.canSwipe) { // determine swipe time, "release" timestamp minus "press" timestamp let swipeTime = e.upTime - e.downTime; // determine swipe vector let swipe = new Phaser.Math. Vector2(e.upX - e.downX, e.upY - e.downY); // get the magnitude, or length, of swipe vector let swipeMagnitude = swipe.length(); // reduce the vector to a magnitude of 1 let swipeNormal = swipe.normalize(); // we have a vertical swipe when: // * swipeMagnitude is bigger than 20, that is the player swiped for at least 20 pixels // * swipeTime is less than 1 second, gestures longer than one second can't be considered swipes // * the absolute value of the y component of the normal is 0.8 if(swipeMagnitude > 20 && swipeTime < 1000 && Math.abs(swipeNormal.y) > 0.8) { // swiping down if(swipeNormal.y > 0.8) { this.handleSwipe(DOWN); } // swiping up if(swipeNormal.y < -0.8) { this.handleSwipe(UP); } } } } // method to handle a swipe, given the direction handleSwipe(direction) { // we are swiping so we can't swipe anymore this.canSwipe = false; // which card are we moving? let cardToMove = (this.nextCardIndex + 1) % 2; // set moving card vertical position this.cardsInGame[cardToMove].y += direction * gameOptions.cardHeight * gameOptions.cardScale * 1.1; // tween the card to move to the horizontal center of the stage... this.tweens.add({ targets: this.cardsInGame[cardToMove], x: game.config.width / 2, duration: 500, ease: "Cubic.easeOut", callbackScope: this, onComplete: function() { // ... then wait a second or little more... this.time.addEvent({ delay: 1200, callbackScope: this, // ... then call moveCards method callback: this.moveCards, }); } }) } // method to update cards position moveCards() { // moving the first card let cardToMove = this.nextCardIndex % 2; // tween the card outside of the stage to the right this.tweens.add({ targets: this.cardsInGame[cardToMove], x: game.config.width + 2 * gameOptions.cardWidth * gameOptions.cardScale, duration: 500, ease: "Cubic.easeOut" }); // moving the second card cardToMove = (this.nextCardIndex + 1) % 2; // tween the card to the center of the stage... this.tweens.add({ targets: this.cardsInGame[cardToMove], y: game.config.height / 2, duration: 500, ease: "Cubic.easeOut", callbackScope: this, onComplete: function(){ // ... then recycle the card which we moved outside the screen cardToMove = this.nextCardIndex % 2; this.cardsInGame[cardToMove].setFrame(this.deck[this.nextCardIndex]); this.nextCardIndex = (this.nextCardIndex + 1) % 52; this.cardsInGame[cardToMove].x = gameOptions.cardWidth * gameOptions.cardScale / -2; // now we can swipe again this.canSwipe = true; } }); } }
Inside the source code of the project you will also find a PSD file with the card spritesheet, courtesy of game-icons.net, but keep in mind the texture I used is just for this test and it’s way too big for most, if not all, mobile devices.