Phaser. Unit 6. Using tilemaps to build a space shooter game

The source code and the assets

You can download this zip file to get the source code and all the assets.

Updating the map

As done previously, you can use the tile map editor to update the map. In case you have not done it yet, you can download the editor from this link and install it on your computer.

To update the map you have to go the assets folder and open the file “town.tmx” which contains the map of the example in this unit. Select a layer (on the right corner, at the top) and change anything you like (by adding or removing some objects to or from the map). When you finish, export the map to JSON format using the option “File” -> “Export as…”, and overwrite the file “town.json”, located inside the “assets” folder.

Finally check the results using your browser and move the player to go to each part of the map. You will notice that the camera will follow you on each movement. Do not forget to fully refresh the contents of the browser by pressing Ctrl+F5, since the map and some other contents are usually cached by the browser. Also move the player and check that the map has been updated with the changes you have made.

Frames per second (FPS)

You can update the number of frames per second to choose the best renderization performance for your computer:

const MAX_FPS = 25;

Using the keyboard and the mouse

In this game we are using the keys A (left), S (down), D (right), W (up) to move the player on desktop computers and the joystick for mobile devices:

  let vX = 0, vY = 0;

  // Horizontal movement
  if (wsadKeys.A.isDown || joyStick.left) vX = -speed;
  else if (wsadKeys.D.isDown || joyStick.right) vX = speed;

  // Vertical movement
  if (wsadKeys.W.isDown || joyStick.up) vY = -speed;
  else if (wsadKeys.S.isDown || joyStick.down) vY = speed;

The mouse is used for shooting on desktop computers:

  let pointer = this.input.activePointer;
  if (pointer.isDown && this.sys.game.device.os.desktop) createBullet.call(this, pointer.worldX - player.x, pointer.worldY - player.y);

In case you do not have any mouse, you may use the cursor keys on desktop computers, or the second joystick on mobile devices, to shoot the bullets and to choose the shooting direction:

  let vX = 0, vY = 0;

  // Horizontal bullets
  if (cursors.left.isDown || joyStick2.left) vX = -speed;
  else if (cursors.right.isDown || joyStick2.right) vX = speed;

  // Vertical bullets
  if (cursors.up.isDown || joyStick2.up) vY = -speed;
  else if (cursors.down.isDown || joyStick2.down) vY = speed;

  if (vX || vY) createBullet.call(this, vX, vY);

Enjoy the game!

You may enjoy playing this wonderful game online here.

Phaser. Unit 5. Using tilemaps to build a shooter game in a huge world

A preview of the map

You can have a look at this preview of the whole map.

The source code and the assets

You can download this zip file to get the source code and all the assets.

Updating the map

As done previously, you can use the tile map editor to update the map. In case you have not done it yet, you can download the editor from this link and install it on your computer.

To update the map you have to go the assets folder and open the file “town.tmx” which contains the map of the example in this unit. Select a layer (on the right corner, at the top) and change anything you like (by adding or removing some objects to or from the map). When you finish, export the map to JSON format using the option “File” -> “Export as…”, and overwrite the file “town.json”, located inside the “assets” folder.

Since we are working with a lot of tilesets, in this case we are setting the collisions with all the objects in some of the layers: “Terrain_3”, “Buildings_1” and “Buildings_2”. The player and the enemies will collide with all the objects that you drawn in these three layers.

Finally check the results using your browser and move the player to go to each part of the map. You will notice that the camera will follow you on each movement. Do not forget to fully refresh the contents of the browser by pressing Ctrl+F5, since the map and some other contents are usually cached by the browser. Also move the player and check that the map has been updated with the changes you have made.

Renderization problems

Due to the large amount of resources this game is going to use from your computer, you might need to adjust a couple of parameters. It seems it works fine on Chrome browser, but if you are using another browser, or in case you find any problems, you can try updating the following parameters.

Phaser renderer

I have selected “Phaser.CANVAS” inside the phaser configuration settings, which seems to work much better on Chrome browser, but you may try “Phaser.AUTO” instead:

const config = {
  type: Phaser.AUTO
  ...
}

Frames per second (FPS)

You can also change the number of frames per second to choose the right renderization performance:

const MAX_FPS = 25;

Using the keyboard and the mouse

In this game we are using the keys A (left), S (down), D (right), W (up) to move the player on desktop computers and the joystick for mobile devices:

  let vX = 0, vY = 0;

  // Horizontal movement
  if (wsadKeys.A.isDown || joyStick.left) vX = -speed;
  else if (wsadKeys.D.isDown || joyStick.right) vX = speed;

  // Vertical movement
  if (wsadKeys.W.isDown || joyStick.up) vY = -speed;
  else if (wsadKeys.S.isDown || joyStick.down) vY = speed;

The mouse is used for shooting on desktop computers:

  let pointer = this.input.activePointer;
  if (pointer.isDown && this.sys.game.device.os.desktop) createBullet.call(this, pointer.worldX - player.x, pointer.worldY - player.y);

In case you do not have any mouse, you may use the cursor keys on desktop computers, or the second joystick on mobile devices, to shoot the bullets and to choose the shooting direction:

  let vX = 0, vY = 0;

  // Horizontal bullets
  if (cursors.left.isDown || joyStick2.left) vX = -speed;
  else if (cursors.right.isDown || joyStick2.right) vX = speed;

  // Vertical bullets
  if (cursors.up.isDown || joyStick2.up) vY = -speed;
  else if (cursors.down.isDown || joyStick2.down) vY = speed;

  if (vX || vY) createBullet.call(this, vX, vY);

Enjoy the game!

You may enjoy playing this wonderful game online here.

Phaser. Unit 4. Using tilemaps to build a pacman game

Introduction

In this unit we are going to use Phaser together with a tilemap editor to build a maze. You may find more information at the official page of the Tiled editor and you may also have a look at some interesting demonstrations at the Phaser’s examples page, and also performing some search on the latest examples.

HTML and CSS code

The first thing we should do is linking the Phaser library. We will use the last version at the time this unit has been written:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>

After that we are going to insert some CSS code to remove any margin and padding, and also to remove the scrollbars, so that all the window size is used when the game is started:

html, body {
    margin: 0px;
    padding: 0px;
    overflow: hidden;
    height: 100%;
} 

And finally we are going to use a single file to put all the code inside. This is going to be the basic structure of the “.html” file:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>First game with Phaser 3</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
    <style type="text/css">
        html, body {
            margin: 0px;
            padding: 0px;
            overflow: hidden;
            height: 100%;
        }
    </style>
</head>
<body>
    <script type="text/javascript">
        ...
    </script>
</body>
</html>

Constants

We are going to define some constants at the beginning of the code so that we can easily change the number of coins, fruits and ghosts that will be created:

const numCoins = 10;
const numFruits = 5;
const numGhosts = 5;

Phaser configuration and other variables

Following the constants we are going to create some variables so keep all the information about the game and also to start the initialization of Phaser:

const config = {
    type: Phaser.AUTO,
    pixelArt: true,
    scale: {
        mode: Phaser.Scale.ENVELOP,
        width: window.innerWidth,
        height: window.innerHeight
    },
    physics: {
        default: "arcade",
        arcade: {
            gravity: { y: 0 }
        }
    },
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};

const game = new Phaser.Game(config);
let speed = 60, vX = 0, vY = 0, prevX = 0, prevY = 0, prevTime = 0;
let belowLayer, worldLayer, aboveLayer, tileset, emptyTiles;
let map, player, cursors, bell, bell2, dead, hurt, timeout;
let joyStick = joyStick2 = { up: false, down: false, left: false, right: false };
let score = 0, lives = 5, scoreText = null, gameOver = false;

The assets (images, sprites and sounds)

We will need some images, sprites and sounds. You may use any assets you like, but we are also providing some examples so that you can easily start testing (they can also be downloaded here):

Images

Tileset containing almost every image needed to build the game
Restart button

Sounds and music

When the player gets a coin
When the user collects a fruit
When the user gets hurt by a ghost
When the user kills a ghost
Background music always playing

Loading all the assets

So that we can use all those images, sprites and music, they have to be loaded previously. That is the purpose of the “preload()” function. We will also include here the initialization of the joystick so that we can use it in another section of this unit.

Proposed exercise: Game initialization

Create a specific folder for your game in your domain to put all the code and assets inside. After that, create an “index.html” inside that folder with the code below, and also create an “assets” folder to put all the images and sounds, which can be downloaded here. After uploading everything to your domain, test the game using the url of that folder in your browser, and you should get a full black screen. Click on it and the background music should start playing.

You can see the result here (you can also look at the source code by pressing Ctrl+U).
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>First game with Phaser 3</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
    <style type="text/css">
        html,
        body {
            margin: 0px;
            padding: 0px;
            overflow: hidden;
            height: 100%;
        }
    </style>
</head>

