Sceneの取り扱い② Sceneの構造、Scene間の連携

今回はシーンの基本的な構造について紹介いたします。

preload, create, update

前回のシーン実行の記事では説明を省きましたが、 preload, create, updateはシーンの基本構造です。
これらを使って再生ボタンの周りを回転する円弧を作ってみましょう。

f:id:Phaser_3:20181120182032g:plain


    //別シーンからアクセスできるようにpublicにしておく
    public sprite: Phaser.GameObjects.Sprite;

    constructor() {
        //Sceneを拡張してクラスを作る際にコンストラクタでSceneの設定を渡します
        super({ key: 'MyScene1', active: true });
    }

    preload() {
        //画像の読み込み
        this.load.image('play', 'assets/images/play.png');
    }

    create() {

        //読み込んだ画像を表示
        this.add.sprite(this.sys.canvas.width / 2, this.sys.canvas.height / 2, "play");

        //PhaserのGraphicsで円弧を描く
        let graphics = this.add.graphics();
        graphics.lineStyle(4, 0xff00ff, 1);
        graphics.beginPath();
        graphics.lineStyle(10, 0x000000)
        graphics.arc(400, 300, 200, Phaser.Math.DegToRad(90), Phaser.Math.DegToRad(180), true);
        graphics.strokePath();

        //描いた円弧をテクスチャにする
        graphics.generateTexture("arc")

        //spriteとして表示するのでgraphicsを非表示に
        graphics.visible = false

        //テクスチャにしたgraphicsをスプライトにする
        this.sprite = this.add.sprite(this.sys.canvas.width / 2, this.sys.canvas.height / 2, "arc")

        //背景を白に
        this.cameras.main.setBackgroundColor('#ffffff')

    }

    update() {
        //毎フレーム実行される
        this.sprite.angle += 1
    }


preload : アセットの読み込み、シーンの前準備

preloadはシーンの起動時に最初に実行される関数です。
一度だけ実行され、この関数が終了するまでcreateやupdateは実行されません。
サンプルコードではassets/images内にあるplay.pngを読み込んでいます。 (素材はCMANさんからいただきました)

  preload() {
        //画像の読み込み
        this.load.image('play', 'assets/images/play.png');
    }

上記実行時点ではなにも表示されていませんね。 f:id:Phaser_3:20181120152412p:plain

create : ゲームオブジェクトの作成に適したタイミング

createはpreloadのあとに実行されます。
preloadの処理が終了するまでは呼ばれないため、 preloadでアセットを読み込み、createで読み込んだアセットを用いてゲームオブジェクトを作成する、
といった使い方ができます。 サンプルコードではPhaserのグラフィックス機能を用いて作成した円弧をgeneratetextureを用いてテクスチャ化し、スプライトを作成してpreloadで読み込んだ画像とともに表示しています。

    create() {

        //読み込んだ画像を表示
        this.add.sprite(this.sys.canvas.width / 2, this.sys.canvas.height / 2, "play");

        //PhaserのGraphicsで円弧を描く
        let graphics = this.add.graphics();
        graphics.lineStyle(4, 0xff00ff, 1);
        graphics.beginPath();
        graphics.lineStyle(10, 0x000000)
        graphics.arc(400, 300, 200, Phaser.Math.DegToRad(90), Phaser.Math.DegToRad(180), true);
        graphics.strokePath();

        //描いた円弧をテクスチャにする
        graphics.generateTexture("arc")

        //spriteとして表示するのでgraphicsを非表示に
        graphics.visible = false

        //テクスチャにしたgraphicsをスプライトにする
        this.sprite = this.add.sprite(this.sys.canvas.width / 2, this.sys.canvas.height / 2, "arc")

        //背景を白に
        this.cameras.main.setBackgroundColor('#ffffff')

    }

カメラを白背景にし、スプライトを追加したため、画像と黒い円弧が見えるようになりました。 f:id:Phaser_3:20181120182615p:plain

update : 毎フレーム実行、いわゆるゲームループ部分

updateはシーンが実行されている間、毎フレーム呼ばれる関数です。
ここにゲームオブジェクトを操作するための処理を書きます。
サンプルコードでは円弧を回転させるために円弧スプライトのangleに毎フレーム1を加算しています。

    update() {
        //毎フレーム実行される
        this.sprite.angle += 1
    }

これで冒頭のgifのように回転する円弧ができました。 f:id:Phaser_3:20181120182032g:plain

scene.getで他のシーンの中身を取得する

今度はもう一つシーンを作ってシーン間の連携を試してみましょう。

scene.getでシーンを取得する

scene.getを使うと他のシーンの要素を参照できます。
以下はMyScene1で作った円弧のangleを画面に表示するシーンのサンプルコードです。

class MyScene2 extends Phaser.Scene {
    
    private scene1: MyScene1;
    private text:Phaser.GameObjects.Text

