Talking about Legends of Runeterra game, Game development, HTML5, Javascript and Phaser.
A lot of you enjoyed the post draw a card from your hand like in “Legends of Runeterra” mobile version, so here I am with an update.
Now you can draw cards, by dragging them on the field. While in first step I managed the cards in hand with an array, now I use two groups: a group for the cards you have in hand, and another group for the cards played on the field.
Each card position is updated on the fly according to the amount of cards in hand and on the field.
The preview card is a sprite whose texture matched the one of the card we are about to play.
Everything is managed by tweens and input events, have a look:
Pick cards, move them around the stage, and drop them on the drop zone to add them to the field. A camera shake effect gives a feeling of awesomeness to the card you just played.
Now the source code is way more organized and I also added a lot of comments:
let game;
let gameOptions = {
startingCards: 6,
cardWidth: 265,
cardHeight: 400,
handSizeRatio: 0.5,
boardSizeRatio: 0.3
}
window.onload = function() {
let gameConfig = {
type: Phaser.AUTO,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
parent: "thegame",
width: 1280,
height: 740
},
scene: playGame
}
game = new Phaser.Game (gameConfig);
window.focus();
}
class playGame extends Phaser.Scene {
constructor() {
super("PlayGame");
}
preload() {
this.load.image("background", "background.jpg");
this.load.spritesheet("cards", "cards.png", {
frameWidth: gameOptions.cardWidth,
frameHeight: gameOptions.cardHeight
});
}
create() {
// group containing cards on board
this.boardGroup = this.add.group();
// group containing cards in hand
this.handGroup = this.add.group();
// game background
this.background = this.add.sprite(game.config.width / 2, game.config.height / 2, "background");
// creation of card preview sprite
this.createCardPreview();
// set a drop zone
this.zone = this.add.zone(650, 460, 800, 200);
this.zone.setRectangleDropZone(800, 200);
// create and place cards in hand
for(let i = 0; i < gameOptions.startingCards; i ++) {
this.createCard(i);
}
// listener fired when we start dragging
this.input.on("dragstart", function(pointer, card) {
// is the card in hand?
if(this.handGroup.contains(card)) {
// remove card from hand
this.handGroup.remove(card);
// re-arrange cards in hand
this.arrangeCardsInHand();
// bring the card in front
card.setDepth(this.handGroup.countActive());
// set card preview frame to match dragged card frame
this.cardPreview.setFrame(card.frame.name);
// tween to animate the card
this.tweens.add({
targets: card,
angle: 0,
x: pointer.x,
y: pointer.y,
displayWidth: gameOptions.cardWidth,
displayHeight: gameOptions.cardHeight,
duration: 150
});
// tween to fade the background
this.tweens.add({
targets: this.background,
alpha: 0.3,
duration: 150
});
};
}, this);
// listener fired when we are dragging
this.input.on("drag", function(pointer, card) {
// if the card is not in hand and not on the board...
if(!this.handGroup.contains(card) && !this.boardGroup.contains(card)) {
// move the card to pointer position
card.x = pointer.x;
card.y = pointer.y;
}
}, this);
// listener fired when we are dragging and the input enters the drop zone
this.input.on("dragenter", function() {
// show card preview
this.cardPreview.visible = true;
// arrange cards on board
this.arrangeCardsOnBoard(true);
}, this);
// listener fired when we are dragging and the input leaves the drop zone
this.input.on("dragleave", function() {
// hide card preview
this.cardPreview.visible = false;
// arrange cards on board
this.arrangeCardsOnBoard(false);
}, this);
// listener fired when we are dragging and the input leaves the drop zone
this.input.on("drop", function(pointer, card) {
card.setDepth(0);
// move the card on its final position
this.tweens.add({
targets: card,
angle: 0,
x: this.cardPreview.x,
y: this.cardPreview.y,
displayWidth: gameOptions.cardWidth * gameOptions.boardSizeRatio,
displayHeight: gameOptions.cardHeight * gameOptions.boardSizeRatio,
duration: 150,
callbackScope: this,
onComplete: function(){
// little camere shake
this.cameras.main.shake(300, 0.02);
// add the card to board group
this.boardGroup.add(card);
// arrange cards on board
this.arrangeCardsOnBoard(false);
}
});
}, this);
// listener fired when we stop dragging
this.input.on("dragend", function(pointer, card, dropped) {
if(!this.handGroup.contains(card) && !this.boardGroup.contains(card)) {
// hide card preview
this.cardPreview.visible = false;
// if the card hasn't been dropped in the drop zone...
if(!dropped) {
// add dragged card to hand group
this.handGroup.add(card);
// arrange cards in hand
this.arrangeCardsInHand();
}
// tween to make the background visible again
this.tweens.add({
targets: this.background,
alpha: 1,
duration: 150
});
}
}, this);
}
// THE FOLLOWING METHODS ARE ONLY USED TO SET CARDS POSITION, BOTH IN HAND AND ON THE TABLE
// method to create the sprite used to preview card
createCardPreview() {
this.cardPreview = this.add.sprite(0, 0, "cards");
this.cardPreview.visible = false;
this.cardPreview.alpha = 0.25;
this.cardPreview.displayWidth = gameOptions.cardWidth * gameOptions.boardSizeRatio;
this.cardPreview.displayHeight = gameOptions.cardHeight * gameOptions.boardSizeRatio;
this.cardPreview.setOrigin(0.5, 1);
}
// method to create a card
// n = card number
createCard(n) {
let coordinates = this.setHandCoordinates(n, gameOptions.startingCards);
let card = this.add.sprite(coordinates.x, coordinates.y, "cards", n);
card.setOrigin(0.5, 1);
card.rotation = coordinates.r;
card.handPosition = n;
card.setInteractive({
draggable: true
});
card.displayWidth = gameOptions.cardWidth * gameOptions.handSizeRatio;
card.displayHeight = gameOptions.cardHeight * gameOptions.handSizeRatio;
this.handGroup.add(card);
}
// method to set card board coordinates
// n = card card number
// totalCards = amount of cards on the board
setPreviewCoordinates(n, totalCards) {
return {
x: game.config.width / 2 - (totalCards - 1) * gameOptions.cardWidth * gameOptions.boardSizeRatio * 0.6 + n * gameOptions.cardWidth * gameOptions.boardSizeRatio * 1.2,
y: 700
}
}
// method to arrange cards on board
// preview = true if we have to show card preview
arrangeCardsOnBoard(preview) {
// determine the amount of cards on board, preview included
let cardsOnBoard = this.boardGroup.countActive() + (preview ? 1 : 0);
// set position of the cards on board
this.boardGroup.children.iterate(function(card, i) {
let coordinates = this.setPreviewCoordinates(i, cardsOnBoard);
card.x = coordinates.x;
card.y = coordinates.y;
}, this);
// set the position of card preview, if any
if(preview){
let cardPreviewPosition = this.setPreviewCoordinates(cardsOnBoard - 1, cardsOnBoard);
this.cardPreview.x = cardPreviewPosition.x;
this.cardPreview.y = cardPreviewPosition.y;
}
}
// method to set card in hand coordinates
// n = card card number
// totalCards = amount of cards on the board
setHandCoordinates(n, totalCards) {
let rotation = Math.PI / 4 / totalCards * (totalCards - n - 1);
let xPosition = game.config.width + 200 * Math.cos(rotation + Math.PI / 2);
let yPosition = game.config.height + 200 - 200 * Math.sin(rotation + Math.PI / 2);
return {
x: xPosition,
y: yPosition,
r: -rotation
}
}
// method to arrange cards in hand
arrangeCardsInHand() {
this.handGroup.children.iterate(function(card, i) {
card.setDepth(i);
let coordinates = this.setHandCoordinates(i, this.handGroup.countActive());
this.tweens.add({
targets: card,
rotation: coordinates.r,
x: coordinates.x,
y: coordinates.y,
displayWidth: gameOptions.cardWidth / 2,
displayHeight: gameOptions.cardHeight / 2,
duration: 150
});
}, this);
}
}
I want to add some more effects, in the 3rd part we’ll see how to draw cards from the deck to player hand, maybe with a fake 3D effect, download the source code and stay tuned.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.