LoginSignup
18
21

More than 3 years have passed since last update.

Phaser3.JSでゲームを作ってみた

Last updated at Posted at 2019-10-07

Phaser3.JSで簡単なゲームを作ってみました

JavaScriptベースのゲームエンジンであるPhaser3.JSを使用した簡単なゲームを作ってみましたので、その作成手順をステップバイステップで説明します。 なお、Windows10の環境において Microsoft Edge (Ver. 11.0.17763.379), Firefox (Ver. 65.0.2/64 bit) 及び Google Chrome (Ver. 73.0.3683.86/64 bit) で動作を確認しました。 しかしながら、タッチ・イベントは未実装ですのでAndroidやiOSでは動作しませんので悪しからず。
また、ここで使用しているグラフィックの一部に「​どらぴか様」ならびに「ぴぽや様」作成の素材をサウンドエフェクトには「効果音ラボ様」作成の素材を使用させていただきました。 フリー素材を提供して下さった各位に感謝いたします。

ファイル構成

ここで説明する内容に関するファイルは全て「GitHub Phaser3.JS_Sample_games」からダウンロード可能です。 また、そのファイル構成は次の通りです。
・ images: 本ゲームで使用するグラフィックスを収容するフォルダーです。
・ js: ゲーム・プログラム本体を収納するフォルダーです。
 - maze02.js: Step-7で使用する迷路作成プログラムです。
 - phaser.js, phaser.min.js, phaser-debug.min.js, phlog.js: Phaser3.jsゲームエンジン本体と関連ファイルで、Phaser3.jsからダウンロードしたものです。
 - sample_phaser_01.js ~ sample_phaser_07_with_sound_effect.js: 各ステップ用のゲーム・プログラムです。
 - stages_01.js: Step-5, Step-6で使用するステージ・データです。
・ sound: サウンド・エフェクト用ファイルを収容するフォルダーです。
・ index_Phaser_01.html ~ index_Phaser_07.html: 各ステップでのGame開始用HTMLファイルです。

ゲーム作成の説明

以下にゲーム作成の手順をステップバイステップで説明します。

Step-1: 静止画グラフィックスを表示させる

始めにゲーム開始用のHTMLファイルを示します。 読込むJSファイルの設定等を行い、全てのステップでほぼ同一ですが、各ステップ毎にタイトルと「src="sample_phaser_01.js"」の部分を各ステップ番号に変更しています。
Step-1のデモ実行

index_Phaser_01.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Phaser Sample Game #01 by T. Fujita on 2019/9/11</title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>
<body>
    <div id="game"></div>
    <script src="./js/phaser.js"></script>
    <script src="./js/sample_phaser_01.js"></script>
</body>
</html>

次にメイン・プログラムである「sample_phaser_01.js」を示します。 背景画像とグラフィック(player)データを読み込み・表示します。

sample_phaser_01.js
// Sample for phaser.js by T. Fujita on 2019/9/11 (Display a static character)

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    physics: {
    default: 'arcade',
    arcade: {
        debug: false
    }
    },
    scene: {
    preload: preload,
    create: create,
    update: update
    }
};

var scale = 1;
var P_size = 48 * scale;
var player;
var cursors;

var game = new Phaser.Game(config);

function preload ()
{
        this.load.image('bg', './images/bg.jpg');
        this.load.spritesheet('Player', './images/hukei_01.png', { frameWidth: 48, frameHeight: 48 });
}

function create ()
{
        this.add.image(800, 600, 'bg');
        player = this.physics.add.sprite(400, 300, 'Player');
}

function update ()
{

}

Step-2: 静止画グラフィックスを移動させてみる

前述の「sample_phaser_01.js」にマウスをクリックした位置あるいはキー入力の検知を追加し、それに合わせてグラフィック(player)を移動させるものが「sample_phaser_02.js」です。 追加・修正個所を以下に示します。
Step-2のデモ実行

sample_phaser_02.js(追加・修正部分)
function create ()
{
        this.add.image(800, 600, 'bg');
        player = this.physics.add.sprite(400, 300, 'Player');

        cursors = this.input.keyboard.createCursorKeys();
    this.input.mouse.capture = true;
}

