JavaScriptでオブジェクト指向なゲームづくり(1)

JavaScript

こんにちは。
ECF Techブログ
担当 Michiharu.Tです。

JavaScriptゲームフレームワーク、Phaserを使ったプログラミングの第3回です。今回は、前回作ったプログラムをオブジェクト指向プログラミングっぽくリメイクしていきます。

本記事は前回の続編の位置づけとなります。前回の記事は下記からご確認ください。

JavaScriptゲームフレームワーク Phaserで遊ぼう! Part2
こんにちは。 ECF Techブログ 担当 Michiharu.Tです。 前回に引き続き、JavaScriptの2Dゲームフレームワーク Phaserを使ったプログラムをご紹介したいと思います。 今回は物理エンジンを活用して、2Dアクション...

また、使用している魔法使いの画像はぴぽや倉庫さんの素材を利用しています。素晴らしい2D向け素材が満載です。

ぴぽや倉庫
はじめに

JavaScriptはOOPに特化した言語ではないのでやや工夫が必要になりますが、JavaScriptでのオブジェクト指向的プログラミングの手法も併せて解説できればと思っています。よろしくお願いします。

対象読者

  • JavaScriptの基本的なプログラミング経験者
  • オブジェクト指向プログラミングの基本を知っている方
  • Webの2Dゲームフレームワークを使ってみたい方

JavaScriptによるオブジェクト指向プログラミング

まずは簡単な例で、JavaScriptによるオブジェクト指向プログラミングを見ていきたいと思います。

下はクラス(もどき)の定義例です。

Player = function(name){
    this.name = name;
    this.hp = 10;
}
Player.prototype.sayName = function(){
    console.log( 'こんにちは' + this.name + 'です。');
}

namehpがフィールド、sayNameがメソッドとなります。

下のようなコードを実行することで、インスタンスの生成とそれぞれのインスタンスでのメソッド実行ができます。

var taro = new Player('タロウ');
var hanako = new Player('ハナコ');
//メソッドの実行
taro.sayName();
hanako.sayName();

実行結果

こんにちはタロウです。
こんにちはハナコです。

この実装方法が基本になります。

INFO
このオブジェクト指向プログラミングの実装方法は、MDN Web Docs(下記リンク)でご覧いただけます。Web技術の入門として大変オススメです。ぜひご覧ください。

MDN Web Docs
The MDN Web Docs site provides information about Open Web technologies including HTML, CSS, and APIs for both Web sites and progressive web apps.

アクションゲームの作成

クラスの作成

それではまず、プレイヤーのクラスを作成します。クラス名はPlayerとします。コードは次のとおりです。

/*--------------------------------------------*/
// Playerクラス
/*--------------------------------------------*/
Player = function (phaser) {
    //アニメーションの構築
    phaser.anims.create({       //左移動
        key: 'witch_left',
        frames: phaser.anims.generateFrameNumbers('witch', { start: 3, end: 5}),
        frameRate: 5,
        repeat: -1
    });
    phaser.anims.create({       //右移動
        key: 'witch_right',
        frames: phaser.anims.generateFrameNumbers('witch', { start: 6, end: 8 }),
        frameRate: 5,
        repeat: -1
    });
    //Phaserスプライトの設定
    this.pSprite = phaser.physics.add.sprite(400, 300, 'witch');
    //画面の端を壁と判断する設定
    this.pSprite.setCollideWorldBounds(true);
}
  • 引数phaserは、Phaserフレームワークの機能を利用するためのオブジェクトです。
  • phaserのメソッドを利用し、アニメーションの設定を行なっています。
  • phaserのメソッドを利用し、スプライトの作成とスプライトへの物理演算情報の設定を行なっています。

つづいてPlayerのメソッドを追加します。

/**
 * 左移動
 */
Player.prototype.goLeft = function () {
    this.pSprite.setVelocityX(-100);
    this.pSprite.anims.play('witch_left',true);
}
/**
 * 右移動
 */
Player.prototype.goRight = function () {
    this.pSprite.setVelocityX(100);
    this.pSprite.anims.play('witch_right',true);
}
/**
 * 停止
 */
Player.prototype.stand = function () {
    this.pSprite.setVelocityX(0);
}
/**
 * ジャンプ
 */
Player.prototype.jump = function () {
    this.pSprite.setVelocityY(-300);
}
  • 左右移動、停止、ジャンプの4つのメソッドを作成しています。
  • this.pSpriteはphaserのスプライトですので、各メソッドが利用できます。setVelocityXsetVelocityYはそれぞれの方向へ働く力です。
  • 左右移動ではアニメーションを開始します。

シーンの作成

シーン(scene)は、ゲームにおける各画面を定義するオブジェクトです。タイトル画面、プレイ画面、キャラクター選択画面などはすべて1つ1つのシーンとなります。