<body>
    <div id="game-container"></div>
    <script type="text/javascript">
        const numCoins = 10;
        const numFruits = 5;
        const numGhosts = 5;

        const config = {
            type: Phaser.AUTO,
            pixelArt: true,
            scale: {
                mode: Phaser.Scale.ENVELOP,
                width: window.innerWidth,
                height: window.innerHeight
            },
            physics: {
                default: "arcade",
                arcade: {
                    gravity: { y: 0 }
                }
            },
            scene: {
                preload: preload,
                create: create,
                update: update
            }
        };

        const game = new Phaser.Game(config);
        let speed = 60, vX = 0, vY = 0, prevX = 0, prevY = 0, prevTime = 0;
        let belowLayer, worldLayer, aboveLayer, tileset, emptyTiles;
        let map, player, cursors, bell, bell2, dead, hurt, timeout;
        let joyStick = joyStick2 = { up: false, down: false, left: false, right: false };
        let score = 0, lives = 5, scoreText = null, gameOver = false;

        function preload() {
            this.load.image("restart", "assets/restart.png");
            this.load.image("tiles", "assets/tileset.png");
            this.load.tilemapTiledJSON("map", "assets/map.json");
            this.load.spritesheet("sprites", "assets/tileset.png", { frameWidth: 20, frameHeight: 20, margin: 1, spacing: 1 });

            this.load.audio("bell", "assets/ding.mp3");
            this.load.audio("bell2", "assets/ding2.mp3");
            this.load.audio("dead", "assets/dead.mp3");
            this.load.audio("hurt", "assets/hurt.mp3");
            this.load.audio("music", "assets/music.mp3");

            this.load.plugin('rexvirtualjoystickplugin', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/rexvirtualjoystickplugin.min.js', true);
        }

        function initSounds() {
            bell = this.sound.add('bell', { volume: 0.2 });
            bell2 = this.sound.add('bell2', { volume: 0.8 });
            dead = this.sound.add('dead', { volume: 0.7 });
            hurt = this.sound.add('hurt', { volume: 0.9 });
            this.sound.add('music', { volume: 0.4 }).play({ loop: -1 });
        }

        function create() {
            initSounds.call(this);
        }

        function update(time, delta) {
        }
    </script>
</body>

</html>

Proposed exercise: Using your own music

Download any other music file you like to use it as a background music and change the JavaScript code inside the “preload()” function to set the file name accordingly. Also in the “initSounds()” function you will find that you may change the volume of the music (volume:0.4). Try changing that value from 0.1 to 1 and you will notice that the volume of the music will also change.

You may find in the following sites some free images and sounds which you can download and use for your own game:

The maze

Once we have initialized Phaser and after loading all the assets, we can go ahead and display the maze on the screen.

Proposed exercise: Displaying the maze

Add the function “createWorld()” (provided below) to your code, and modify the calling function “create()” so that it is called when the game is started (also shown below). Finally test everything in your browser from your domain.

About the music, do not forget to click on the screen after loading the game (the music will not start playing unless the browser gets the focus). Also, you can see the result here (you can look at the source code by pressing Ctrl+U).
  1. This is the new function you have to insert in your code to create the world:
function createWorld() {
    map = this.make.tilemap({ key: "map" });

    // Parameters are the name you gave the tileset in Tiled and then the key of the tileset image in
    // Phaser's cache (i.e. the name you used in preload)
    tileset = map.addTilesetImage("tileset", "tiles");

    // Parameters: layer name (or index) from Tiled, tileset, x, y
    belowLayer = map.createLayer("Below Player", tileset, 0, 0).setDepth(1);
    worldLayer = map.createLayer("World", tileset, 0, 0).setDepth(2);
    aboveLayer = map.createLayer("Above Player", tileset, 0, 0).setDepth(3);

    worldLayer.setCollisionByProperty({ collides: true });

    this.physics.world.setBounds(0, 0, map.widthInPixels, map.heightInPixels);

    // Find empty tiles where new zombies, coins or health can be created
    emptyTiles = worldLayer.filterTiles(tile => (tile.index === -1));

    this.scale.resize(map.widthInPixels, map.heightInPixels).refresh();
}
  1. Do not forget to update the “create()” function to create the world. This is the only line you should add to that function:
function create() {
    ...
    createWorld.call(this);            
}

Proposed exercises: Modifying the maze

Download the tile map editor from this link and install it on your computer (if you don’t have installed it yet). Go to the assets folder and open the file “map.tmx” which contains the map of the example explained in this unit. Select the layer “World” (on the right corner, at the top) and change anything you like (by adding or removing some objects to or from the map). When you finish, export the map to JSON format using the option “File” -> “Export as…”, and overwrite the file “map.json”, located inside the “assets” folder. Finally check the results using your browser.

Do not forget to fully refresh the contents of the browser by pressing Ctrl+F5, since the map and some other contents are usually cached by the browser. Then you can check that the map has been updated with the changes you have made.

The animations (sprites)

We will use sprites to create the player and the animated objects. Let’s have a look at the the picture containing all the sprites:

Tileset containing all the sprites

We may appreciate that we have several groups of images. This way, we may easily create the animations:

  • The coin: Pictures from 0 to 7
  • The point: Picture 8
  • The ghosts:
    • Blue: Pictures from 17 to 20
    • Pink: Pictures from 21 to 24
    • Yellow: Pictures from 25 to 28
    • Green: Pictures from 34 to 37
    • Orange: Pictures from 38 to 41
    • Red: Pictures from 42 to 45
  • The player:
    • Left: Pictures from 51 to 58
    • Right: Pictures from 68 to 75
    • Down: Pictures from 85 to 92
    • Up: Pictures from 102 to 109
  • The fruits: Pictures 46, 47, 114, 143

Controlling the player’s animations

So that this game can be used in both desktop and mobile devices, we have to initialize both the cursor keys and the joystick. Once this is done, we must change the player’s velocity and the current animation when the user presses the keys or moves the joystick. In the next exercise we will first define the animations inside the “createAnimations()” function, and after that we will go ahead with the “createPlayer()” function, where we will display the player. We will finally include in the “update()” function some conditions to adjust the player’s speed and current animation.

Proposed exercise: Adding the player

Add the code below to your game and check both the player animations and movements. After that, look for another sprite and change the code accordingly to use your own sprite. Finally, change the values of the constant “speed” (at the top of your code) and check the results.

You can see the result here (you can look at the source code by pressing Ctrl+U). Also, as listed above, you may find many free assets in OpenGameArt and choose other sprites you like.
  1. These are the new functions you have to insert in your code to create the animations and initialize everything related to the player:
function newAnimation(name, rate, sprites, context) {
    context.anims.create({
        key: name, frameRate: rate, repeat: -1,
        frames: context.anims.generateFrameNumbers('sprites', sprites),
    });
}

function createAnimations() {
    // Player
    newAnimation("left", 10, { start: 51, end: 58 }, this);
    newAnimation("right", 10, { start: 68, end: 75 }, this);
    newAnimation("down", 10, { start: 85, end: 92 }, this);
    newAnimation("up", 10, { start: 102, end: 109 }, this);
    // Point
    newAnimation("point", 1, { start: 8, end: 8 }, this);
    // Coin
    newAnimation("spinning", 10, { start: 0, end: 7 }, this);
    // Fruits
    newAnimation("fruits", 2, { frames: [46, 47, 114, 143] }, this);
    // Ghosts
    newAnimation("blue", 5, { start: 17, end: 20 }, this);
    newAnimation("pink", 5, { start: 21, end: 24 }, this);
    newAnimation("yellow", 5, { start: 25, end: 28 }, this);
    newAnimation("green", 5, { start: 34, end: 37 }, this);
    newAnimation("orange", 5, { start: 38, end: 41 }, this);
    newAnimation("red", 5, { start: 42, end: 45 }, this);
}

function createPlayer() {
    // Object layers in Tiled let you embed extra info into a map - like a spawn point or custom
    // collision shapes. In the tmx file, there's an object layer with a point named "Spawn Point"
    const spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point");

    // Create a sprite with physics enabled via the physics system
    player = this.physics.add.sprite(spawnPoint.x, spawnPoint.y, "sprites").setDepth(2);

    // Watch the player and worldLayer for collisions, for the duration of the scene
    this.physics.add.collider(player, worldLayer);

    // Show points on empty tiles
    emptyTiles.forEach(tile => {
        let point = this.physics.add.sprite(tile.pixelX, tile.pixelY, 'sprites').setOrigin(0).anims.play('point', true);
        this.physics.add.overlap(player, point, collectPoint, null, this);
    });

    cursors = this.input.keyboard.createCursorKeys();

    // Show the joysticks only in mobile devices
    if (!this.sys.game.device.os.desktop) {
        joyStick = this.plugins.get('rexvirtualjoystickplugin').add(this, {
            x: 380, y: 420, radius: 20,
            base: this.add.circle(0, 0, 20, 0x888888).setAlpha(0.5).setDepth(4),
            thumb: this.add.circle(0, 0, 20, 0xcccccc).setAlpha(0.5).setDepth(4)
        }).on('update', update, this);

        joyStick2 = this.plugins.get('rexvirtualjoystickplugin').add(this, {
            x: 40, y: 420, radius: 20,
            base: this.add.circle(0, 0, 20, 0x888888).setAlpha(0.5).setDepth(4),
            thumb: this.add.circle(0, 0, 20, 0xcccccc).setAlpha(0.5).setDepth(4)
        }).on('update', update, this);
    }
}

function showScore() {
    if (!scoreText) scoreText = this.add.text(map.widthInPixels/2, 4, '', { fontSize: (18) + 'px', fill: '#FFF' }).setOrigin(0.5, 0).setScrollFactor(0).setDepth(4);
    scoreText.setText('Score:' + score + ' / Lives:' + lives);
}

function collectPoint(player, point) {
    bell.play();
    point.destroy();

    score += 5;
    showScore();
}
  1. Also you have to insert some code inside the “update()” function to respond to the cursor keys and the joystick:
function update(time, delta) {
    if (gameOver) return;

    // Choose the right animation depending on the player's direction
    if (player.x > prevX) player.anims.play('right', true);
    else if (player.x < prevX) player.anims.play('left', true);
    else if (player.y > prevY) player.anims.play('down', true);
    else if (player.y < prevY) player.anims.play('up', true);

    // If the player goes outside the map
    if (player.x < 0) player.x = map.widthInPixels - 20;
    else if (player.x > map.widthInPixels) player.x = 0;

    let key = player.anims.currentAnim.key;
    let blocked = player.body.blocked;

    // Reset the velocity when the player touches a wall
    if (key == 'right' && blocked.right || key == 'left' && blocked.left) vX = 0;
    if (key == 'up' && blocked.up || key == 'down' && blocked.down) vY = 0;

    // Horizontal movement
    if (cursors.left.isDown || joyStick.left || joyStick2.left) vX = -speed;
    else if (cursors.right.isDown || joyStick.right || joyStick2.right) vX = speed;

    // Vertical movement
    if (cursors.up.isDown || joyStick.up || joyStick2.up) vY = -speed;
    else if (cursors.down.isDown || joyStick.down || joyStick2.down) vY = speed;

    player.setVelocity(vX, vY);

    if ((time - prevTime) > 100) {
        prevX = player.x;
        prevY = player.y;
        prevTime = time;
    }
}
  1. And finally do not forget to update the “create()” function to create the animations, the player and the score. You just have to insert some new lines:
function create() {
    ...
    createAnimations.call(this); 
    createPlayer.call(this);
    showScore.call(this);
}

Coins, fruits, and ghosts

We will create many different objects at once using the images from the same tileset. We will just use a loop to print as many objects as we like.

Proposed exercises: Adding the coins

Add the code below to your file and check the results. After that, change the value of the constant “numCoins” (at the top of your code) to check how easily you can customize your game. Refresh the page several times and you will notice that the generated coins appear at random positions. Finally, look for another sprite sheet (it may be a coin or any other object you like) and change the code accordingly to use your own animated object.

You can see the result here (you can look at the source code by pressing Ctrl+U). Also, as listed above, you may find many free assets in OpenGameArt and choose the images you like.
function newObject(animation, context) {
    const tile = Phaser.Utils.Array.GetRandom(emptyTiles);
    return context.physics.add.sprite(tile.pixelX, tile.pixelY, 'sprites').setOrigin(0).anims.play(animation, true);
}

function createCoin() {
    let coin = newObject('spinning', this);
    coin.body.setAllowGravity(false);
    this.physics.add.overlap(player, coin, collectCoin, null, this);
}

function protect(color) {
    player.setTint(color);
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => { timeout = false; player.clearTint() }, 5000);
}

function collectCoin(player, coin) {
    bell2.play();
    coin.destroy();
    createCoin.call(this);

    score += 10;
    showScore();

    protect(0xFFFF00);
}
function create() {
    ...
    for (i = 0; i < numCoins; i++) setTimeout(() => createCoin.call(this), Phaser.Math.Between(0, 5000));
}

Proposed exercises: Adding the fruits

Add the code below to your game and check the results. After that, change the value of the constants “numFruits” (at the top of your code) to check how easily you can customize your game. Finally, look for another sprite and change the code accordingly to use your own object to provide the player with extra lives and points.

You may click here to see how the game looks like. As listed above, you may find many free assets in OpenGameArt.
function createFruits() {
    let fruits = newObject('fruits', this);
    fruits.body.setAllowGravity(false);
    this.physics.add.overlap(player, fruits, collectFruits, null, this);
}

function collectFruits(player, fruits) {
    bell2.play();
    fruits.destroy();
    createFruits.call(this);

    score += 15;
    showScore();
}
function create() {
    ...
    for (i = 0; i < numFruits; i++) setTimeout(() => createFruits.call(this), Phaser.Math.Between(0, 5000));
}

Proposed exercises: Adding the ghosts

Add the code below to your game and check the results. After that, change the value of the constants “numGhosts” (at the top of your code) to check how easily you can customize your game. Finally, look for another sprite and change the code accordingly to display your own ghosts.

You may click here to see how the game looks like. As listed above, you may find many free assets in OpenGameArt.
function newGhost(animation, context) {
    const spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point 2");
    return context.physics.add.sprite(spawnPoint.x, spawnPoint.y, 'sprites').setOrigin(0).anims.play(animation, true).setDepth(2);
}

function createGhost() {
    const colors = ['blue', 'pink', 'yellow', 'green', 'orange', 'red'];
    let ghost = newGhost(colors[Phaser.Math.Between(0, colors.length - 1)], this).setCollideWorldBounds(true).setBounce(1);
    ghost.setVelocity(Phaser.Math.Between(speed / 3, speed / 2), Phaser.Math.Between(-speed / 2, -speed / 3)).body.setAllowGravity(false);
    this.physics.add.collider(ghost, worldLayer);
    this.physics.add.overlap(player, ghost, hitGhost, null, this);
}

function hitGhost(player, ghost) {
    // If the player is already hurt, it cannot be hurt again for a while
    if (player.tintTopLeft == 0xFF00FF) return;

    if (timeout) {
        score += 15;
        dead.play();
    }
    else {
        lives--;
        hurt.play();
        protect(0xFF00FF);
    }

    showScore();

    if (lives == 0) {
        this.physics.pause();
        gameOver = true;
        this.add.image(210, 230, 'restart').setScale(2).setScrollFactor(0).setDepth(4)
            .setInteractive().on('pointerdown', () => location.reload());
    }
    else {
        ghost.destroy();
        createGhost.call(this);
    }
}  
function create() {
    ...
    for (i = 0; i < numGhosts; i++) setTimeout(() => createGhost.call(this), Phaser.Math.Between(0, 15000)); 
}

Enjoy the game!

You may enjoy playing this wonderful game online here.

Phaser. Unit 3. Using tilemaps

Introduction

In this unit we are going to use Phaser together with a tilemap editor to build our world. You may find more information at the official page of the Tiled editor and you may also have a look at some interesting demonstrations at the Phaser’s examples page, and also performing some search on the latest examples.

HTML and CSS code

The first thing we should do is linking the Phaser library. We will use the last version at the time this unit has been written:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>

After that we are going to insert some CSS code to remove any margin and padding, and also to remove the scrollbars, so that all the window size is used when the game is started:

html, body {
    margin: 0px;
    padding: 0px;
    overflow: hidden;
    height: 100%;
} 

And finally we are going to use a single file to put all the code inside. This is going to be the basic structure of the “.html” file:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>First game with Phaser 3</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
    <style type="text/css">
        html, body {
            margin: 0px;
            padding: 0px;
            overflow: hidden;
            height: 100%;
        }
    </style>
</head>
<body>
    <script type="text/javascript">
        ...
    </script>
</body>
</html>

Constants

We are going to define some constants at the beginning of the code so that we can easily change the game appearance, difficulty, and some other parameters:

const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
const numCoins = 15;
const numHealth = 5;
const numPower = 5;
const numZombies = 10;

Phaser configuration and other variables

Following the constants we are going to create some variables so keep all the information about the game and also to start the initialization of Phaser:

const config = {
    type: Phaser.AUTO,
    width: screenWidth,
    height: screenHeight,
    physics: {
        default: "arcade",
        arcade: {
            debug: false,
            gravity: { y: 0 }
        }
    },
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};

const game = new Phaser.Game(config);
let worldWidth, worldHeight, joystickSize = 40, scale = 1.75, speed = 175;
let belowLayer, worldLayer, aboveLayer, tileset, emptyTiles;
let map, player, cursors, bell, bell2, dead, timeout;
let joyStick = joyStick2 = { up: false, down: false, left: false, right: false };
let score = 0, lives = 5, scoreText = null, gameOver = false;

The assets (images, sprites and sounds)

We will need some images, sprites and sounds. You may use any assets you like, but we are also providing some examples so that you can easily start testing (they can also be downloaded here):

Images

Tileset to be used to build the map
Restart button

Sprites (player and animated objects)

