Talking about Yanga game, Game development, HTML5, Javascript and Phaser.
If you love videogames, you don’t care which platform they are developed for. You just play great games, everywhere.
So I am introducing you Yanga, a game made by Serdjuk for the ZX Spectrum. Yes, the ZX Spectrum, the 8-bit personal home computer released in 1982, 39 years ago.
You should definitively play it, as it’s an interesting mix between Sokoban, a match 3 game and an action puzzle game.
This short GIF video represents level 1.
I am showing you how to draw a Yanga level, starting from a Tiled map array filled with objects. In this case, just like with Totem Destroyer prototype, I preferred to use objects rather than tiles because they allow more room for customization.
This is level 2:
And this is the Tiled scheme of the level:
As you can see, I did not draw walls tile by tile, but with bigger objects. Basically because I am lazy.
And this is my version of the level:
There is no interaction or input management at the moment, but there are the glowing runes and walls, both player and exit have their animations, and wall tiles are randomly placed, rotated, flipped and colored.
The orginal game has not been decompiled. You should never decompile games, the real challenge is the rebuild it on your own. I used the original graphics just to let you see how the prototype looks like the original game.
Now, the completely commented source code:
let game;
let gameOptions = {
// tile size in Tiled map
tiledSize: 32,
// tile size in the game
actualSize: 64,
// wall colors
wallColors: [0xcecb00, 0x00fb00, 0x00cbce],
// rune colors
runeColors: [0xff0000, 0x0000ff],
// player color
playerColor: 0xcecbce,
// door colors
doorColors: [0xff0000, 0x00fb00]
}
window.onload = function() {
let gameConfig = {
type: Phaser.AUTO,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
parent: "thegame",
width: 960,
height: 640
},
scene: playGame
}
game = new Phaser.Game(gameConfig);
window.focus();
}
class playGame extends Phaser.Scene {
constructor() {
super("PlayGame");
}
preload() {
// load tiled map
this.load.tilemapTiledJSON("level", "level.json");
// player sprites
this.load.spritesheet("player", "player.png", {
frameWidth: gameOptions.actualSize,
frameHeight: gameOptions.actualSize
});
// door sprites
this.load.spritesheet("door", "door.png", {
frameWidth: gameOptions.actualSize,
frameHeight: gameOptions.actualSize
});
// wall tiles
this.load.spritesheet("tiles", "tiles.png", {
frameWidth: gameOptions.actualSize,
frameHeight: gameOptions.actualSize
});
}
create() {
// add the tilemap
let map = this.add.tilemap("level");
// array to store runes
this.runeWallArray = [];
// player animation
this.anims.create({
key: "playerAnim",
frames: this.anims.generateFrameNumbers("player", {
frames: [0, 1, 0, 2]
}),
frameRate: 8,
repeat: -1
});
// door animation
this.anims.create({
key: "doorAnim",
frames: this.anims.generateFrameNumbers("door", {
frames: [0, 2, 1, 3, 1, 2, 0]
}),
frameRate: 8,
repeat: -1
});
// select all objects in Object Layer zero, the first - and only, at the moment - level
let blocks = map.objects[0].objects;
// looping through all blocks and execute addBlock method
blocks.forEach (blocks => this.addBlock(blocks));
// give some kind of glow effect to runes
this.tweens.add({
targets: this.runeWallArray,
alpha: 0.8,
duration: 100,
yoyo: true,
repeat: -1
});
}
// method to add a level block
addBlock(block) {
// get block x and y position
let blockX = block.x / gameOptions.tiledSize * gameOptions.actualSize + gameOptions.actualSize / 2;
let blockY = block.y / gameOptions.tiledSize * gameOptions.actualSize + gameOptions.actualSize / 2;
// check block type
switch (block.type) {
// wall
case "Wall":
// get wall width and height
let wallWidth = block.width / gameOptions.tiledSize;
let wallHeight = block.height / gameOptions.tiledSize;
// place the tiles
for (let i = 0; i < wallWidth; i ++) {
for (let j = 0; j < wallHeight; j ++) {
// place tile sprite
let tile = this.add.sprite(blockX + i * gameOptions.actualSize, blockY + j * gameOptions.actualSize, "tiles", Phaser.Math.RND.integerInRange(0, 3));
// give randomness to tile
tile.setFrame(Phaser.Math.RND.integerInRange(0, 3));
tile.angle = Phaser.Math.RND.integerInRange(0, 3) * 90;
tile.flipX = Phaser.Math.RND.integerInRange(0, 1) == 1;
tile.flipY = Phaser.Math.RND.integerInRange(0, 1) == 1;
tile.setTint(Phaser.Math.RND.pick(gameOptions.wallColors));
}
}
break;
// rune
case "Rune":
let runeValue = block.properties[0].value;
let rune = this.add.sprite(blockX, blockY, "tiles", 4 + 2 * runeValue);
rune.setTint(gameOptions.runeColors[runeValue])
break;
// rune wall
case "Rune Wall":
let wallValue = block.properties[0].value;
let runeWall = this.add.sprite(blockX, blockY, "tiles", 5 + 2 * wallValue);
runeWall.setTint(gameOptions.runeColors[wallValue]);
this.runeWallArray.push(runeWall);
break;
// player
case "Player":
let player = this.add.sprite(blockX, blockY, "player");
player.play("playerAnim");
player.setTint(gameOptions.playerColor);
break;
// entrance door
case "Entrance":
let entrance = this.add.sprite(blockX, blockY, "door");
entrance.setTint(gameOptions.doorColors[0]);
break;
// exit door
case "Exit":
let exit = this.add.sprite(blockX, blockY, "door");
exit.setTint(gameOptions.doorColors[1]);
exit.play("doorAnim");
break;
}
}
}
Next time I am going to add game logic and input management, then you can start building your Yanga-like game. Download the source code, Tiled project included.
And don’t forget to have a look at the original game.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.