function update ()
{
        if (cursors.left.isDown)
        {
            player.setVelocityX(-1 * speed);

        }
        else if (cursors.right.isDown)
        {
            player.setVelocityX(speed);

        }
        else if (cursors.up.isDown)
        {
            player.setVelocityY(-1 * speed);

        }
        else if (cursors.down.isDown)
        {
            player.setVelocityY(speed);

        }
    else if (game.input.mousePointer.isDown)
    {
        if (Math.floor(player.x / P_size) < Math.floor(game.input.mousePointer.x / P_size))
        {
        player.setVelocityX(speed);
        }
        else if (Math.floor(player.x / P_size) > Math.floor(game.input.mousePointer.x / P_size))
        {
        player.setVelocityX(-1 * speed);
        }
        if (Math.floor(player.y / P_size) < Math.floor(game.input.mousePointer.y / P_size))
        {
        player.setVelocityY(speed);
        }
        else if (Math.floor(player.y / P_size) > Math.floor(game.input.mousePointer.y / P_size))
        {
        player.setVelocityY(-1 * speed);
        }
    }
        else
        {
            player.setVelocityX(0);
            player.setVelocityY(0);
       }
}

Step-3: アニメーション・グラフィックスを表示させる

前述の「sample_phaser_01.js」を元にplayerをアニメーションさせる「sample_phaser_03.js」を作成します。 追加・変更部分を次に示します。 Phaser3.JSでは、歩行グラフィックス等のアニメーションには横に連続したグラフィクスが使用できます。
Step-3のデモ実行

sample_phaser_03.js(追加・修正部分)
function create ()
{
        this.add.image(800, 600, 'bg');
        player = this.physics.add.sprite(400, 300, 'Player');

        this.anims.create({
            key: 'down',
            frames: this.anims.generateFrameNumbers('Player', { start: 0, end: 2 }),
            frameRate: f_rate,
            repeat: -1
        });
}

function update ()
{
    player.setVelocityX(0);
    player.setVelocityY(0);
    player.anims.play('down', true);
}

Step-4: アニメーション・グラフィックスを移動させてみる

前述の「sample_phaser_02.js」と「sample_phaser_03.js」を組み合わせると共に前後(上下)左右それぞれの歩行グラフィックスを追加したものが「sample_phaser_04.js」です。 追加の歩行分を以下に示します。
Step-4のデモ実行

sample_phaser_04.js(追加・修正部分)
function create ()
{
        this.add.image(800, 600, 'bg');
        player = this.physics.add.sprite(400, 300, 'Player');

        this.anims.create({
            key: 'left',
            frames: this.anims.generateFrameNumbers('Player', { start: 3, end: 5 }),
            frameRate: f_rate,
            repeat: -1
        });
        this.anims.create({
            key: 'right',
            frames: this.anims.generateFrameNumbers('Player', { start: 6, end: 8 }),
            frameRate: f_rate,
            repeat: -1
        });
        this.anims.create({
            key: 'up',
            frames: this.anims.generateFrameNumbers('Player', { start: 9, end: 11 }),
            frameRate: f_rate,
            repeat: -1
        });
        this.anims.create({
            key: 'down',
            frames: this.anims.generateFrameNumbers('Player', { start: 0, end: 2 }),
            frameRate: f_rate,
            repeat: -1
        });

        cursors = this.input.keyboard.createCursorKeys();
        this.physics.add.collider(player);

}

function update ()
{
        if (cursors.left.isDown)
        {
            player.setVelocityX(-1 * speed);
            player.anims.play('left', true);
        }
        else if (cursors.right.isDown)
        {
            player.setVelocityX(speed);
            player.anims.play('right', true);
        }
        else if (cursors.up.isDown)
        {
            player.setVelocityY(-1 * speed);
            player.anims.play('up', true);
        }
        else if (cursors.down.isDown)
        {
            player.setVelocityY(speed);
            player.anims.play('down', true);
        }
    else if (game.input.mousePointer.isDown)
    {
        if (Math.floor(player.x / P_size) < Math.floor(game.input.mousePointer.x / P_size))
        {
        player.setVelocityX(speed);
            player.anims.play('right', true);
        }
        else if (Math.floor(player.x / P_size) > Math.floor(game.input.mousePointer.x / P_size))
        {
        player.setVelocityX(-1 * speed);
            player.anims.play('left', true);
        }
        if (Math.floor(player.y / P_size) < Math.floor(game.input.mousePointer.y / P_size))
        {
        player.setVelocityY(speed);
            player.anims.play('down', true);
        }
        else if (Math.floor(player.y / P_size) > Math.floor(game.input.mousePointer.y / P_size))
        {
        player.setVelocityY(-1 * speed);
            player.anims.play('up', true);
        }
    }
        else
        {
            player.setVelocityX(0);
            player.setVelocityY(0);
        }
}