Player’s sprite sheet
Enemy’s sprite sheet
Sprite sheet to animate the coins
Sprite sheet to animate the health objects
Sprite sheet to animate the power object

Sounds and music

When the player gets a coin
When the user collects a health object
When the user gets hurt by a zombie
Background music always playing

Loading all the assets

So that we can use all those images, sprites and music, they have to be loaded previously. That is the purpose of the “preload()” function. We will also include here the initialization of the joystick so that we can use it in another section of this unit.

Proposed exercise: Game initialization

Create a specific folder for your game in your domain to put all the code and assets inside. After that, create an “index.html” inside that folder with the code below, and also create an “assets” folder to put all the images and sounds, which can be downloaded here. After uploading everything to your domain, test the game using the url of that folder in your browser, and you should get a full black screen. Click on it and the background music should start playing.

You can see the result here (you can also look at the source code by pressing Ctrl+U).
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>First game with Phaser 3</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
    <style type="text/css">
        html,
        body {
            margin: 0px;
            padding: 0px;
            overflow: hidden;
            height: 100%;
        }
    </style>
</head>
<body>
    <div id="game-container"></div>
    <script type="text/javascript">
        const screenWidth = window.innerWidth;
        const screenHeight = window.innerHeight;
        const numCoins = 15;
        const numHealth = 5;
        const numPower = 5;
        const numZombies = 10;

        const config = {
            type: Phaser.AUTO,
            width: screenWidth,
            height: screenHeight,
            physics: {
                default: "arcade",
                arcade: {
                    debug: false,
                    gravity: { y: 0 }
                }
            },
            scene: {
                preload: preload,
                create: create,
                update: update
            }
        };

        const game = new Phaser.Game(config);
        let worldWidth, worldHeight, joystickSize = 40, scale = 1.75, speed = 175;
        let belowLayer, worldLayer, aboveLayer, tileset, emptyTiles;
        let map, player, cursors, bell, bell2, dead, timeout;
        let joyStick = joyStick2 = { up: false, down: false, left: false, right: false };
        let score = 0, lives = 5, scoreText = null, gameOver = false;

        function preload() {
            this.load.image("restart", "assets/restart.png");
            this.load.image("tiles", "assets/tileset.png");
            this.load.tilemapTiledJSON("map", "assets/town.json");
            this.load.spritesheet("player", "assets/lidia.png", { frameWidth: 64, frameHeight: 64 });
            this.load.spritesheet("coin", "assets/coin.png", { frameWidth: 32, frameHeight: 32 });
            this.load.spritesheet("health", "assets/health.png", { frameWidth: 32, frameHeight: 32 });
            this.load.spritesheet("power", "assets/power.png", { frameWidth: 64, frameHeight: 64 });
            this.load.spritesheet("zombie", "assets/zombie.png", { frameWidth: 48, frameHeight: 64 });

            this.load.audio("bell", "assets/ding.mp3");
            this.load.audio("bell2", "assets/ding2.mp3");
            this.load.audio("dead", "assets/dead.mp3");
            this.load.audio("music", "assets/music.mp3");

            this.load.plugin('rexvirtualjoystickplugin', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/rexvirtualjoystickplugin.min.js', true);
        }

        function initSounds() {
            bell = this.sound.add('bell', { volume: 0.2 });
            bell2 = this.sound.add('bell2', { volume: 0.2 });
            dead = this.sound.add('dead', { volume: 0.7 });
            this.sound.add('music', { volume: 0.4 }).play({ loop: -1 });
        }

        function create() {
            initSounds.call(this);
        }

        function update(time, delta) {
        }
    </script>
</body>
</html>

Proposed exercise: Using your own music

Download any other music file you like to use it as a background music and change the JavaScript code inside the “preload()” function to set the file name accordingly. Also in the “initSounds()” function you will find that you may change the volume of the music (volume:0.4). Try changing that value from 0.1 to 1 and you will notice that the volume of the music will also change.

You may find in the following sites some free images and sounds which you can download and use for your own game:

The map

Once we have initialized Phaser and after loading all the assets, we can go ahead and display the map on the screen.

Proposed exercise: Displaying the map

Add the function “createWorld()” (provided below) to your code, and modify the calling function “create()” so that it is called when the game is started (also shown below). Finally test everything in your browser from your domain.

About the music, do not forget to click on the screen after loading the game (the music will not start playing unless the browser gets the focus). Also, you can see the result here (you can look at the source code by pressing Ctrl+U).
  1. This is the new function you have to insert in your code to create the world:
function createWorld() {
    map = this.make.tilemap({ key: "map" });

    scale *= Math.max(screenWidth/map.widthInPixels, screenHeight/map.heightInPixels);

    worldWidth = map.widthInPixels * scale;
    worldHeight = map.heightInPixels * scale;
    speed *= scale;
    joystickSize *= scale;

    // Parameters are the name you gave the tileset in Tiled and then the key of the tileset image in
    // Phaser's cache (i.e. the name you used in preload)
    tileset = map.addTilesetImage("tileset", "tiles");

    // Parameters: layer name (or index) from Tiled, tileset, x, y
    belowLayer = map.createLayer("Below Player", tileset, 0, 0).setScale(scale).setDepth(1);
    worldLayer = map.createLayer("World", tileset, 0, 0).setScale(scale).setDepth(2);
    aboveLayer = map.createLayer("Above Player", tileset, 0, 0).setScale(scale).setDepth(3);

    worldLayer.setCollisionByProperty({ collides: true });

    // Find empty tiles where new zombies, coins or health can be created
    emptyTiles = worldLayer.filterTiles(tile => (tile.index === -1 || !tile.collides));
}
  1. Do not forget to update the “create()” function to create the world. This is the only line you should add to that function:
function create() {
    ...
    createWorld.call(this);            
}

Proposed exercise: Scaling the map

Change the value of the “scale” variable (at the top of your code) and refresh the map in your browser to check that it is scaled accordingly. You can set values such as “scale = 0.5” and you will notice that the map just fits half the screen. Set also higher values and check the results again.

The animations (sprites)

We will use sprites to create the player and the animated objects. For example, let’s have a look at the player:

Player’s sprite sheet

We may appreciate that we have 36 pictures divided into 4 different groups. This way, we may easily create all four animations:

  • Up: Pictures from 0 to 8
  • Left: Pictures from 9 to 17
  • Down: Pictures from 18 to 26
  • Right: Pictures from 27 to 35

Controlling the player’s animations

So that this game can be used in both desktop and mobile devices, we have to initialize both the cursor keys and the joystick. Once this is done, we must change the player’s velocity and the current animation when the user presses the keys or moves the joystick. In the next exercise we will first define the animations inside the “createAnimations()” function, and after that we will go ahead with the “createPlayer()” function, where we will display the player. We will finally include in the “update()” function some conditions to adjust the player’s speed and current animation.

Proposed exercise: Adding the player

Add the code below to your game and check both the player animations and movements. After that, look for another sprite and change the code accordingly to use your own sprite. Finally, change the values of the constant “speed” (at the top of your code) and check the results. You will also notice that the camera will follow you on each player’s movement.

You can see the result here (you can look at the source code by pressing Ctrl+U). Also, as listed above, you may find many free assets in OpenGameArt and choose other sprites you like.
  1. These are the new functions you have to insert in your code to create the animations and initialize everything related to the player:
function createAnimations() {
    const anims = this.anims;
    // Player
    anims.create({
        key: "left", frameRate: 10, repeat: -1,
        frames: this.anims.generateFrameNumbers('player', { start: 9, end: 17 }),
    });
    anims.create({
        key: "right", frameRate: 10, repeat: -1,
        frames: this.anims.generateFrameNumbers('player', { start: 27, end: 35 }),
    });
    anims.create({
        key: "up", frameRate: 10, repeat: -1,
        frames: this.anims.generateFrameNumbers('player', { start: 0, end: 8 }),
    });
    anims.create({
        key: "down", frameRate: 10, repeat: -1,
        frames: this.anims.generateFrameNumbers('player', { start: 18, end: 26 }),
    });
    anims.create({
        key: "waiting", frameRate: 3, repeat: -1,
        frames: this.anims.generateFrameNumbers('player', { start: 19, end: 21 }),
    });
    // Coin
    anims.create({
        key: "flying", frameRate: 10, repeat: -1,
        frames: this.anims.generateFrameNumbers('coin', { start: 0, end: 7 }),
    });
    // Health
    anims.create({
        key: "floating", frameRate: 5, repeat: -1,
        frames: this.anims.generateFrameNumbers('health', { start: 0, end: 3 }),
    });
    // Power
    anims.create({
        key: "growing", frameRate: 5, repeat: -1,
        frames: this.anims.generateFrameNumbers('power', { start: 0, end: 3 }),
    });
    // Zombie
    anims.create({
        key: "moving", frameRate: 5, repeat: -1,
        frames: this.anims.generateFrameNumbers('zombie', { start: 0, end: 11 }),
    });
}

function createPlayer() {
    // Object layers in Tiled let you embed extra info into a map - like a spawn point or custom
    // collision shapes. In the tmx file, there's an object layer with a point named "Spawn Point"
    const spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point");

    // Create a sprite with physics enabled via the physics system. The image used for the sprite has
    // a bit of whitespace, so I'm using setSize & setOffset to control the size of the player's body
    player = this.physics.add
        .sprite(spawnPoint.x * scale, spawnPoint.y * scale, "player")
        .setSize(16, 32).setOffset(24, 32).setScale(scale)
        .setCollideWorldBounds(true).setDepth(2);

    // Watch the player and worldLayer for collisions, for the duration of the scene
    this.physics.add.collider(player, worldLayer);

    const camera = this.cameras.main;
    const world = this.physics.world;
    camera.startFollow(player);
    camera.setBounds(0, 0, worldWidth, worldHeight);
    world.setBounds(0, 0, worldWidth, worldHeight);

    cursors = this.input.keyboard.createCursorKeys();

    // Do not show the joysticks on desktop devices
    if (!this.sys.game.device.os.desktop) {
        joyStick = this.plugins.get('rexvirtualjoystickplugin').add(this, {
            x: screenWidth - joystickSize * 2,
            y: screenHeight - joystickSize * 2,
            radius: joystickSize,
            base: this.add.circle(0, 0, joystickSize, 0x888888).setAlpha(0.5).setDepth(4),
            thumb: this.add.circle(0, 0, joystickSize, 0xcccccc).setAlpha(0.5).setDepth(4)
        }).on('update', update, this);

        joyStick2 = this.plugins.get('rexvirtualjoystickplugin').add(this, {
            x: joystickSize * 2,
            y: screenHeight - joystickSize * 2,
            radius: joystickSize,
            base: this.add.circle(0, 0, joystickSize, 0x888888).setAlpha(0.5).setDepth(4),
            thumb: this.add.circle(0, 0, joystickSize, 0xcccccc).setAlpha(0.5).setDepth(4)
        }).on('update', update, this);
    }          
}
  1. Also you have to insert some code inside the “update()” function to respond to the cursor keys and the joystick:
function update(time, delta) {
    if (gameOver) return;

    let vX = 0, vY = 0;

    // Horizontal movement
    if (cursors.left.isDown || joyStick.left || joyStick2.left) vX = -speed;
    else if (cursors.right.isDown || joyStick.right || joyStick2.right) vX = speed;

    // Vertical movement
    if (cursors.up.isDown || joyStick.up || joyStick2.up) vY = -speed;
    else if (cursors.down.isDown || joyStick.down || joyStick2.down) vY = speed;

    // Set and normalize the velocity so that player can't move faster along a diagonal
    player.setVelocity(vX, vY).body.velocity.normalize().scale(speed);

    // Choose the right player's animation
    if (vX < 0) player.anims.play('left', true);
    else if (vX > 0) player.anims.play('right', true);
    else if (vY < 0) player.anims.play('up', true);
    else if (vY > 0) player.anims.play('down', true);
    else player.anims.play('waiting', true);            
}
  1. And finally do not forget to update the “create()” function to create the animations and the player. You just have to insert a couple of new lines:
function create() {
    ...
    createAnimations.call(this); 
    createPlayer.call(this);
}

Proposed exercises: Modifying the map

Download the tile map editor from this link and install it on your computer. Go to the assets folder and open the file “town.tmx” which contains the map of the example explained in this unit. Select the layer “World” (on the right corner, at the top) and change anything you like (by adding or removing some objects to or from the map). When you finish, export the map to JSON format using the option “File” -> “Export as…”, and overwrite the file “town.json”, located inside the “assets” folder. Finally check the results using your browser and move the player to go to each part of the map. You will notice again that the camera will follow you on each movement.

Do not forget to fully refresh the contents of the browser by pressing Ctrl+F5, since the map and some other contents are usually cached by the browser. Also move the player and check that the map has been updated with the changes you have made.

Score, coins, power, health, and enemies

We will create many different objects at once using the same images. We will just change the size of the objects by scaling them, and we will use a loop to print as many objects as we like.

Proposed exercise: Adding the score

Using the global variables, we may easily show both the score and number of remaining lives. Add the code below to your game and check the results. After that, change the value of the variable “lives” (at the top of your code) to check how easily you can customize that text.

You can see the result here (you can look at the source code by pressing Ctrl+U).
function showScore() {
    if (!scoreText) {
        scoreText = this.add.text(screenWidth/2, 16, '', { fontSize: (32 * scale) + 'px', fill: '#FFF' });
        scoreText.setShadow(3, 3, 'rgba(0,0,0,1)', 3).setOrigin(0.5, 0).setScrollFactor(0).setDepth(4);
    }
    scoreText.setText('Score:' + score + ' / Lives:' + lives);
}
function create() {
    ...
    showScore.call(this);
}

Proposed exercises: Adding the coins

Add the code below to your file and check the results. After that, change the value of the constant “numCoins” (at the top of your code) to check how easily you can customize your game. Refresh the page several times and you will notice that the generated coins have random sizes and they appear at random positions. Finally, look for another sprite sheet (it may be a coin or any other object you like) and change the code accordingly to use your own animated object.

You can see the result here (you can look at the source code by pressing Ctrl+U). Also, as listed above, you may find many free assets in OpenGameArt and choose the images you like.
function newObject(type, animation, context) {
    let tile = Phaser.Utils.Array.GetRandom(emptyTiles);
    return context.physics.add.sprite(tile.pixelX * scale, tile.pixelY * scale, type).anims.play(animation, true).setDepth(2);
}

function createCoin() {
    const v = Phaser.Math.Between(speed / 10, speed / 5);
    let coin = newObject('coin', 'flying', this).setSize(16, 32).setScale(1.5 * scale).setBounce(1).setVelocity(v, 0);
    coin.body.setAllowGravity(false).setCollideWorldBounds(true);
    this.physics.add.collider(coin, worldLayer);
    this.physics.add.overlap(player, coin, collectCoin, null, this);
} 

function collectCoin(player, coin) {
    bell.play();
    coin.destroy();
    createCoin.call(this);

    score += 10;
    showScore();
}
function create() {
    ...
    for (i = 0; i < numCoins; i++) setTimeout(() => createCoin.call(this), Phaser.Math.Between(0, 5000));
}

Proposed exercises: Adding the health

Add the code below to your game and check the results. After that, change the value of the constants “numHealth” (at the top of your code) to check how easily you can customize your game. Finally, look for another sprite and change the code accordingly to use your own object to provide the player with extra lives.

You may click here to see how the game looks like. As listed above, you may find many free assets in OpenGameArt.
function createHealth() {
    let health = newObject('health', 'floating', this).setSize(16, 32).setScale(1.2 * scale);
    health.body.setAllowGravity(false);
    this.physics.add.overlap(player, health, collectHealth, null, this);
}

function collectHealth(player, health) {
    bell2.play();
    health.destroy();
    createHealth.call(this);

    lives++;
    showScore();
}
function create() {
    ...
    for (i = 0; i < numHealth; i++) setTimeout(() => createHealth.call(this), Phaser.Math.Between(0, 5000));
}

Proposed exercises: Adding the power

Add the code below to your game and check the results. After that, change the value of the constants “numPower” (at the top of your code) to check how easily you can customize your game. Finally, look for another sprite and change the code accordingly to use your own power object.

You may click here to see how the game looks like. As listed above, you may find many free assets in OpenGameArt.
function createPower() {
    let power = newObject('power', 'growing', this).setSize(32, 64).setScale(scale/2);
    power.body.setAllowGravity(false);
    this.physics.add.overlap(player, power, collectPower, null, this);
}

function protect(color) {
    player.setTint(color);
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => { timeout = false; player.clearTint() }, 5000);
}