今回のプレイ画面となるシーンは次のようになります。

class Playing extends Phaser.Scene {
    constructor() {
        super('Playing');
    }
    preload() {
        //背景の画像
        this.load.image('back', 'assets/back.png');
        //魔法使い
        this.load.spritesheet('witch', 'assets/witch.png',
            { frameWidth: 32, frameHeight: 32 }
        );
    }
    create = function () {
        //背景の設定
        this.add.image(400, 300, 'back');
        //プレイヤースプライトの作成
        MyObj.player = new Player(this,400,300);
    }
    update = function () {
        //左キーが押された時
        if (MyObj.cursor.left.isDown)
        {
            MyObj.player.goLeft();
            player.anims.play('left', true);
        }
        //右キーが押された時
        else if (MyObj.cursor.right.isDown)
        {
            MyObj.player.goRight();
            player.anims.play('right', true);
        }
    }
}
  • JavaScriptのクラス構文を用いています。Phaser.Sceneを継承して作成する必要があります。
  • preloadcreateupdateの3つの関数を実装します。
  • preload関数では必要な画像を読み込みます。プレイヤーとなる魔法使いと背景の画像を読み込んでいます。
  • createは、ゲーム内のオブジェクトを生成・登場させる部分です。phaserのメソッドを利用し、背景と魔法つかいを登場させています。new演算でPlayerオブジェクトを生成している点に注目です。
INFO
classextendsというオブジェクト指向らしいキーワードが登場していますが、最初に述べたとおりJavaScriptに明確にクラスという概念は存在しません。これらは糖衣構文(Sugar Syntax)と呼ばれるものです。こちらも詳細はMDN Web Docsなどをご覧ください。

ゲームの準備

次に起動の準備をしましょう。ゲームで扱うオブジェクトを入れておく変数とphaserのゲーム起動のプログラムを追加します。

//ゲーム内で取り扱うグローバルなオブジェクト入れ物
var MyObj = {}
//phaserゲームの起動
var GameMain = new Phaser.Game({
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    //物理エンジンの設定
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 200 },
            debug: false
        }
    },
    scene:[Playing]
});
  • MyObjはゲームに使うオブジェクトをまとめて入れておく入れ物です。
  • new Phaser.Game(以降の記述については、前回の記事にまとめておりますのでご覧ください。
  • scene:[Playing]は、実行するシーンを指定しています。

ソースコード全文

では、ここまでのソースコード全文を示します。動作確認用のHTMLもありますので、ぜひ動作させてみてください。

(classes.js)

/*--------------------------------------------*/
// Playerクラス
/*--------------------------------------------*/
Player = function (phaser) {
    //アニメーションの構築
    phaser.anims.create({        //左移動
        key: 'witch_left',
        frames: phaser.anims.generateFrameNumbers('witch', { start: 3, end: 5}),
        frameRate: 5,
        repeat: -1
    });
    phaser.anims.create({        //右移動
        key: 'witch_right',
        frames: phaser.anims.generateFrameNumbers('witch', { start: 6, end: 8 }),
        frameRate: 5,
        repeat: -1
    });
    //Phaserスプライトの設定
    this.pSprite = phaser.physics.add.sprite(400, 300, 'witch');
    //画面の端を壁と判断する設定
    this.pSprite.setCollideWorldBounds(true);
}
/**
 * 左移動
 */
Player.prototype.goLeft = function () {
    this.pSprite.setVelocityX(-100);
    this.pSprite.anims.play('witch_left',true);
}
/**
 * 右移動
 */
Player.prototype.goRight = function () {
    this.pSprite.setVelocityX(100);
    this.pSprite.anims.play('witch_right',true);
}
/**
 * 停止
 */
Player.prototype.stand = function () {
    this.pSprite.setVelocityX(0);
}
/**
 * ジャンプ
 */
Player.prototype.jump = function () {
    this.pSprite.setVelocityY(-300);
}

(game.js)

class Playing extends Phaser.Scene {
    constructor() {
        super('Playing');
    }
    preload() {
        //背景の画像
        this.load.image('back', 'assets/back.png');
        //魔法使い
        this.load.spritesheet('witch', 'assets/witch.png',
            { frameWidth: 32, frameHeight: 32 }
        );
    }
    create = function () {
        //背景の設定
        this.add.image(400, 300, 'back');
        //プレイヤースプライトの作成
        MyObj.player = new Player(this, 400, 300);
        //キーボードイベント取得オブジェクト
        MyObj.cursor = this.input.keyboard.createCursorKeys();
    }
    update = function () {
        //左キーが押された時
        if (MyObj.cursor.left.isDown)
        {
            MyObj.player.goLeft();
        }
        //右キーが押された時
        else if (MyObj.cursor.right.isDown)
        {
            MyObj.player.goRight();
        }
    }
}
//ゲーム内で取り扱うグローバルなオブジェクト入れ物
var MyObj = {}
//phaserゲームの起動
var GameMain = new Phaser.Game({
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    //物理エンジンの設定
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 200 },
            debug: false
        }
    },
    scene:[Playing]
});