Step-5: 壁等を表示させ、移動を制限する

「sample_phaser_04.js」に「stages_01.js」で設定する各ステージを読み込みタイルデータを表示させたものが「sample_phaser_05.js」です。 ここでは、壁を通り抜け出来ない設定とゴール、再度実行(Aタイル)のみ対応するようにしています。
Step-5のデモ実行

sample_phaser_05.js(タイルデータ表示部分)
    for (var i=0; i<ROOM.length; i++) {
        temp[i] = [];
        for (var j=0; j<ROOM[i].length; j++) {
        temp[i][j] = ROOM[i].substr(j,1);
        if(ROOM[i].substr(j,1) == "P") {
            player = this.physics.add.sprite(j * P_size, i * P_size, 'Player').setOrigin(0, 0).setScale(scale);
        }
        else if(ROOM[i].substr(j,1) == "A") { 
            again = this.add.image(j * P_size, i * P_size, 'Again').setOrigin(0, 0).setScale(scale);
        }
        else if(ROOM[i].substr(j,1) == "G") { 
            goal = this.physics.add.sprite(j * P_size, i * P_size, 'Goal').setOrigin(0, 0).setScale(scale);
        }
        else if(ROOM[i].substr(j,1) == "B") { 
            BLOCK[BL_counter] = this.physics.add.sprite(j * P_size, i * P_size, 'Block').setOrigin(0, 0).setScale(scale);
            BL_counter = BL_counter + 1;
        }
        else if(ROOM[i].substr(j,1) == "w") { 
            wall_0 = this.physics.add.sprite(j * P_size, i * P_size, 'Wall_0').setOrigin(0, 0).setScale(scale);
        }
        else if(ROOM[i].substr(j,1) == "W") { 
            wall_1 = this.physics.add.sprite(j * P_size, i * P_size, 'Wall_1').setOrigin(0, 0).setScale(scale);
        }
        }
    }
sample_phaser_05.js(ゴール対応部分)
// Process for reaching the goal
function Check_Goal() {
    if((pos[px][py] == "G") && (Goal_flag == 0)) {
        Goal_flag = 1;
        End_flag = "Goal !";
        counter = counter + 1;
        game.scene.start('Next_Game');
    }
}


//Scene_NextGame
class Scene_NextGame extends Phaser.Scene {
    constructor (){
          super({ key: 'Next_Game' });
    }

    create(){
    let sceneName01 = this.add.text(400, 150, End_flag).setFontSize(30).setFontFamily("Arial").setOrigin(0.5).setColor("#FF0000").setInteractive();
    let sceneName02 = this.add.text(400, 250, 'ここで使用しているグラフィクスの一部に「どらぴか」様 \n URL: https://dorapika.wixsite.com/pikasgame \n作成の素材を使用しております。').setFontSize(20).setFontFamily("Arial").setOrigin(0.5).setColor("#FF0000").setInteractive();
    let change = this.add.text(400, 350, 'Push Here for Continue !').setFontSize(30).setFontFamily("Arial").setColor("#FF0000").setOrigin(0.5).setInteractive();

    change.on('pointerdown', function (pointer) {
        sceneName01.destroy();
        sceneName02.destroy();
        change.destroy();
        game.scene.start('Game_Start');
    }, this);
   }
};

各ステージを構成する「stage_01.js」の一部を以下に示します。 メモ帳等のエディターで修正・追加してみてください。

stage_01.js
// All the rooms are here. 
// by T. Fujita
// 
// A: Again
// B: Block(Movable)
// F: Floor
// G: Goal
// P: Player
// W: Wall

var room = [];
    room[0] = [ "WwwwwGwW",
        "WFFFFFFW",
        "WFFFFFFW",
        "WFPFFFFW",
        "WFFFFFFW",
        "WwwwwwwA"]

    room[1] = [ "WwwwwGwW",
        "WFFFFBFW",
        "WFFFFFFW",
        "WFPFFFFW",
        "WFFFFFFW",
        "WwwwwwwA"]

    room[2] = [ "WwwwwwwwwwwwwGwW",
        "WFFFFFFFFFFFFFFW",
        "WBBBBBBBBBBBBBBW",
        "WFFFFFFFFFFFFFFW",
        "WFFFFFFFFFFFFFFW",
        "WFFFFFFFFFFFFFFW",
        "WFFFFFFFFFFFFFFW",
        "WFFFFFFFFFFFFFFW",
        "WFFPFFFFFFFFFFFW",
        "WFFFFFFFFFFFFFFW",
        "WFFFFFFFFFFFFFFW",
        "WwwwwwwwwwwwwwwA"]

