Tìm hiểu về Phaser

1.Phaser là gì?

Phaser là một HTML5 game framework mã nguồn mở. Phaser sử dụng Pixi.js để rendering trên WebGL và Canvas , Phaser hỗ trợ các trình duyệt web trên cả desktop và mobile. Game được phát triển bởi Phaser có thể dễ dàng được biên soạn thành ứng dụng cho iOS, Android và desktop thông qua các công cụ của bên thứ ba. Phaser là một framework cho HTML5 nên có thể viết bằng JavaScript hoặc TypeScript.

Hiện tại Phaser đã có Phaser 2 và 3, version mới nhất của phaser 2 là 2.6.2. Phaser 3 đã ra mắt tuy nhiên not yet ready for production use

Cùng với cộng đồng mã nguồn mở, Phaser được tích cực phát triển và duy trì bởi Photon Storm. Nhờ việc hỗ trợ nhanh chóng và xâu dựng một API thân thiện với người phát triển, Phaser hiện là một trong những most starred game frameworks trên GitHub.

2.Cài đặt Phaser

Có 2 cách để install Phaser: Sử dụng bower (hoặc npm) và dùng CDN

  • Bower / npm

      bower install phaser
    
      npm install phaser
    
  • CDN: Chỉ cần thêm 1 trong 2 dòng sau vào file html

      <script src="//cdn.jsdelivr.net/phaser/2.6.2/phaser.js"></script>
    
      <script src="//cdn.jsdelivr.net/phaser/2.6.2/phaser.min.js"></script>
    

Webpack

Bắt đầu từ Phaser 2.4.5, người dùng có thể sử dụng Webpack Khi đó p2 trở thành một dependency.

Webpack Config

var path = require('path');
var webpack = require('webpack');

var phaserModule = path.join(__dirname, '/node_modules/phaser/');
var phaser = path.join(phaserModule, 'build/custom/phaser-split.js'),
  pixi = path.join(phaserModule, 'build/custom/pixi.js'),
  p2 = path.join(phaserModule, 'build/custom/p2.js');

module.exports = {
    ...
    module: {
        loaders: [
            { test: /pixi.js/, loader: "script" },
        ]
    },
    resolve: {
        alias: {
            'phaser': phaser,
            'pixi.js': pixi,
            'p2': p2,
        }
    }
    ...
}

Main js file

require('pixi.js');
require('p2');
require('phaser');

Game Breakout

Breakout là game có luật chơi đơn giản:

  • Có 4 hàng x 15 viên gạch(break)
  • Có một thanh ngang(paddle) và một quả bóng đặt ở trên(ball)
  • Khi người chơi click thì quả bóng sẽ bay lên. Mỗi khi quả bóng chạm vào một viên gạch thì viên gạch đó sẽ biến mất
  • Người chơi phải dùng thanh ngang đỡ quả bóng khi nó đang rơi xuống
  • Khi tất cả breaks biến mất thì sẽ qua bàn mới. Khi người chơi mất hết mạng tức là làm rơi tất cả số bóng thì game over

Trong code phaser game Breakout có 3 func chính:

  • preload: load các assets cần thiết
  • create: được chạy 1 lần, khởi tạo và thiết lập cấu hình
  • update: sẽ được chạy sau một khoảng delta cố định để cập nhật thay đổi

Preload

Ở bước này thì đơn thuần là load asset cho game: background, các hình ảnh của bricks, ball, paddle

function preload() {
    game.load.atlas('breakout', 'assets/games/breakout/breakout.png', 'assets/games/breakout/breakout.json');
    game.load.image('starfield', 'assets/misc/starfield.jpg');
}

Create

Tạo backgound từ một image, starfield là image ta đã khai báo ở preload

    s = game.add.tileSprite(0, 0, 800, 600, 'starfield');

Tạo 4 hàng x 15 bricks

    bricks = game.add.group();
    bricks.enableBody = true;
    bricks.physicsBodyType = Phaser.Physics.ARCADE;

    var brick;

    for (var y = 0; y < 4; y++)
    {
        for (var x = 0; x < 15; x++)
        {
            brick = bricks.create(120 + (x * 36), 100 + (y * 52), 'breakout', 'brick_' + (y+1) + '_1.png');
            brick.body.bounce.set(1);
            brick.body.immovable = true;
        }
    }

Tạo paddle và ball


    paddle = game.add.sprite(game.world.centerX, 500, 'breakout', 'paddle_big.png');
    paddle.anchor.setTo(0.5, 0.5);

    game.physics.enable(paddle, Phaser.Physics.ARCADE);

    paddle.body.collideWorldBounds = true;
    paddle.body.bounce.set(1);
    paddle.body.immovable = true;

    ball = game.add.sprite(game.world.centerX, paddle.y - 16, 'breakout', 'ball_1.png');
    ball.anchor.set(0.5);
    ball.checkWorldBounds = true;
     game.physics.enable(ball, Phaser.Physics.ARCADE);

    ball.body.collideWorldBounds = true;
    ball.body.bounce.set(1);

    ball.animations.add('spin', [ 'ball_1.png', 'ball_2.png', 'ball_3.png', 'ball_4.png', 'ball_5.png' ], 50, true, false);

Thêm sự kiện khi ball bị rơi ra ngoài paddle tức là người chơi dùng thanh ngang đỡ trượt khi quả bóng rơi xuống. Lúc nàyta giảm lives xuống, kiểm tra nếu lives = 0 thì game over, không thì ta reset, đặt bóng quay trở lại paddle ball.reset(paddle.body.x + 16, paddle.y - 16);

    ball.events.onOutOfBounds.add(ballLost, this);
    function ballLost () {

    lives--;
    livesText.text = 'lives: ' + lives;

    if (lives === 0)
    {
        gameOver();
    }
    else
    {
        ballOnPaddle = true;

        ball.reset(paddle.body.x + 16, paddle.y - 16);
        
        ball.animations.stop();
    }

}

Thêm event khi ball rơi xuống tiếp xúc với paddle. Lúc này ta chỉ cẩn set velocity để ball khi chạm vào paddle thì bật ngược lại

    game.input.onDown.add(releaseBall, this);
    function releaseBall () {

        if (ballOnPaddle)
        {
            ballOnPaddle = false;
            ball.body.velocity.y = -300;
            ball.body.velocity.x = -75;
            ball.animations.play('spin');
            introText.visible = false;
        }

    }

Update

function update () {
    paddle.x = game.input.x;
    if (paddle.x < 24)
    {
        paddle.x = 24;
    }
    else if (paddle.x > game.width - 24)
    {
        paddle.x = game.width - 24;
    }

    if (ballOnPaddle)
    {
        ball.body.x = paddle.x;
    }
    else
    {
        game.physics.arcade.collide(ball, paddle, ballHitPaddle, null, this);
        game.physics.arcade.collide(ball, bricks, ballHitBrick, null, this);
    }

}

Nếu toàn bộ các bricks đã bị vỡ thì sang level mới và đưa ball về đặt trên paddle

game.physics.arcade.collide(ball, bricks, ballHitBrick, null, this);
function ballHitBrick (_ball, _brick) {

    _brick.kill();
    score += 10;
    scoreText.text = 'score: ' + score;

    if (bricks.countLiving() == 0)
    {
        score += 1000;
        scoreText.text = 'score: ' + score;
        introText.text = '- Next Level -';

        ballOnPaddle = true;
        ball.body.velocity.set(0);
        ball.x = paddle.x + 16;
        ball.y = paddle.y - 16;
        ball.animations.stop();

        bricks.callAll('revive');
    }
}