(index.html)



    
        
        
    
    
    

動作確認

この時点でゲームを動かしてみると、次のような画面があらわれます。

次の動作を確認できます。

  • 魔法使いが画面の下まで落ちてとどまります。
  • 左右キーで左右移動ができます。歩いているアニメーションになっています。

ゲームの原形はこれで完成です。あとはゲーム内のオブジェクトを増やしていく。という作業がメインになってきます。

今回は、次の機能を実装していきます。

  • 地面の追加
  • 魔法使いのジャンプ・停止

地面の追加

地面は魔法つかいなどのキャラクターが乗る事のできるオブジェクトとして準備します。phaserのスプライト機能だけで実現できますので、クラスは作成しません。

Playingクラスのpreloadメソッド内に次の記述を追記します。

//地面の画像
this.load.image('ground', 'assets/ground.png');

Playingクラスのcreateメソッド内に次の記述を追記します。

//地面の作成
MyObj.grounds = this.physics.add.staticGroup();
MyObj.grounds.create(100,550,'ground');
MyObj.grounds.create(300,350,'ground');
MyObj.grounds.create(500,150,'ground');
MyObj.grounds.create(700, 350, 'ground');

魔法使いのジャンプ・停止

メソッドはすでに用意していますので、キーボード操作の部分を追加します。

Playingクラスのupdateメソッド内、else if文の続きとして、次を追加します。

//左右いずれのキーも押されていない
else
{
    MyObj.player.stand();
}
//上キーが押されたらジャンプ(接地しているときのみ)
if (MyObj.cursor.up.isDown && MyObj.player.pSprite.body.touching.down)
{
    MyObj.player.jump();
}

ソースコード全体の表示

あらためて、変更のあったgame.jsのコード全体を示します。

class Playing extends Phaser.Scene {
    constructor() {
        super('Playing');
    }
    preload() {
        //背景の画像
        this.load.image('back', 'assets/back.png');
        //魔法使い
        this.load.spritesheet('witch', 'assets/witch.png',
            { frameWidth: 32, frameHeight: 32 }
        );
        //地面の画像
        this.load.image('ground', 'assets/ground.png');
    }
    create = function () {
        //背景の設定
        this.add.image(400, 300, 'back');
        //プレイヤースプライトの作成
        MyObj.player = new Player(this, 400, 300);
        //地面の作成
        MyObj.grounds = this.physics.add.staticGroup();
        MyObj.grounds.create(100,550,'ground');
        MyObj.grounds.create(300,350,'ground');
        MyObj.grounds.create(500,150,'ground');
        MyObj.grounds.create(700, 350, 'ground');
        //魔法使いと地面は衝突する関係にあることを設定
        this.physics.add.collider(MyObj.player.pSprite, MyObj.grounds);

        //キーボードイベント取得オブジェクト
        MyObj.cursor = this.input.keyboard.createCursorKeys();
    }
    update = function () {
        //左キーが押された時
        if (MyObj.cursor.left.isDown)
        {
            MyObj.player.goLeft();
        }
        //右キーが押された時
        else if (MyObj.cursor.right.isDown)
        {
            MyObj.player.goRight();
        }
        //左右いずれのキーも押されていない
        else
        {
            MyObj.player.stand();
        }
        //上キーが押されたらジャンプ(接地しているときのみ)
        if (MyObj.cursor.up.isDown && MyObj.player.pSprite.body.touching.down)
        {
            MyObj.player.jump();
        }
    }
}
//ゲーム内で取り扱うグローバルなオブジェクト入れ物
var MyObj = {}
//phaserゲームの起動
var GameMain = new Phaser.Game({
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    //物理エンジンの設定
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 200 },
            debug: false
        }
    },
    scene:[Playing]
});

実行結果は下の画面のようになります。

次のことが確認できます。

  • 地面が用意され、魔法使いが乗れる
  • 魔法使いはジャンプと停止ができる

おわりに

本日は以上とさせていただきます。最後までありがとうございました。実装内容は前回と同じところまでですが、オブジェクト指向プログラミングを取り入れた実装方法に変更することで、今後の拡張が容易になると思います。次回は敵キャラクターを登場させていきたいと思います。引き続き、よろしくお願いします。


合同会社イー・シー・エフでは、子ども向けプログラミングなどの教育講座を実施しています。プログラミング教室の案内や教育教材の情報、また関連するご相談・問い合わせにつきましては下記よりご確認ください。

ECFエデュケーション
タイトルとURLをコピーしました