function collectPower(player, power) {
    bell2.play();
    power.destroy();
    createPower.call(this);

    protect(0xFFFF00);
}
function create() {
    ...
    for (i = 0; i < numPower; i++) setTimeout(() => createPower.call(this), Phaser.Math.Between(0, 5000)); 
}

Proposed exercises: Adding the enemies

Add the code below to your game and check the results. After that, change the value of the constants “numZombies” (at the top of your code) to check how easily you can customize your game. Finally, look for another sprite and change the code accordingly to display your own enemies.

You may click here to see how the game looks like. As listed above, you may find many free assets in OpenGameArt.
function createZombie() {
    const v = Phaser.Math.Between(-speed / 4, -speed / 3);
    let zombie = newObject('zombie', 'moving', this).setSize(16, 32).setOffset(16, 32).setScale(1.2 * scale).setBounce(1).setVelocity(0, v);
    zombie.body.setAllowGravity(false).setCollideWorldBounds(true);
    this.physics.add.collider(zombie, worldLayer);
    this.physics.add.overlap(player, zombie, hitZombie, null, this);
}

function hitZombie(player, zombie) {
    // If the player is already hurt, it cannot be hurt again for a while
    if (player.tintTopLeft == 0xFF0000) return;

    if (timeout) {
        score += 15;
        showScore();
    }
    else {
        protect(0xFF0000);
        lives--;
    }

    dead.play();
    showScore();

    if (lives == 0) {
        this.physics.pause();
        gameOver = true;
        this.add.image(screenWidth / 2, screenHeight / 2, 'restart')
            .setScale(4).setScrollFactor(0).setDepth(4)
            .setInteractive().on('pointerdown', () => location.reload());
    }
    else {
        zombie.destroy();
        createZombie.call(this);
    }
} 
function create() {
    ...
    for (i = 0; i < numZombies; i++) setTimeout(() => createZombie.call(this), Phaser.Math.Between(0, 5000)); 
}

Enjoy the game!

You may enjoy playing this wonderful game online here.

Phaser. Unit 2. A bigger world

Introduction

In this unit we are going to expand our world with a whole different scene. We will use the game developed in the previous unit so that by modifying or adding some code, we will have double the entertainment than before 🙂

The new assets

These are the new assets that we will use to build the next scene (you can download all the assets here):

An arrow to jump to the next scene
Flying stars that will increase the score
Dead trees
Flying dragons trying to kill the player
Sound to be played after collecting a star

Expanding the world

The first thing we are going to do is creating a wider sky and platform, using new colors for the new scene.

Proposed exercise: Expanding sky and platform

First check that you have a working game on your domain. After that, follow the instructions below, and upload the new code and the new assets to your domain (you can get all the assets from this link). Finally, check the results in your browser.

Now you should have an arrow on the right corner, at the top of the screen, so that you can easily jump to the next scene. Your new game should look now like this one.
  1. Insert at the top of your code a new constant with the new size of the world (double than before):
const maxWorldWidth = worldWidth * 2;
  1. Modify the function “createWorld()” so that both the camera and world bounds use this new constant, and also insert the new sky and the new platform:
function createWorld() {
    this.cameras.main.setBounds(0, 0, maxWorldWidth, screenHeight);
    this.physics.world.setBounds(0, 0, maxWorldWidth, screenHeight);

    ...

    // New sky
    this.add.rectangle(worldWidth, 0, worldWidth, screenHeight, 0xff6600).setOrigin(0);

    // New platform
    platform2 = this.add.rectangle(worldWidth, screenHeight, worldWidth, platformHeight, 0x006600).setOrigin(0, 1);
    this.physics.add.existing(platform2);
    platform2.body.setCollideWorldBounds(true);
}
  1. Activate the collision between the player and the new platform. This can be done with a single line inside the “createPlayer()” function:
function createPlayer() {
    ...
    this.physics.add.collider(player, platform2);
}
  1. Load the new assets (the button to jump to the next scene, the dead tree, the stars to be collected, the sound to be played when collecting the stars, and the sprite sheet of the flying dragon) inside the “preload()” function:
function preload() {
    ...
    this.load.image('arrow', 'assets/arrow.png');
    this.load.image('tree2', 'assets/tree2.png');
    this.load.image('star', 'assets/star.png');
    this.load.audio('bell2', 'assets/ding2.mp3');
    this.load.spritesheet('dragon', 'assets/dragon.png', { frameWidth: 144, frameHeight: 128 });
}
  1. Insert a new function to display the arrow to jump to the next scene:
function showArrow() {
    this.add.image(screenWidth, 0, 'arrow').setOrigin(1, 0).setScale(2).setScrollFactor(0).setInteractive().on('pointerdown', () => player.x = (player.x + worldWidth) % maxWorldWidth);
}
  1. And finally you must execute that code, just by adding a new line to the “create()” function:
function create() {
    ...            
    showArrow.call(this);
}

Proposed exercise: A big tree between the two scenes

To achieve a better transition between one scene to the other, we will put a dead tree in the middle. You just need to insert one line of code at the end of the “createWorld()” function:

You can see the expected result here.
function createWorld() {
    ...
    this.add.image(worldWidth, 0, 'tree2').setOrigin(0.5, 0).setDisplaySize(screenWidth/5, screenHeight);
}

Proposed exercise: Changing the new scene’s colors

Choose any colors you like for the new scene, and change the colors of both the sky and the platform. You only have to change a couple of values inside the function “createWorld()”.

You can use a color picker to get the values of the new colors in hexadecimal notation.

More trees, more fires, more clouds…

Now we have to show the fires and the clouds in the new scene. And to change even more the appearance of the new scene, we will not change only the colors but also the background images, by using new trees.

Proposed exercise: More trees

Update the constant with the number of trees to be displayed, and also, add a new line to the “createTree()” function so that lots of new trees are shown inside the new scene.

You can see the expected result here.
  1. Update the constant to show as many trees as you like:
const numTrees = 100;
  1. Create new trees in the new scene (you will notice that these trees are shown at ‘x+worldWidth’ position, and also the ‘scale’ is adjusted for the new image):
function createTree() {
    ...
    this.add.image(x + worldWidth, y, 'tree2').setOrigin(1).setScale(scale * 2);
}

Proposed exercise: More fires

Update the constant with the number of fires to be displayed, and also, update the “createFire()” function to use the constant ‘maxWorldWidth’ as the limit to generate the fires.

You can see the expected result here.
  1. Update the constant to show as many fires as you like:
const numFires = 50;
  1. Update the following line to use ‘maxWorldWidth’ constant instead:
function createFire() {
    const x = Phaser.Math.Between(screenWidth/2, maxWorldWidth);
    ...
}

Proposed exercise: More clouds

Update the constant with the number of clouds to be displayed, and also, update the “createClouds()” function to use the constant “maxWorldWidth” as the limit to generate the clouds.

You can see the expected result here.
  1. Update the constant to show as many clouds as you like:
const numClouds = 100;
  1. Update the following line to use “maxWorldWidth” constant instead:
function createCloud() {
    const x = Phaser.Math.Between(0, maxWorldWidth);
    ...
}

The new moving objects (stars and dragons)

Now we will add to the new scene a couple of moving objects: stars and dragons. After collecting a star, a new sound will be played, and as a new feature, we will be protected for several seconds so that in case we hit a killing object, we remain unaffected. About the dragons, we may get the same behaviour as if we hit the bombs or the fires.

Proposed exercise: Adding the stars

Add or modify all the required code that will be in charge of creating the stars in the new scene and that will also handle the events related to the new objects (playing sound, increasing lives, etc.). You will have to follow the steps below.

You can see the expected result here.
  1. Create the new variables (the number of stars to be displayed, and the timeout to be protected when you are hit by the bombs, the fires, or the dragons):
const numStars = 50;
var timeout = false;
  1. Initialize the sound to be played when collecting the stars, just by adding a single line inside the existing “initSounds()” function. You might also need to adjust the volume by using the right value from 0.1 to 1:
function initSounds() {
    bell2 = this.sound.add('bell2', { volume: 0.2 });
    ...
}
  1. Add the new functions to your code. The “collectStar()” function will print each star, the “protect()” function will activate a player’s protection to avoid being killed for a while, and the “collectStar()” function will contain the code be executed each time you collect a star:
function createStar() {
    const x = Phaser.Math.Between(worldWidth, worldWidth * 2);
    const vX = Phaser.Math.Between(-velocityX, velocityX);
    const vY = Phaser.Math.Between(velocityY/2, velocityY);
    let star = this.physics.add.image(x, 0, 'star').setScale(0.5).setBounce(1).setCollideWorldBounds(true).setVelocity(vX, vY);
    star.body.setAllowGravity(false);
    this.physics.add.collider(star, platform);
    this.physics.add.collider(star, platform2);
    this.physics.add.collider(player, star, collectStar, null, this);
}

function protect(color) {
    player.setTint(color);
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => { timeout=false; player.clearTint() }, 3000);
}

function collectStar(player, star) {
    bell2.play();
    star.destroy();
    createStar.call(this);

    protect(0xFFFF00);

    score += 10;
    if (score % 100 == 0) lives++;
    showScore();
}
  1. And finally insert the loop to create all the stars at once inside the “create()” function:
funtion create() {
    ...
    for (i = 0; i < numStars; i++) createStar.call(this);
}

Proposed exercise: Adding the dragons

Add or modify all the required code that will be in charge of creating the dragons in the new scene and that will also handle the events related to them (playing sound, decreasing lives, etc.). You will have to follow the steps below.

You can see the expected result here.
  1. Insert at the top of your code a new constat to set the number of dragons you would like to print:
const numDragons = 25;
  1. Add the “flying” animation to the function “createAnimations()”:
function createAnimations() {
    ...
    // Dragon
    this.anims.create({
        key: 'flying',
        frames: this.anims.generateFrameNumbers('dragon', { start: 0, end: 11 }),
        frameRate: 5,
        repeat: -1
    });
}
  1. Add the new functions “createDragon()” and “hitDragon()” to your code, to display each dragon and to perform any action we want to be done after being hit:
function createDragon() {
    const x = Phaser.Math.Between(worldWidth, worldWidth * 2);
    const y = Phaser.Math.Between(0, screenHeight - platformHeight);
    const v = Phaser.Math.Between(velocityY/2, velocityY);
    let dragon = this.physics.add.sprite(x, y, 'dragon').setOrigin(1).setSize(72, 64).setScale(2).anims.play('flying', true).setBounce(1).setVelocity(0, v);
    dragon.body.setAllowGravity(false).setCollideWorldBounds(true);
    this.physics.add.collider(dragon, platform2);
    this.physics.add.collider(player, dragon, hitDragon, null, this);
}

function hitDragon(player, dragon) {
    dead.play();
    lives--;
    showScore();

    protect(0xFF0000);

    if (lives == 0) {
        this.physics.pause();
        gameOver = true;
        this.add.image(screenWidth/2, screenHeight/2, 'restart').setScale(5).setScrollFactor(0).setInteractive().on('pointerdown', () => location.reload());
    }
    else {
        dragon.destroy();
    }
}
  1. And finally, add a new loop to the “create()” function to print all the dragons at once:
function create() {
    ...
    for (i = 0; i < numDragons; i++) createDragon.call(this);
}

Enjoy the game!

You may enjoy playing this wonderful game online here.

Phaser. Unit 1. Your first game

Introduction

Phaser is one of the best frameworks to develop Desktop and mobile HTML games. You will be able to create 2D games and make them available to everyone with a simple domain with less than 10MB storage capacity. Also, it is fast, free, and opensource!

You may find more information at the Phaser official page and you may also have a look at some interesting demonstrations at the examples page.

HTML and CSS code

The first thing we should do is linking the Phaser library. We will use the last version at the time this unit has been written:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>

After that we are going to insert some CSS code to remove any margin and padding, and also to remove the scrollbars, so that all the window size is used when the game is started:

html, body {
    margin: 0px;
    padding: 0px;
    overflow: hidden;
    height: 100%;
} 

And finally we are going to use a single file to put all the code inside. This is going to be the basic structure of the “.html” file:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>First game with Phaser 3</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
    <style type="text/css">
        html, body {
            margin: 0px;
            padding: 0px;
            overflow: hidden;
            height: 100%;
        }
    </style>
</head>
<body>
    <script type="text/javascript">
        ...
    </script>
</body>
</html>

Constants

We are going to define some constants at the beginning of the code so that we can easily change the game appearance, difficulty, and some other parameters:

// The game will use the whole window
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;

// The world width will be 10 times the size of the screen
const worldWidth = screenWidth*10;

// Height of platform and joystick size (at the bottom)
const platformHeight = screenHeight/6;
const joystickSize = platformHeight/3;

// Both x and y velocity (depending on the screen size) 
const velocityX = screenWidth/4;
const velocityY = screenHeight/2;

// Number of clouds, tries, fires and coins
const numClouds = 50;
const numTrees = 100;
const numFires = 25;
const numCoins = 50;

Phaser configuration and other variables

Following the constants we are going to create some variables so keep all the information about the game and also to start the initialization of Phaser:

var config = {
    type: Phaser.AUTO,
    width: screenWidth,
    height: screenHeight,
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: velocityY }
        }
    },
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};

var platform, player, bell, dead;
var cursors, joyStick;
var score = 0, lives = 5, scoreText = null;
var gameOver = false;

// Init Phaser
var game = new Phaser.Game(config);

The assets (images, sprites and sounds)

We will need some images, sprites and sounds. You may use any assets you like, but we are also providing some examples so that you can easily start testing (they can also be downloaded here):

Images

Cloud

Tree
Bomb
Restart button

Sprites (player and animated objects)

Player
Fire
Coin

Sounds and music

When the player gets a coin
When the user gets hurt by a bomb or a fire
Background music always playing

Loading all the assets

So that we can use all those images, sprites and music, they have to be loaded previously. That is the purpose of the “preload()” function. We will also include here the initialization of the joystick so that we can use it in another section of this unit.

Proposed exercise: Game initialization

Create a specific folder for your game in your domain to put all the code and assets inside. After that, create an “index.html” inside that folder with the code below, and also create an “assets” folder to put all the images and sounds, which can be downloaded here. After uploading everything to your domain, test the game using the url of that folder in your browser, and you should get a full black screen. Click on it and the background music should start playing.

You can see the result here (you can also look at the source code by pressing Ctrl+U).
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>First game with Phaser 3</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
    <style type="text/css">
        html, body {
            margin: 0px;
            padding: 0px;
            overflow: hidden;
            height: 100%;
        }
    </style>
</head>