Step-6: ボックスを押して移動可能にさせる

「sample_phaser_06.js」でplayerがBoxを押して移動できるようにしました。 これでゲームの基本部分は完成です。
Step-6のデモ実行

sample_phaser_06.js(左側の壁対応にBOX移動を追加した部分)
        if( flag == "left") {
            player.anims.play('left', true);
        if((pos[px - 1][py] =="w") ||(pos[px - 1][py] =="W")) {
            player.x = px * P_size;
            player.y = py * P_size;
        } else if((pos[px - 1][py] =="B") && (pos[px - 2][py] =="F")) {
            pos[px][py] = "F";
            pos[px - 1][py] = "P";
            pos[px - 2][py] = "B";
            for(var i=0; i<BLOCK.length; i++) {
            if((Math.floor(BLOCK[i].x / P_size)  == (px - 1)) && (Math.floor(BLOCK[i].y / P_size) == py)) {
                BL_counter = i;
            }
            }
            nextBX = px * P_size - P_size * 2;
            nextBY = py * P_size;
            this.tweens.add({
            targets: BLOCK[BL_counter],
            x: nextBX,
            y: nextBY,
            duration: speed,
            }, this);
            nextX = px * P_size - P_size;
            nextY = py * P_size;
            this.tweens.add({
            targets: player,
            x: nextX,
            y: nextY,
            duration: speed,
            }, this);
        } else if ((pos[px - 1][py] =="B") && (pos[px - 2][py] !="F")) {
            player.x = px * P_size;
            player.y = py * P_size;
        } else {
            if(pos[px - 1][py] != "G") {
            pos[px][py] = "F";
            pos[px - 1][py] = "P";
            }
            nextX = px * P_size - P_size;
            nextY = py * P_size;
            this.tweens.add({
            targets: player,
            x: nextX,
            y: nextY,
            duration: speed,
            }, this);
        }
        } else if( flag == "right") {

さらにゴール時の効果音を追加した「sample_phaser_06_with_sound_effect.js」を以下に示します。
Step-6効果音付きのデモ実行

sample_phaser_06_with_sound_effect.js(preload部分とcreate部分)
    preload ()
    {
        this.load.image('bg', './images/bg.jpg');
        this.load.spritesheet('Player', './images/hukei_01.png', { frameWidth: 48, frameHeight: 48 });
        this.load.image('Again', './images/A_48.png');
        this.load.image('Block', './images/Box_06.png');
        this.load.image('Goal', './images/Goal_00.png');
        this.load.image('Wall_0', './images/Block_04.png');
        this.load.image('Wall_1', './images/Block_05.png');

    this.load.audio('GOAL', ['./sound/info-girl1-goal1.mp3']);
    }

    create ()
    {
        this.add.image(800, 600, 'bg');
    wall_0  = this.physics.add.staticGroup();
    wall_1  = this.physics.add.staticGroup();
    goal_sound = this.sound.add('GOAL');
    End_flag = " ";
    BL_flag = 0;
sample_phaser_06_with_sound_effect.js(ゴール時の効果音を追加した部分)
// Process for reaching the goal
function Check_Goal() {
    if((pos[px][py] == "G") && (Goal_flag == 0)) {
        Goal_flag = 1;
        End_flag = "Goal !";
        counter = counter + 1;
        this.goal_sound.play();
        game.scene.start('Next_Game');
    }
}

Step-7: 迷路を作成しアニメーション・グラフィックスを移動させてみる

「sample_phaser_06_with_sound_effect.js」を若干変更し、「maze_02.js」で作成した迷路を表示させたものが「sample_phaser_07_with_sound_effect.js」です。 各グラフィックスのサイズは縦横共に1/2に設定しています。 迷路は、「Algoful : Algorithm for making a maze」のアルゴリズムを使用させていただきました。
Step-7のデモ実行

maze_02.js
// This program is based on "http://algoful.com/Archive/Algorithm/MazeDig" by Algoful.
// by T. Fujita

var maze = [];
var startCells = [];

var Maze = function(X, Y) 
{
    var w = X;                                  // 幅(奇数)
    var h = Y;                                  // 高さ(奇数)
    if(w < 11) {w = 11;}
    if(h < 11) {h = 11;}
    if(w % 2 == 0) {w = w - 1;}
    if(h % 2 == 0) {h = h - 1;}

    var x;
    var y;
    var results = [];

    var currentCells = [];

    for (x = 0; x < w; x++) {
    maze[x] = new Array();
    for (y = 0; y < h; y++) {
            if (x == 0 || y == 0 || x == w - 1 || y == h - 1) {
                maze[x][y] = "F";                       // 外周を通路に設定
            } else {
        maze[x][y] = "W";                       // 残りを壁に設定
        }
    }
    }

    startCells[0] = [];
    startCells[0][0] = (Math.floor(Math.random() * (w / 2 - 2))) * 2 + 1;
    startCells[0][1] = (Math.floor(Math.random() * (h / 2 - 2))) * 2 + 1;

    Dig(startCells[0][0], startCells[0][1]);

    for (x = 0; x < w; x++)
    {
    for (y = 0; y < h; y++)
    {
        if (x == 0 || y == 0 || x == w - 1 || y == h - 1)
        {
        maze[x][y] = "W";                       // 外周を壁に戻す
        }
    }
    }
    let i = 0;
    while (maze[i][1] == "W") {
    if(maze[i + 1][1] == "F") {
        maze[i + 1][1] = "P";
        break;
    }
    i = i + 1;
    }
    i = maze.length - 2;
    let j = maze[0].length - 1;
    while(maze[i][j] == "W") {
    if(maze[i - 1][j - 1] == "F") {
        maze[i - 1][j] = "G";
        break;
    }
    i = i - 1;
    }
    for (y = 0; y < h; y++) {
    results[y] = [];
    for (x = 0; x < w; x++) {
        results[y] = results[y] + maze[x][y];
    }
    }
// alert(results[0] + "\n" + results[1]);
    return results;
}


function Dig(x, y) 
{
  while (true) {
    var directions = [];
    if(maze[x][y-1] == "W" && maze[x][y-2] =="W") {directions.push("UP");}
    if(maze[x][y+1] == "W" && maze[x][y+2] =="W") {directions.push("DOWN");}
    if(maze[x-1][y] == "W" && maze[x-2][y] =="W") {directions.push("LEFT");}
    if(maze[x+1][y] == "W" && maze[x+2][y] =="W") {directions.push("RIGHT");}
    if(directions.length == 0) {break;}
    var rnd = Math.floor(Math.random() * directions.length);
    switch (directions[rnd])
    {
    case 'UP':
        SetPath(x, --y);
            SetPath(x, --y);
            break;
        case 'DOWN':
            SetPath(x, ++y);
            SetPath(x, ++y);
            break;
        case 'LEFT':
            SetPath(--x, y);
            SetPath(--x, y);
            break;
        case 'RIGHT':
            SetPath(++x, y);
            SetPath(++x, y);
            break;
    }
// console.log(maze[0] + "\n" + maze[1] + "\n" + maze[2] + "\n" + maze[3] + "\n" + maze[4] + "\n" + maze[5]);
    var cell = GetStartCell();
    if(cell != null)
    {
    Dig(cell[0], cell[1]);
    }
  }
}


function SetPath(x, y) 
{
    maze[x][y] = "F";
    if((x % 2 == 1) && (y % 2 == 1))
    {
    var Temp = [x, y];
    startCells.push(Temp);
    }
}


function GetStartCell()
{
    if(startCells.length == 0) {return null;}
    var rnd = Math.floor(Math.random() * startCells.length);
    var cell = startCells[rnd];
    startCells.splice(rnd, 1);
    return cell;
}

Reference

  1. github Phaser3.JS_Sample_games
  2. Phaser3.JS
  3. Home Page of PIKA's GAME
  4. Home Page of Pipoya
  5. Algoful : Algorithm for making a maze
  6. Home Page of soundeffect-lab

おまけ

JavaScriptベースのゲームエンジンは他にも多数ありますが、その内の幾つかで同様のゲームを作成してみました。 ソースファイルを覘くとゲームエンジン別の特徴がうかがえるかと思います。

以上

18
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
21