    constructor() {
        //!!自動実行をfalseにしておく!!
        super({ key: 'MyScene2', active: false });
    }

    create() {
        //MyScene1のspriteを参照したいため取得したシーンをMyScene1にキャスト
        //constructorで指定したキーでシーンを取得
        this.scene1 = this.scene.get("MyScene1") as MyScene1;

        //MyScene1で作成したの原点を取得、Y軸をすこしずらす
        this.text = this.add.text(this.scene1.sprite.x, this.scene1.sprite.y + 140, '').setFontSize(64).setColor('#000');
        this.text.setOrigin(0.5)
    }

    update() {
        //円弧のangleをリアルタイムに取得する
        this.text.setText(this.scene1.sprite.angle.toFixed(0).toString())
    }

}

create内の

        this.scene1 = this.scene.get("MyScene1") as MyScene1;

というコードでシーンを取得しています。
scene.get("取得したいシーンのキー")でシーンを取得することができます。 MyScene1内のspriteにアクセスしやすいようにscene1をMyScene1にキャストしましょう。

※MyScene2はactiveを必ずfalseにしてください。
自動実行をオフにしておかないと、参照元のシーンではまだpreloadが終了していないのにMyScene2が実行され、作成されていないオブジェクトを参照するなどして大抵の場合エラーがおきます。
今回のサンプルコードの場合、参照したいシーン=MyScene1の最終行にscene.launchを追加して安全にMyScene2を実行しましょう。

class MyScene1 extends Phaser.Scene {
~~
    create() {
       ~~
        //ここでMyScene2をlaunch
        this.scene.launch("MyScene2")
    }
~~

}



これでMyScene1の要素にアクセスできるようになったため、 MyScene2内のupdateではMyScene1の円弧の角度をリアルタイムに取得して画面に出力しています。

今回のサンプルではangleを取得しているだけですが、getしたシーンの関数を使用することもできます。 f:id:Phaser_3:20181120183327g:plain

以上、今回はシーンに基本的な構造と連携について勉強しました。
まとめると、

  • preloadでアセットの読み込み、createでゲームオブジェクトの作成、updateで毎フレームの処理
  • scene.getで他シーンの要素にアクセスできる
    というところが要点になるでしょう。

以下が今回のサンプルコードの全文です。


class MyScene1 extends Phaser.Scene {

    //別シーンからアクセスできるようにpublicにしておく
    public sprite: Phaser.GameObjects.Sprite;

    constructor() {
        //Sceneを拡張してクラスを作る際にコンストラクタでSceneの設定を渡します
        super({ key: 'MyScene1', active: true });
    }

    preload() {
        //画像の読み込み
        this.load.image('play', 'assets/images/play.png');
    }

    create() {

        //読み込んだ画像を表示
        this.add.sprite(this.sys.canvas.width / 2, this.sys.canvas.height / 2, "play");

        //PhaserのGraphicsで円弧を描く
        let graphics = this.add.graphics();
        graphics.lineStyle(4, 0xff00ff, 1);
        graphics.beginPath();
        graphics.lineStyle(10, 0x000000)
        graphics.arc(400, 300, 200, Phaser.Math.DegToRad(90), Phaser.Math.DegToRad(180), true);
        graphics.strokePath();

        //描いた円弧をテクスチャにする
        graphics.generateTexture("arc")

        //spriteとして表示するのでgraphicsを非表示に
        graphics.visible = false

        //テクスチャにしたgraphicsをスプライトにする
        this.sprite = this.add.sprite(this.sys.canvas.width / 2, this.sys.canvas.height / 2, "arc")

        //背景を白に
        this.cameras.main.setBackgroundColor('#ffffff')

        //ここでMyScene2をlaunch
        this.scene.launch("MyScene2")
    }

    update() {
        //毎フレーム実行される
        this.sprite.angle += 1
    }
}

class MyScene2 extends Phaser.Scene {
    
    private scene1: MyScene1;
    private text:Phaser.GameObjects.Text

    constructor() {
        //自動実行をfalseにしておく
        super({ key: 'MyScene2', active: false });
    }

    create() {
        //MyScene1のspriteを参照したいため取得したシーンをMyScene1にキャスト
        //constructorで指定したキーでシーンを取得
        this.scene1 = this.scene.get("MyScene1") as MyScene1;

        //MyScene1で作成したの原点を取得、Y軸をすこしずらす
        this.text = this.add.text(this.scene1.sprite.x, this.scene1.sprite.y + 140, '').setFontSize(64).setColor('#000');
        this.text.setOrigin(0.5)
    }

    update() {
        //円弧のangleをリアルタイムに取得する
        this.text.setText(this.scene1.sprite.angle.toFixed(0).toString())
    }

}

var config: GameConfig = {
    parent: 'content',
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    scene: [MyScene1, MyScene2],

}

window.onload = () => {
    new Phaser.Game(config);
}