<body>
    <script type="text/javascript">
        const screenWidth = window.innerWidth;
        const screenHeight = window.innerHeight;

        const worldWidth = screenWidth * 10;

        const platformHeight = screenHeight / 6;
        const joystickSize = platformHeight / 3;

        const velocityX = screenWidth / 4;
        const velocityY = screenHeight / 2;

        const numTrees = 100;
        const numClouds = 50;
        const numFires = 25;
        const numCoins = 50;

        var config = {
            type: Phaser.AUTO,
            width: screenWidth,
            height: screenHeight,
            physics: {
                default: 'arcade',
                arcade: {
                    gravity: { y: velocityY }
                }
            },
            scene: {
                preload: preload,
                create: create,
                update: update
            }
        };

        var platform, player, bell, dead;
        var cursors, joyStick;
        var score = 0, lives = 5, scoreText = null;
        var gameOver = false;

        var game = new Phaser.Game(config);

        function preload() {
            // Load joyStick
            this.load.plugin('rexvirtualjoystickplugin', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/rexvirtualjoystickplugin.min.js', true);
       
            // Load images
            this.load.image('cloud', 'assets/cloud.png');
            this.load.image('tree', 'assets/tree.png');
            this.load.image('bomb', 'assets/bomb.png');
            this.load.image('restart', 'assets/restart.png');
            this.load.spritesheet('dude', 'assets/dude.png', { frameWidth: 32, frameHeight: 48 });
            this.load.spritesheet('fire', 'assets/fire.png', { frameWidth: 32, frameHeight: 32 });
            this.load.spritesheet('coin', 'assets/coin.png', { frameWidth: 32, frameHeight: 32 });

            // Load sounds and music
            this.load.audio('bell', 'assets/ding.mp3');
            this.load.audio('dead', 'assets/dead.mp3');
            this.load.audio('music', 'assets/music.mp3');
        }

        function initSounds() {
            bell = this.sound.add('bell');
            dead = this.sound.add('dead');
            this.sound.add('music', {volume:0.5}).play({ loop: -1 });
        }

        function create() {
            initSounds.call(this);
        }

        function update() {
        }
    </script>
</body>
</html>

Proposed exercise: Using your own music

Download any other music file you like to use it as a background music and change the JavaScript code inside the “preload()” function to set the file name accordingly. Also in the “initSounds()” function you will find that you may change the volume of the music (volume:0.5). Try changing that value from 0.1 to 1 and you will notice that the volume of the music will also change.

You may find in the following sites some free images and sounds which you can download and use for your own game:

Sky and platform

Once we have initialized Phaser and after loading all the assets, we can go ahead and paint something on the screen. We are going the create a couple of rectangles filled with any color we like, and this way we will create the sky and the platform.

We will use the following colors (you may find more information about colors in hexadecimal notation here):

  • Sky (light blue): 0x87CEEB
  • Platform (brown): 0xB76743

Proposed exercise: Adding sky and platform

Add the function “createWorld()” (provided below) to your code, and modify the calling function “create()” so that it is called when the game is started (also shown below). Finally test everything in your browser from your domain. You may also change the colors of both the platform and the sky.

You may easily get the hexadecimal code of any color you like with a simple color picker. About the music, do not forget to click on the screen after loading the game (the music will not start playing unless the browser gets the focus). Also, you can see the result here (you can look at the source code by pressing Ctrl+U).
  1. This is the new function you have to insert in your code to create the world:
function createWorld() {
    this.cameras.main.setBounds(0, 0, worldWidth, screenHeight);
    this.physics.world.setBounds(0, 0, worldWidth, screenHeight);

    // Sky
    sky = this.add.rectangle(0, 0, worldWidth, screenHeight, 0x87CEEB).setOrigin(0);

    // Platform
    platform = this.add.rectangle(0, screenHeight, worldWidth, platformHeight, 0xB76743).setOrigin(1);
    this.physics.add.existing(platform);
    platform.body.setCollideWorldBounds(true);
}
  1. Do not forget to update the “create()” function to create the world (sky, platform, camera, and world boundaries). This is the only line you should add to that function:
function create() {
    ...
    createWorld.call(this);            
}

Objects in background (trees)

We will first create the trees so that they remain in the background. We may create all of them using the same image. We will just change the size of the trees by scaling them randomly, and we will also choose a random position for each of them. Finally we will use a loop to print as many trees as we like.

Proposed exercises: Adding the trees

Add the code below to your file and check the results. After that, change the value of the constant “numTrees” (at the top of your code) and also the value of the “scale” variable to check how easily you can customize your game. Refresh the page several times and you will notice that the generated trees have random sizes and they also appear in random positions. Finally, look for another image (a tree or any other object) and change the code accordingly to use your own image.

You can see the result here (you can look at the source code by pressing Ctrl+U). Also, as listed above, you may find many free assets in OpenGameArt.
  1. This is the new function you have to insert in your code to create each tree:
function createTree() {
    const x = Phaser.Math.Between(0, worldWidth);
    const y = screenHeight-platformHeight;
    const scale = Phaser.Math.FloatBetween(0.5, 2);
    this.add.image(x, y, 'tree').setOrigin(1).setScale(scale);
}
  1. Do not forget to update the “create()” function to create all the trees. You only need one single line to create all trees at once using a loop:
function create() {
    ...
    for(i=0; i<numTrees; i++) createTree.call(this);          
}

The animations (sprites)

We will use sprites to create the player and the animated objects. For example, let’s have a look at the player:

Sprite with three player animations

We may appreciate that we have 9 pictures divided into 3 different groups. This way, we may easily create all three animations:

  • Left: Pictures from 0 to 3
  • Turn: Picture number 4
  • Right: Pictures from 5 to 8

Controlling the player’s animations

So that this game can be used in both desktop and mobile devices, we have to initialize both the cursor keys and the joystick. Once this is done, we must change the player’s velocity and the current animation when the user presses the keys or moves the joystick. In the next exercise we will first define the animations inside the “createAnimations()” function, and after that we will go ahead with the “createPlayer()” function, where we will display the player. We will finally include in the “update()” function some conditions to adjust the player’s speed and current animation.

Proposed exercise: Adding the player

Add the code below to your game and check both the player animations and movements. After that, look for another sprite and change the code accordingly to use your own sprite. Finally, change the values of the constants “velocityX” and “velocityY” (at the top of your code) and check the results.

You can see the result here (you can look at the source code by pressing Ctrl+U). Also, as listed above, you may find many free assets in OpenGameArt and choose other sprites you like.
  1. These are the new functions you have to insert in your code to create the animations and initialize everything related to the player:
function createAnimations() {
    // Player
    this.anims.create({
        key: 'left',
        frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
        frameRate: 10,
        repeat: -1
    });
    this.anims.create({
        key: 'turn',
        frames: [{ key: 'dude', frame: 4 }]
    });
    this.anims.create({
        key: 'right',
        frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
        frameRate: 10,
        repeat: -1
    });
    // Fire
    this.anims.create({
        key: 'burning',
        frames: this.anims.generateFrameNumbers('fire', { start: 0, end: 4 }),
        frameRate: 10,
        repeat: -1
    });
    // Coin
    this.anims.create({
        key: 'rotate',
        frames: this.anims.generateFrameNumbers('coin', { start: 0, end: 7 }),
        frameRate: 10,
        repeat: -1
    });            
}

function createPlayer() {
    player = this.physics.add.sprite(0, screenHeight-platformHeight, 'dude').setOrigin(1).setScale(3).setBounce(0.2).setCollideWorldBounds(true);
    this.physics.add.collider(player, platform);
    this.cameras.main.startFollow(player, true, 0.05, 0.05);
    // Cursor keys and joystick
    cursors = this.input.keyboard.createCursorKeys();
    joyStick = this.plugins.get('rexvirtualjoystickplugin').add(this, {
        x: screenWidth / 2,
        y: screenHeight - joystickSize*1.5,
        radius: joystickSize
    }).on('update', update, this);
}
  1. Also you have to insert some code inside the “update()” function to respond to the cursor keys and the joystick:
function update() {
    if (gameOver) return;

    if (cursors.left.isDown || joyStick.left) {
        player.setVelocityX(-velocityX).anims.play('left', true);
    }
    else if (cursors.right.isDown || joyStick.right) {
        player.setVelocityX(velocityX).anims.play('right', true);
    }
    else {
        player.setVelocityX(0).anims.play('turn');
    }

    if ((cursors.up.isDown || joyStick.up) && player.body.touching.down) {
        player.setVelocityY(-velocityY);
    }
}
  1. And finally do not forget to update the “create()” function to create the animations and the player. You just have to insert a couple of new lines:
function create() {
    ...
    createAnimations.call(this); 
    createPlayer.call(this);
}

The clouds, the score, the fires and the coins

We will create many different objects (clouds, fires and coins) at once using the same images. We will just change the size of the objects by scaling them randomly, and we will use a loop to print as many objects as we like choosing random positions.

Proposed exercises: Adding the clouds

Add the code below to your file and check the results. After that, change the value of the constant “numClouds” (at the top of your code) and also the value of the “scale” variable to check how easily you can customize your game. Refresh the page several times and you will notice that the generated clouds and also the trees have random sizes and they appear in random positions. Finally, look for another image (a cloud or any other object) and change the code accordingly to use your own image.

You can see the result here (you can look at the source code by pressing Ctrl+U). Also, as listed above, you may find many free assets in OpenGameArt and choose the images you like.
function createCloud() {
    const x = Phaser.Math.Between(0, worldWidth);
    const y = Phaser.Math.Between(0, screenHeight-platformHeight*2);
    const scale = Phaser.Math.FloatBetween(0.5, 1.5);
    this.add.image(x, y, 'cloud').setScale(scale);
}
function create() {
    ...
    for(i=0; i<numClouds; i++) createCloud.call(this);
}

Proposed exercise: Adding the score

Using the global variables, we may easily show both the score and number of remaining lives. Add the code below to your game and check the results. After that, change the value of the constant “numLives” (at the top of your code) and also the value of the “scale” variable to check how easily you can customize that text.

You can see the result here (you can look at the source code by pressing Ctrl+U).
function showScore() {
    if (!scoreText) scoreText = this.add.text(16, 16, '', { fontSize:(screenWidth/20)+'px', fill:'#000' }).setScrollFactor(0);
    scoreText.setText('Score:' + score + ' / Lives:' + lives);
}
function create() {
    ...
    showScore.call(this);
}

Proposed exercises: Adding the fires and the coins

Add the code below to your game and check the results. After that, change the value of the constants “numFires” and “numCoins” (at the top of your code) and also the value of the “scale” variables to check how easily you can customize your game. Finally, look for another sprites (fires, coins, or any other objects) and change the code accordingly to use your own sprites.

You may click here to see how the game looks like. As listed above, you may find many free assets in OpenGameArt.
function createFire() {
    const x = Phaser.Math.Between(screenWidth/2, worldWidth);
    const y = Phaser.Math.Between(screenHeight-platformHeight, screenHeight);
    let fire = this.physics.add.sprite(x, y, 'fire').setOrigin(1).setScale(3).setImmovable(true).anims.play('burning', true)
    fire.body.setAllowGravity(false);
    this.physics.add.collider(player, fire, hitBombOrFire, null, this);   
}

function createCoin() {     
    const x = Phaser.Math.Between(screenWidth/2, worldWidth);
    const bounce = Phaser.Math.FloatBetween(0.1, 0.5);
    let coin = this.physics.add.sprite(x, 0, 'coin').setOrigin(1).setScale(3).setBounce(bounce).anims.play('rotate');
    coin.body.setOffset(0, -10);
    this.physics.add.collider(coin, platform);
    this.physics.add.overlap(player, coin, collectCoin, null, this);
}

function createBomb() {
    const x = Phaser.Math.Between(0, worldWidth);
    const v = Phaser.Math.Between(-velocityX, velocityX);
    let bomb = this.physics.add.image(x, 0, 'bomb').setScale(2).setBounce(1).setCollideWorldBounds(true).setVelocity(v, velocityY);
    bomb.body.setAllowGravity(false);
    this.physics.add.collider(bomb, platform);     
    this.physics.add.collider(player, bomb, hitBombOrFire, null, this);   
}

function collectCoin(player, coin) { 
    bell.play();
    coin.destroy();
    createCoin.call(this);
    createBomb.call(this);    

    score += 10;
    if (score % 100 == 0) lives++;
    showScore();
}

function hitBombOrFire(player, thing) {
    dead.play();
    lives--;
    showScore();
    player.setTint(0xff0000).anims.play('turn');

    if (lives == 0) {
        this.physics.pause();
        gameOver = true;
        this.add.image(screenWidth/2, screenHeight/2, 'restart').setScale(5).setScrollFactor(0).setInteractive().on('pointerdown', ()=>location.reload());                
    }
    else {
        thing.destroy();
        setTimeout(()=>player.clearTint(), 3000);
    }
}
function create() {
    ...
    for(i=0; i<numCoins; i++) createFire.call(this);
    for(i=0; i<numCoins; i++) createCoin.call(this);
}

Enjoy the game!

You may enjoy playing this wonderful game online here.

CSS. Unit 6. Flexbox.

Why flexbox?

Flexbox is a one-dimensional layout method for laying out items in rows or columns. Items flex to fill additional space and shrink to fit into smaller spaces. This unit explains all the fundamentals.

For a long time, the only reliable cross browser-compatible tools available for creating CSS layouts were things like floats and positioning. These are fine, and they work, but in some ways they are also rather limiting and frustrating.

The following simple layout requirements are either difficult or impossible to achieve with such tools, in any kind of convenient, flexible way:

  • Vertically centering a block of content inside its parent.
  • Making all the children of a container take up an equal amount of the available width/height, regardless of how much width/height is available.
  • Making all columns in a multiple-column layout adopt the same height even if they contain a different amount of content.

As you’ll see in subsequent sections, flexbox makes a lot of layout tasks much easier. Let’s dig in!

Introducing a simple example

In this unit we are going to get you to work through a series of exercises to help you understand how flexbox works. To get started, we will use some simple code like this one:

<header>
  <h1>Valencian Community</h1>
</header>

<section>
  <article>
    <h2>Alicante</h2>
    <p>The campus of the University of Alicante lies in San Vicente del Raspeig, bordering the city of Alicante to the north. More than 25,000 students attend the University.</p>
  </article>

  <article>
    <h2>Valencia</h2>
    <p>Valencia is the capital of the autonomous community of Valencia and the third-largest city in Spain after Madrid and Barcelona, surpassing 800,000 inhabitants in the municipality.</p>
  </article>

  <article>
    <h2>Castellón</h2>
    <p>The city is notorious for its music festivals, among which we find: the Tanned Tin music festival for alternative music, the Benicàssim's International Festival, the Arenal Sound and the Rototom Sunsplash Festival, known for its reggae music.</p>
  </article>
</section>
html {
  font-family: sans-serif;
}

body {
   margin: 0;
}

header {
  background: purple;
  height: 75px;
}

h1 {
  text-align: center;
  color: white;
  line-height: 75px;
  margin: 0;
}

article {
  padding: 1%;
  margin: 1%;
  background: aqua;
}

Valencian Community

Alicante

The campus of the University of Alicante lies in San Vicente del Raspeig, bordering the city of Alicante to the north. More than 25,000 students attend the University.

Valencia

Valencia is the capital of the autonomous community of Valencia and the third-largest city in Spain after Madrid and Barcelona, surpassing 800,000 inhabitants in the municipality.

Castellón

The city is notorious for its music festivals, among which we find: the Tanned Tin music festival for alternative music, the Benicàssim’s International Festival, the Arenal Sound and the Rototom Sunsplash Festival, known for its reggae music.

You’ll see that we have a <header> element with a top level heading inside it, and a <section> element containing three <article>s. We are going to use these to create a fairly standard three column layout.

Proposed exercise: Simple boxes

Using the code of the previous example, create a new webpage with a similar content. You can use any other colors and styles you like.

Specifying what elements to lay out as flexible boxes

To start with, we need to select which elements are to be laid out as flexible boxes. To do this, we set a special value of display on the parent element of the elements you want to affect. In this case we want to lay out the <article> elements, so we set this on the <section>:

section {
  display: flex;
}

This causes the <section> element to become a flex container, and its children to become flex items. The result of this should be something like so:

Valencian Community

Alicante

The campus of the University of Alicante lies in San Vicente del Raspeig, bordering the city of Alicante to the north. More than 25,000 students attend the University.

Valencia

Valencia is the capital of the autonomous community of Valencia and the third-largest city in Spain after Madrid and Barcelona, surpassing 800,000 inhabitants in the municipality.

Castellón

The city is notorious for its music festivals, among which we find: the Tanned Tin music festival for alternative music, the Benicàssim’s International Festival, the Arenal Sound and the Rototom Sunsplash Festival, known for its reggae music.

So, this single declaration gives us everything we need — incredible, right? We have our multiple column layout with equal-sized columns, and the columns are all the same height. This is because the default values given to flex items (the children of the flex container) are set up to solve common problems such as this.

To be clear, let’s reiterate what is happening here. The element we’ve given a   display value of flex to is acting like a block-level element in terms of how it interacts with the rest of the page, but its children are being laid out as flex items. The next section will explain in more detail what this means. Note also that you can use a display value of inline-flex if you wish to lay out an element’s children as flex items, but have that element behave like an inline element.

Proposed exercise: Flexible boxes

Add some CSS code to the previous exercise so that all the contents inside the <section> element are displayed as flexible boxes (display:flex) which are shown from left to right, all of them having the same height.

As explained, you only have to add three lines of code and everything is done! You will have flexible boxes:
section {
  display: flex;
}

The flex model

When elements are laid out as flex items, they are laid out along two axes:

  • The main axis is the axis running in the direction the flex items are being laid out in (e.g. as rows across the page, or columns down the page.) The start and end of this axis are called the main start and main end.
  • The cross axis is the axis running perpendicular to the direction the flex items are being laid out in. The start and end of this axis are called the cross start and cross end.
  • The parent element that has display: flex set on it (the <section> in our example) is called the flex container.
  • The items being laid out as flexible boxes inside the flex container are called flex items (the <article> elements in our example).

Bear this terminology in mind as you go through subsequent sections. You can always refer back to it if you get confused about any of the terms being used.

Columns or rows?

Flexbox provides a property called flex-direction that specifies what direction the main axis runs in (what direction the flexbox children are laid out in). By default this is set to row, which causes them to be laid out in a row in the direction your browser’s default language works in (usually from left to right), but you can also use the following declaration inside your <section> rule:

flex-direction: column;

This would put the items back in a column layout, much like they were before we added any CSS.

Wrapping

One issue that arises when you have a fixed amount of width or height in your layout is that eventually your flexbox children will overflow their container, breaking the layout. And also if you have many boxes, the final result will be presented with some quite narrow columns. Have a look at our example:

Valencian Community

Alicante

The campus of the University of Alicante lies in San Vicente del Raspeig, bordering the city of Alicante to the north. More than 25,000 students attend the University.

Valencia

Valencia is the capital of the autonomous community of Valencia and the third-largest city in Spain after Madrid and Barcelona, surpassing 800,000 inhabitants in the municipality.

Castellón

The city is notorious for its music festivals, among which we find: the Tanned Tin music festival for alternative music, the Benicàssim’s International Festival, the Arenal Sound and the Rototom Sunsplash Festival, known for its reggae music.

Alcoy

Alcoy is an industrial and university city, region and municipality located in the province of Alicante. The Serpis river crosses the municipal boundary.

Benidorm

It has been a tourist destination since its port was extended and the first hotels were built. Today it is known for its beaches and skyscrapers and receives as many tourists from abroad as from Spain.

Here we see that the children are indeed breaking out of their container for small screens of filling the window with narrow columns. One way in which you can fix this is to add the following declaration to your <section> rule:

section {
  ...
  flex-wrap: wrap;
}

Also, add the following declaration to your <article> rule:

article {
  ...
  flex: 48%;
}

You’ll see that the layout looks much better with this included:

Valencian Community

Alicante

The campus of the University of Alicante lies in San Vicente del Raspeig, bordering the city of Alicante to the north. More than 25,000 students attend the University.

Valencia

Valencia is the capital of the autonomous community of Valencia and the third-largest city in Spain after Madrid and Barcelona, surpassing 800,000 inhabitants in the municipality.

Castellón

The city is notorious for its music festivals, among which we find: the Tanned Tin music festival for alternative music, the Benicàssim’s International Festival, the Arenal Sound and the Rototom Sunsplash Festival, known for its reggae music.

Alcoy

Alcoy is an industrial and university city, region and municipality located in the province of Alicante. The Serpis river crosses the municipal boundary.

Benidorm

It has been a tourist destination since its port was extended and the first hotels were built. Today it is known for its beaches and skyscrapers and receives as many tourists from abroad as from Spain.

We now have multiple rows — as many flexbox children are fitted onto each row as makes sense, and any overflow is moved down to the next line. The flex:48% declaration set on the articles means that each will be at least half the window size (48% wide, since we have 1% margins on both left and right). You might also notice that the last few children on the last row are each made wider so that the entire row is still filled.

But there’s more we can do here. For example, you can try changing your flex-direction property value to row-reverse. Now you’ll see that you still have your multiple row layout, but it starts from the opposite corner of the browser window and flows in reverse.

Proposed exercise: Wrapped content

Using the code of the last example above, add three more towns you like (to show at least 9 towns in total), and also insert a picture for each one. You may also change the colors of the boxes and text as you like, or you can even add more styles to your code (box and text shadows, etc.).

For example:

Valencian Community

Alicante

The campus of the University of Alicante lies in San Vicente del Raspeig, bordering the city of Alicante to the north. More than 25,000 students attend the University.

Valencia

Valencia is the capital of the autonomous community of Valencia and the third-largest city in Spain after Madrid and Barcelona, surpassing 800,000 inhabitants in the municipality.

Castellón

The city is notorious for its music festivals, among which we find: the Tanned Tin music festival for alternative music, the Benicàssim’s International Festival, the Arenal Sound and the Rototom Sunsplash Festival, known for its reggae music.

Benidorm

It has been a tourist destination since its port was extended and the first hotels were built. Today it is known for its beaches and skyscrapers and receives as many tourists from abroad as from Spain.

CSS. Unit 5. The box model.

Introduction

Everything in CSS has a box around it, and understanding these boxes is key to being able to create layouts with CSS, or to align items with other items. In this lesson, we will take a proper look at the CSS Box Model so that you can build more complex layout tasks with an understanding of how it works and the terminology that relates to it.

What is the CSS box model?

The full CSS box model applies to block boxes. This model defines how the different parts of a box (margin, border, padding, and content) work together to create a box that you can see on the page.

Parts of a box

When making up a block box in CSS we will have to keep in mind the following parts:

  • Content box: The area where your content is displayed, which can be sized using properties like width and height.
  • Padding box: The padding sits around the content as white space; its size can be controlled using padding and related properties.
  • Border box: The border box wraps the content and any padding. Its size and style can be controlled using border and related properties.
  • Margin box: The margin is the outermost layer, wrapping the content, padding and border as whitespace between this box and other elements. Its size can be controlled using margin and related properties.

The below diagram shows these layers:

In the standard box model, if you give a box a width and a height attribute, this defines the width and height of the content box. Any padding and border is then added to that width and height to get the total size taken up by the box. This is shown in the image below.

If we assume that the box has the following CSS defining widthheightmarginborder, and padding:

.box {
  width: 350px;
  height: 150px;
  margin: 10px;
  padding: 25px;
  border: 5px solid black;
}

The space taken up by our box using the standard box model will actually be 410px (350 + 25 + 25 + 5 + 5), and the height 210px (150 + 25 + 25 + 5 + 5), as the padding and border are added to the width used for the content box:

The margin is not counted towards the actual size of the box. Sure, it affects the total space that the box will take up on the page, but only the space outside the box. The box’s area stops at the border (it does not extend into the margin).

Proposed exercise: Playing with box models

In the below example, you can see a couple of boxes with a CSS class which gives some values to width, height, marginborder, and padding. Using that code, create a web page with five different boxes, each one with different styles. You have to change also the color of the text (you can choose the colors your like). And finally add some shadows to the text using different styles and colors.

You can have a look at the different colors here: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value

CSS code:

.box1 {
  border: 5px solid red;
  background-color: lightgray;
  padding: 10px;
  margin: 25px;
  width: 250px;
  height: 200px;
  ...
}

.box2 {
  border: 7px solid green;
  background-color: gray;
  padding: 5px;
  margin: 0px;
  width: 300px;
  height: 200px;
  color: white;
  ...
}

...

HTML code:

<div class="box1">This is a 250x200px box with a 5px red border, and lightgray background, 10px padding and 25px margin.</div>
<div class="box2">This is a 300x200px box with a 7px green border, gray background, white text color, 5px padding and 0px margin.</div>
...

And the result:

This is a 250x200px box with a 5px red border, and lightgray background, and 10px padding and 25px margin.
This is a 300x200px box with a 7px green border, gray background, white text color, and 5px padding and 0px margin.

Margins, padding and borders

You’ve already seen the marginpadding, and border properties at work in the example above. The properties used in that example are shorthands and allow us to set all four sides of the box at once. These shorthands also have equivalent longhand properties, which allow control over the different sides of the box individually.

Let’s explore these properties in more detail.

Margin

The margin is an invisible space around your box. It pushes other elements away from the box. Margins can have positive or negative values. Setting a negative margin on one side of your box can cause it to overlap other things on the page.

We can control all margins of an element at once using the margin property, or each side individually using the equivalent longhand properties:

Proposed exercise: Custom margins

Create a web page with the example below, and change the margin values to see how the boxes are pushed around due to the margin creating or removing space (if it is a negative margin) between this element and the containing element. Finally, add three more boxes with any style you like inside the same container.

.container {
  border: 5px solid red; 
}

.box1 {
  border: 5px solid green;
  margin-top: 10px;
  margin-left: 10px;
  width: 50%;
}

.box2 {
  border: 5px solid blue;
  margin-top: 10px;
  margin-left: 50px;
  width: 50%;
}

.box3 {
  border: 5px solid orange;
  margin-top: 10px;
  margin-left: 100px;
  width: 50%;
}
...
<div class="container">
  <div class="box1">This is a box with 50% width and 10px from top and left inside a container</div>
  <div class="box2">This is a box with 50% width and 10px from top and 50px left inside a container</div>
  <div class="box3">This is a box with 50% width and 10px from top and 100px left inside a container</div>
  ...
</div>
This is a box with 50% width and 10px from top and left inside a container
This is a box with 50% width and 10px from top and 50px left inside a container
This is a box with 50% width and 10px from top and 100px left inside a container

Padding

The padding sits between the border and the content area. Unlike margins you cannot have negative amounts of padding, so the value must be 0 or a positive value. Any background applied to your element will display behind the padding, and it is typically used to push the content away from the border.

We can control the padding on each side of an element individually using the padding property, or each side individually using the equivalent longhand properties:

Proposed exercise: Custom padding

If you change the values for padding on the classes .box in the example below you can see that this changes where the text begins in relation to the box. Padding can be changed on any element, and will make space between its border and whatever is inside the element. Create a web page with the code below and add three more boxes using different paddings and styles.

.box1 {
  border: 5px solid red;
  margin: 10px;
  ...
}

.box2 {
  border: 5px solid blue;
  margin: 10px;
  padding: 25px;
  ...
}

.box3 {
  border: 5px solid green;
  margin: 10px;
  padding: 50px;
  ...
}
...
<div class="box1">
  This is a box without padding
</div>
<div class="box2">
  This is a box with 25 px padding
</div>
<div class="box3">
  This is a box with 50px padding
</div>
...
This is a box without padding
This is a box with 25 px padding
This is a box with 50px padding

Borders

The border is drawn between the margin and the padding of a box. If you are using the standard box model, the size of the border is added to the width and height of the box.

For styling borders, there are a large number of properties (there are four borders, and each border has a style, width and color that we might want to manipulate).

You can set the width, style, or color of all four borders at once using the border property.

To set the properties of each side individually, you can use:

To set the width, style, or color of all sides, use the following:

To set the width, style, or color of a single side, you can use one of the most granular longhand properties:

Proposed exercise: Custom borders

In the example below we have used various shorthands and longhands to style the borders. Create a new web page with that code and have a play around with the different properties to check that you understand how they work. Finally add three more boxes and set the border styles as you like.

You can have a look at the MDN pages (linked above) to get information about the different styles of border you can choose from. Also you can have a look at the different colors here: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
.box1 {
  border-top: 5px dotted green;
  border-right: 5px dashed orange;
  border-bottom: 5px dotted red;
  border-left: 5px dashed blue;
  padding: 15px;
  margin-top: 15px;
  margin-bottom: 15px;
  text-align: center;
  ...
}

.box2 {
  border: 5px solid black;
  border-left-width: 10px;
  border-right-width: 10px;
  border-top-color: aqua;
  border-bottom-color: pink;
  padding: 15px;
  text-align: center;
  ...
}

.box3 {
  border: 2px solid black;
  padding: 10px;
  ...
}

...
<div class="box1">This is a box with different types of borders</div>
<div class="box2">This is another box with different types of borders</div>
<div class="box1">
  <div class="box2">This is a box with different types of borders inside another box</div>
</div>
<div class="box3">
  <div class="box3">
    <div class="box3">
      <div class="box3">
        <div class="box3">
          <div class="box3">
            These are boxes inside boxes
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
...
This is a box with different types of borders
This is another box with different types of borders
This is a box with different types of borders inside another box
These are boxes inside boxes

CSS. Unit 4. Styling lists.

Introduction

Lists behave like any other text for the most part, but there are some CSS properties specific to lists that you need to know about, and some best practices to consider. This unit explains all.

A simple list example

To begin with, let’s look at a simple example with lists. Throughout this unit, we’ll look at unordered, ordered, and description lists (all have styling features that are similar, and some that are particular to their type of list). The HTML for our example looks like so:

<h2>Shopping (unordered list)</h2>

<ul>
  <li>Hummus</li>
  <li>Pita</li>
  <li>Green salad</li>
  <li>Halloumi</li>
</ul>

<h2>Recipe (ordered list)</h2>

<ol>
  <li>Toast pita, leave to cool, then slice down the edge.</li>
  <li>Fry the halloumi in a shallow, non-stick pan, until browned on both sides.</li>
  <li>Wash and chop the salad.</li>
  <li>Fill pita with salad, hummus, and fried halloumi.</li>
</ol>

<h2>Ingredients (description list)</h2>

<dl>
  <dt>Hummus</dt>
  <dd>A thick dip/sauce generally made from chick peas blended with tahini, lemon juice, salt, garlic, and other ingredients.</dd>
  <dt>Pita</dt>
  <dd>A soft, slightly leavened flatbread.</dd>
  <dt>Halloumi</dt>
  <dd>A semi-hard, unripened, brined cheese with a higher-than-usual melting point, usually made from goat/sheep milk.</dd>
  <dt>Green salad</dt>
  <dd>That green healthy stuff that many of us just use to garnish kebabs.</dd>
</dl>

The list elements probably will have the following styling defaults:

  • The <ul> and <ol> elements have a top and bottom margin of 16px (1em)  and a padding-left of 40px (2.5em).
  • The list items (<li> elements) have no set defaults for spacing.
  • The <dl> element has a top and bottom margin of 16px (1em), but no padding set.
  • The <dd> elements have margin-left of 40px (2.5em).
  • The <p> elements we’ve included for reference have a top and bottom margin of 16px (1em), the same as the different list types.

Proposed exercise: Lists with default styling

Create a web page with the list of the previous example, and check the result in your browser. It should be displayed with the default styling (no CSS must be used right now).

You should get something like this:

Shopping (unordered list)

  • Hummus
  • Pita
  • Green salad
  • Halloumi

Recipe (ordered list)

  1. Toast pita, leave to cool, then slice down the edge.
  2. Fry the halloumi in a shallow, non-stick pan, until browned on both sides.
  3. Wash and chop the salad.
  4. Fill pita with salad, hummus, and fried halloumi.

Ingredients (description list)

Hummus
A thick dip/sauce generally made from chick peas blended with tahini, lemon juice, salt, garlic, and other ingredients.
Pita
A soft, slightly leavened flatbread.
Halloumi
A semi-hard, unripened, brined cheese with a higher-than-usual melting point, usually made from goat/sheep milk.
Green salad
That green healthy stuff that many of us just use to garnish kebabs.

Handling list styling

When styling lists, you need to adjust their styles so they keep the same vertical spacing as their surrounding elements (such as paragraphs and images; sometimes called vertical rhythm), and the same horizontal spacing as each other.

Some CSS for the text styling and spacing could be as follows:

html {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 10px;
}

h2 {
  font-size: 2rem;
  text-shadow: 1px 1px 5px orange;
}

ul, ol, dl {
  font-size: 1.5rem;
}

li {
  line-height: 1.5;
}

dd, dt {
  line-height: 1.5;
}

dt {
  font-weight: bold;
  padding-left: 2rem;
}
  • The first rule sets a sitewide font and a baseline font size of 10px. These are inherited by everything on the page.
  • Rules 2 and 3 set relative font sizes for the headings, and different list types (the children of the list elements inherit these). This means that each list will have the same font size and top and bottom spacing, helping to keep the vertical rhythm consistent.
  • Rule 4 sets the same line-height on list items, so that each individual list item will have the same spacing between lines. This will also help to keep the vertical rhythm consistent.
  • Rules 5 and 6 apply to the description list. We set the same line-height on the description list terms and descriptions as we did with the list items. Again, consistency is good! We also make the description terms have bold font, so they visually stand out easier.

Proposed exercise: Lists with your own styles

Create a web page with the lists and styles of the previous example, and change or add any new styles you like. Now you have to create a CSS file and link it from the HTML code. Finally check the result in your browser and validate your code.

Your result may look now something like this:

Shopping (unordered list)

  • Hummus
  • Pita
  • Green salad
  • Halloumi

Recipe (ordered list)

  1. Toast pita, leave to cool, then slice down the edge.
  2. Fry the halloumi in a shallow, non-stick pan, until browned on both sides.
  3. Wash and chop the salad.
  4. Fill pita with salad, hummus, and fried halloumi.

Ingredients (description list)

Hummus
A thick dip/sauce generally made from chick peas blended with tahini, lemon juice, salt, garlic, and other ingredients.
Pita
A soft, slightly leavened flatbread.
Halloumi
A semi-hard, unripened, brined cheese with a higher-than-usual melting point, usually made from goat/sheep milk.
Green salad
That green healthy stuff that many of us just use to garnish kebabs.

List-specific styles

Now that we’ve looked at general spacing techniques for lists, let’s explore some list-specific properties. As seen in a previous unit, there are three properties you should know about to start with, which can be set on both <ul> or <ol> elements:

  • list-style-type: Sets the type of bullets to use for the list, for example, square or circle bullets for an unordered list, or numbers, letters or roman numerals for an ordered list.
  • list-style-position: Sets whether the bullets appear inside the list items, or outside them before the start of each item.
  • list-style-image: Allows you to use a custom image for the bullet, rather than a simple square or circle.

Bullet and number styles

As mentioned above, the list-style-type property allows you to set what type of bullet to use for the bullet points. In our example, we can set the ordered list to use uppercase roman numerals, with:

ol {
  list-style-type: upper-roman;
}
  1. Toast pita, leave to cool, then slice down the edge.
  2. Fry the halloumi in a shallow, non-stick pan, until browned on both sides.
  3. Wash and chop the salad.
  4. Fill pita with salad, hummus, and fried halloumi.

Bullet position

The list-style-position property sets whether the bullets appear inside the list items, or outside them before the start of each item. The default value is outside, which causes the bullets to sit outside the list items, as seen above.

If you set the value to inside, the bullets will sit inside the lines:

ol {
  list-style-type: upper-roman;
  list-style-position: inside;
}
  1. Toast pita, leave to cool, then slice down the edge.
  2. Fry the halloumi in a shallow, non-stick pan, until browned on both sides.
  3. Wash and chop the salad.
  4. Fill pita with salad, hummus, and fried halloumi.

Using a custom bullet image

The list-style-image property allows you to use a custom image for your bullet. The syntax is pretty simple:

ul {
  list-style-image: url("https://mdn.github.io/learning-area/css/styling-text/styling-lists/star.svg");
}

However, this property is a bit limited in terms of controlling the position, size, etc. of the bullets. You are better off using the background family of properties, which you may also find in the Backgrounds and borders article.

In our example, we have styled the unordered list like so:

ul {
  padding-left: 2rem;
  list-style-type: none;
}

ul li {
  padding-left: 2rem;
  background-image: url("https://mdn.github.io/learning-area/css/styling-text/styling-lists/star.svg");
  background-position: 0 0;
  background-size: 1.6rem 1.6rem;
  background-repeat: no-repeat;
}
  • Hummus
  • Pita
  • Green salad
  • Halloumi

Here we’ve done the following:

  • Set the padding-left of the <ul> down from the default 40px to 20px, then set the same amount on the list items. This is so that overall the list items are still lined up with the order list items and the description list descriptions, but the list items have some padding for the background images to sit inside. If we didn’t do this, the background images would overlap with the list item text, which would look messy.
  • Set the list-style-type to none, so that no bullet appears by default. We’re going to use background properties to handle the bullets instead.
  • Inserted a bullet onto each unordered list item. The relevant properties are as follows:
    • background-image: This references the path to the image file you want to use as the bullet.
    • background-position: This defines where in the background of the selected element the image will appear. In this case we are saying 0 0, which means the bullet will appear in the very top left of each list item.
    • background-size: This sets the size of the background image. We ideally want the bullets to be the same size as the list items (or very slightly smaller or larger). We are using a size of 1.6rem (16px), which fits very nicely with the 20px padding we’ve allowed for the bullet to sit inside (16px plus 4px of space between the bullet and the list item text works well).
    • background-repeat: By default, background images repeat until they fill up the available background space. We only want one copy of the image inserted in each case, so we set this to a value of no-repeat.

Proposed exercise: Putting it all together

Using the code of the previous exercise, change the defaults bullets to use an image, and also change the default numbering, and any other styles you like. Finally check the result in your browser and do not forget to validate your code.

Your result may look now something like this:

Shopping (unordered list)

  • Hummus
  • Pita
  • Green salad
  • Halloumi

Recipe (ordered list)

  1. Toast pita, leave to cool, then slice down the edge.
  2. Fry the halloumi in a shallow, non-stick pan, until browned on both sides.
  3. Wash and chop the salad.
  4. Fill pita with salad, hummus, and fried halloumi.

Ingredients (description list)

Hummus
A thick dip/sauce generally made from chick peas blended with tahini, lemon juice, salt, garlic, and other ingredients.
Pita
A soft, slightly leavened flatbread.
Halloumi
A semi-hard, unripened, brined cheese with a higher-than-usual melting point, usually made from goat/sheep milk.
Green salad
That green healthy stuff that many of us just use to garnish kebabs.

List-style shorthand

It is also worth to know that we may set the three properties mentioned in the section above using a single shorthand property, list-style. For example, the following CSS:

ul {
  list-style-type: disc;
  list-style-image: url(example.png);
  list-style-position: inside;
}

Could be replaced by this:

ul {
  list-style: disc url(example.png) inside;
}

The values can be listed in any order, and you can use one, two or all three (the default values used for the properties that are not included are discnone, and outside). If both a type and an image are specified, the type is used as a fallback if the image can’t be loaded for some reason.

CSS. Unit 3. Styling links.

Introduction

When styling links, it is important to understand how to make use of pseudo-classes to style link states effectively, and how to style links for use in common varied interface features such as navigation menus and tabs. We’ll look at all these topics in this unit.

Let’s look at some links

The first thing to understand is the concept of link states (different states that links can exist in, which can be styled using different pseudo-classes):

  • Link: A link which has a destination (i.e. not just a named anchor), styled using the :link pseudo class.
  • Visited: A link when it has already been visited (exists in the browser’s history), styled using the :visited pseudo class.
  • Hover: A link when it is being hovered over by a user’s mouse pointer, styled using the :hover pseudo class.
  • Focus: A link when it has been focused (for example moved to by a keyboard user using the Tab key or similar, or programmatically focused using HTMLElement.focus()). This is styled using the :focus pseudo class.
  • Active: A link when it is being activated (e.g. clicked on), styled using the :active pseudo class.

Default styles

The following example illustrates what a link will behave like by default (the CSS is simply enlarging and centering the text to make it stand out more):

p {
  font-size: 2rem;
  text-align: center;
}
<p><a href="https://fernandoruizrico.com" target="_blank">A simple link</a></p>

A simple link

You’ll notice a few things as you explore the default styles:

  • Links are underlined.
  • Unvisited links are blue.
  • Visited links are purple.
  • Hovering a link makes the mouse pointer change to a little hand icon.
  • Focused links have an outline around them. You should be able to focus on the links with the keyboard by pressing the tab key (On Mac, you’ll need to use option + tab, or enable the Full Keyboard Access: All controls option by pressing Ctrl + F7.)
  • Active links are red. You can try this feature by holding down the mouse button on the link as you click it.

Proposed exercise: Larger and centered links

Create a web page with at least 5 paragraphs, each one with a different link to any sites you like. You also have to style your page so that the links are double the size of the rest of the text, and they are aligned in the center of the window, as done in the example above.

You are not just limited to the above properties to style your links, you are free to use any properties you like.

Styling some links

Interestingly enough, the default styles used for the links are nearly the same as they were back in the early days of browsers in the mid-1990s. This is because users know and have come to expect this behaviour (if links were styled differently, it would confuse a lot of people). This doesn’t mean that you shouldn’t style links at all, just that you should not stray too far from the expected behaviour. You should at least:

  • Use underlining for links, but not for other things. If you don’t want to underline links, at least highlight them in some other way.
  • Make them react in some way when hovered/focused, and in a slightly different way when activated.

The default styles can be turned off/changed using the following CSS properties:

  • color for the text color.
  • cursor for the mouse pointer style, although you shouldn’t turn this off unless you’ve got a very good reason.
  • outline for the text outline (an outline is similar to a border, the only difference being that border takes up space in the box and an outline doesn’t; it just sits over the top of the background). The outline is a useful accessibility aid, so think carefully before turning it off; you should at least double up the styles given to the link hover state on the focus state too.

Now that we’ve looked at the default states in some detail, let’s look at a typical set of link styles. To start off with, we’ll write out our empty rulesets:

a:link {
}

a:visited {
}

a:focus {
}

a:hover {
}

a:active {
}

This order is important because the link styles build on one another, for example the styles in the first rule will apply to all the subsequent ones, and when a link is being activated, it is usually also being hovered over. If you put these in the wrong order, and you’re changing the same properties in each ruleset, things won’t work as you expect. To remember the order, you could try using a mnemonic like LoVFears HAte.

Now let’s add some more information to get this styled properly:

p {
  font-size: 2rem;
  text-align: center;
}

a:link {
  color: green;
}

a:visited {
  color: olive;
}

a:focus {
  background: orange;
}

a:hover {   
  background: yellow;
  text-decoration: underline red wavy;
}

a:active {
  background: red;
}

We’ll also provide some sample HTML to apply the CSS to:

<p>There are several browsers available, such as:</p>
<p><a href="https://www.mozilla.org/en-US/firefox/new/" target="_blank">Mozilla Firefox</a></p>
<p><a href="https://www.google.com/chrome/" target="_blank">Google Chrome</a></p>
<p><a href="https://www.apple.com/safari/" target="_blank">Apple Safari</a></p>

Putting the two together gives us this result:

There are several browsers available, such as:

Mozilla Firefox

Google Chrome

Apple Safari

Proposed exercise: Styling colors and text decoration

Create a new web page with at least 5 links pointing to your preferred websites. You must change the default colors and decorations so that each possible state (link, visited, focus, hover, active) has other color than the default one, and also another decoration, as done in the example above, where the “underline red wavy” text decoration is used.

As you have seen, you can change the color of the text using the color property, and the background color using the background property. Also remember you can have a look at the different colors here: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value. About the text decoration, remember it can be easily changed with the text-decoration property.

Including icons on links

Some developers include icons on links to provide more of an indicator as to what kind of content the link points to. Let’s look at a really simple example that adds an icon to external links (links that lead to other sites). Such an icon usually looks like a little arrow pointing out of a box (for this example, we’ll use this great example from icons8.com).

For this example, we will use the same HTML as before:

<p>There are several browsers available, such as:</p>
<p><a href="https://www.mozilla.org/en-US/firefox/new/" target="_blank">Mozilla Firefox</a></p>
<p><a href="https://www.google.com/chrome/" target="_blank">Google Chrome</a></p>
<p><a href="https://www.apple.com/safari/" target="_blank">Apple Safari</a></p>

And we will adjust the CSS code:

p {
  font-size: 2rem;
  text-align: center;
}

a {
  background: url('https://mdn.mozillademos.org/files/12982/external-link-52.png') no-repeat 100% 0;
  background-size: 2rem;
  padding-right: 2.5rem;
}

Putting the two together gives us this result:

There are several browsers available, such as:

Mozilla Firefox

Google Chrome

Apple Safari

So what’s going on here? We’ll skip the CSS related to the paragraphs, as it’s just the same information you’ve looked at before. The last rule however is interesting : here we are inserting a custom background image on external links using background shorthand instead of the individual properties. We set the path to the image we want to insert, specify no-repeat so we only get one copy inserted, and then specify the position as 100% of the way over to the right of the text content, and 0 pixels from the top.

We also use background-size to specify the size we want the background image to be shown at. So that you get a good result, it is useful to have a larger icon and then resize it like this as needed for responsive web design purposes.

Finally, we set some padding-right on the links to make space for the background image to appear in, so we aren’t overlapping it with the text.

Proposed exercise: Links with icons

Create a new web page with at least 5 links pointing to your preferred websites (you can use the code you wrote before) and include an icon at the end of each one. Also change some other styles as the text color or text decoration.

Styling links as buttons

The tools you’ve explored so far in this article can also be used in other ways. For example, states like hover can be used to style many different elements, not just links — you might want to style the hover state of paragraphs, list items, or other things.

In addition, links are quite commonly styled to look and behave like buttons in certain circumstances — a website navigation menu is usually marked up as a list containing links, and this can be easily styled to look like a set of control buttons or tabs that provide the user with access to other parts of the site. Let’s explore how.

First, some HTML:

<ul class="navbar">
  <li><a href="#">Home</a></li>
  <li><a href="#">News</a></li>
  <li><a href="#">Contact</a></li>
  <li><a href="#">About</a></li>
</ul>

And now our CSS:

/* General class for navigation bar */
.navbar {
  font-size: 1.25rem;
  font-family: sans-serif;
  text-align: center;
  background: yellow;
}

/* Select only the list items (li) inside the navigation bar */
ul.navbar li {
  display: inline;
}

/* Select only the links (a) inside the navigation bar */
ul.navbar a {
  text-decoration: none;
  display: inline-block;
  padding: 1rem;
}

/* Select only the links inside the navigation bar when mouse over them (a:hover) */
ul.navbar a:hover {     
  background: orange;
}

/* Select only the links inside the navigation bar when mouse pressing (a:active) */
ul.navbar a:active {
  color: white;
  background: red;
}

This gives us the following result:

Let’s explain what’s going on here, focusing on the most interesting parts:

  • Creating a new class for the list:
    • We have created the navbar class so that only the items inside that class have the style of a navigation bar.
    • ul.navbar: By putting this before any selector, the CSS properties between braces will apply only to the items inside an unordered list with class navbar.
  • The styles related to the <ul> element:
    • We change the font-size to enlarge the text a little bit.
    • We change the font-family so that it is different from the rest of the text.
    • We change the text-align so that the links are centered .
    • We change the background to yellow color.
    • <li> elements will normally behave like block elements (they sit on their own lines). In this case, we want to create a horizontal list of links, so we set the display property to inline, which causes the list items to sit on the same line one after the other (they now behave like inline elements).
  • The styles related to the <a> elements:
    • We start by turning off the default text-decoration (we don’t want those spoiling our look).
    • Next, we set the display to inline-block (this will allow to size them, as we will explain in another unit).
    • We set the padding to 1rem to give the buttons some space around the text (we will also explain this in another unit).
    • We also change the color of the items when the mouse is over them, and when they are being pressed.

Proposed exercise: Navigation bar

Create a web page with a navigation bar of your own style. You can change any properties you like (colors, text decoration, font size, font family, etc.). After that, just below insert some other links you like. You can also try to use some other CSS rules to style these links, as shown below:

There are several browsers available, such as:

Mozilla Firefox

Google Chrome

Apple Safari