見出し画像

Phaser-サンプルゲーム作成まで-


PhaserはJSでhtml5出力できるゲームエンジンです。
ティラノでJSで改造できる人は特に問題なく扱えるかも。
組み方は、ゲームエンジンをDLしたら内部にindex.htmlを自分で作成して
そこからコードを打ち込むという形です。


てきとーに書いた奴なので話半分に見てください。



環境作成


必要なもの

Phaser本体
VSccode
VScodeプラグイン「LiveServer」

Phaserはローカルサーバーで起動します。
公式ページにはサーバーを借りる手順が書かれていますが、
LiveServerを入れれば同じことができます。

Phaserはオープンソースなので、GitHubからDLかクローンができます。
またはCLIで直接読み込ませることもできます。

クローンをすると、GitHubに載っているPhaserが更新されたとき、1ボタンで更新が完了します。ただし、GitHubのアカウントが必要。
CLIは解凍する手間が省けます。

DLはGitHubのメニューから。Zipを選んで解凍すればいいです。


DownloadZIPでできる。

解凍する

解凍して開くと、.githubやREADME.mdがあるフォルダ群があります。
ここにindex.htmlを作成して、それをVScodeで展開してLiveServerを使用します。

index.htmlは新規作成→テキストドキュメント→ファイル名と拡張子をindex.htmlに変更するのが多分一番楽です。

LiveServerを使う

VScodeで「フォルダを開く」(ctrl+kまたはctrl+o)から
index.htmlのあるフォルダ(上のとこ)を選択します。
直接index.htmlをVScodeでドラッグアンドドロップすると
正しいエクスプローラーにならないのでやらない方が無難です。

そこからindex.htmlを選んで開いたら

画面右下のGo Liveを押します。これでローカルサーバーで開くことが
できました。
Go Liveボタンがない場合は「LiveServer」プラグインをインストールしてください。VScodeで検索すれば出ます。

またはVScodeが起動したばかりで全ての読み込みが終わっていないかも。
3分ぐらい待ってから再度見てください。


これで作業環境は完成です。
今後は何も書かれていないindex.htmlにコードを打ち込んでいくことになります。

公式で用意されている「Phaserのタイトル画面を表示させる」という結果のコードをindex.htmlにコピペすることで、ローカルサーバー上で表示されます。
現在(23/08/03)、
素材のリンクが切れているので404エラーが出ると思いますが
公式的にも想定してるみたいで特に問題はないです。

こんなんが出たらOK.
画像読込ができないと自動でこうなるっぽい。かっくいい。



サンプルゲームの作り方

公式ページに作り方が書いてあります。
日本語はありませんが英語はあるので拡張機能などで
なんとかしてください。

またはREADME.mdに基本コードが書いてあります。

READMEと同じ内容がサイトにあります。

学び用のアセットも全部index.htmlがある場所で展開します。
もちろんコードのアドレスを変更すれば限った話ではないですが、デフォ設定がそうなってるのでそうした方が多分いいです。





コード

Phaserではティラノのタグのような役割のものを、JSの関数で表します。

基本コード

index.htmlに以下が記入されていることを前提とします。

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/phaser@3.15.1/dist/phaser-arcade-physics.min.js"></script>
</head>
<body>
 <script>
    ★ 
 </script>
</body>
</html>

★の部分に以下のJSを書き込みます。


基本コンフィグ

画面サイズと、作成する関数を宣言します。

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};

var game = new Phaser.Game(config);

function preload ()
{
}

function create ()
{
}

function update ()
{
}

gameオブジェクトを作成(ver game = ~)します。グローバル変数になるので「game.hoge」のような使い方でconfigに設定した内容を呼び出します。
ティラノのTGみたいなもんだとおもます。

configのtypeプロパティは、描写形式を指します。
Phaser.CANVAS、Phaser.WEBGL、Phaser.AUTOの3種があり、推奨はAUTO。
AUTOは基本WebGLで描写しますが、できない環境の場合CANVASに切り替えます。

$$
\def\arraystretch{1.5}
\begin{array}{c:c:c}
機能 & WebGL & CANVAS\\ \hline
3Dグラフィックスのサポート & ○ & ✖ \\ \hline
2Dグラフィックスのサポート & ○ & ○ \\ \hline
API & OpenGLベース & JavaScriptベース
\end{array}
$$

WebGLは高速描写に加えてどちらも対応しているので上位互換と言ってもいいかもしれないですね。

sceneオプションでグローバルで扱う関数を登録します。
グローバルとはスコープの1種で、異なる関数どころか異なるファイルからでも1度読み込ませれば呼び出せるっていうスコープです。
詳しくは「JS スコープ」で検索してください。

    scene: {
        preload: preload,
        create: create,
        update: update
    }

:より左側がkeyで、右側が関数です。
例えばscene.preload(左側)で、preload関数(右側)を呼び出します。

シーン

game.sceneで呼び出す関数を設定します。
Phaserではsceneと呼ばれて管理されています。
以下のコードでは、config内で関数を宣言をしておいて、別スコープで関数の中身(♠♥♦)を作成しています。

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};

var game = new Phaser.Game(config);

function preload ()
{
 ♠
}

function create ()
{
 ♥
}

function update ()
{
 ♦
}

preload

Phaserが起動したとき必ずこれを探し出して読み込みます。
アセットを読み込ませます。要は素材を読み込みます。
キャラクターとか背景とかそういう画像とか音楽とか。

♠の部分にコードを記入します。

例:

function preload ()
{
    this.load.image('sky', 'assets/sky.png');
    this.load.image('ground', 'assets/platform.png');
    this.load.image('star', 'assets/star.png');
    this.load.image('bomb', 'assets/bomb.png');
    this.load.spritesheet('dude', 
        'assets/dude.png',
        { frameWidth: 32, frameHeight: 48 }
    );
}

thisはJSの関数の書き方で、含んでいる関数名を略した書き方です。
つまりこのときはthis==preload(関数名)。

▶画像を読み込む

this.load.image('引数名', '相対パスで素材名拡張子');

これで素材を読み込みます。素材を呼び出すときに引数名で呼び出します。

┗画像を呼び出す

♥部分(create関数)に

this.add.image(400, 300, 'sky');

を打ち込むと表示されます。

this.add.image(X座標, Y座標, '引数');

┗┗画像自体の基本座標

Phaserでのアセット読込は、アセットの中心点を座標に合わせます。

なので座標x,y=0,0にすると

こうなります。
なので、座標400,300にすると画面サイズ800,600の時、
上下左右中央配置になります。

Phaser3からはアセットの基本座標を中央から左上にできるようになりました。

this.add.image(0, 0, 'sky').setOrigin(0, 0)
.setOrigin(アセット座標X,アセット座標Y)

AEのアンカーポイントを移動できるようになったみたいなもん。

┗┗--------------------------------------------------------┘画像自体の基本座標

tip:画像の重なり順

呼び出した順に重なります。
なので、背景→キャラクター→前景 の順に呼び出す必要があります。

tip:画像の配置箇所

ゲーム画面外でも配置が可能です。

┗----------------------------------------------------------------┘画像を呼び出す

▶連続シートを区分けして読み込む

this.load.spritesheet('引数名', 
        '相対パスで素材名拡張子', //←dude.png
        { frameWidth: 32, frameHeight: 48 }
    );

これはスプライトシートです。このdude.pngは

32*48のキャラクターが9つ横並びになっている

こうした画像で、横32px,縦48pxで区切って読み込みます。
キャラクターを操作したときにこの区切ったフレームを連続表示することでパラパラ漫画の要領で動いているように見せます。



create

画面構成する関数です。
♥部分に書き込みます。
これはplatformsをグローバルで宣言定義して、物理演算のシステムを
呼び出しています。

var platforms;

function create ()
{
    this.add.image(400, 300, 'sky'); //画像を表示 

    platforms = this.physics.add.staticGroup();

    platforms.create(400, 568, 'ground').setScale(2).refreshBody();

    platforms.create(600, 400, 'ground');
    platforms.create(50, 250, 'ground');
    platforms.create(750, 220, 'ground');
}

▶物理演算

platforms = this.physics.add.staticGroup();

ここでthis.physics(==物理演算)を宣言しています。
Physics Groupは自動で物理演算が可能な子オブジェクトを作り出す専用関数です。
ただし、宣言しても呼び出し元の記述がないのでconfigに記述を加えます

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
//-----------------------加えたとこ┐
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 300 },
            debug: false
        }
    },
//--------------------------------┘
    scene: {
        preload: preload,
        create: create,
        update: update
    }
};

今回はArcade Physicsシステムを呼び出します。

2018年2月20日の時点では
Arcade PhysicsImpact PhysicsMatter.js Physics が同梱されています。
Arcadeはモバイルゲーム向けに軽量なのが特徴的です。
他2つは知らんです。

https://phaser.io/tutorials/making-your-first-phaser-3-game/part6

┗static(静)物理演算

Arcade物理エンジンは動静の2種類があります。

platforms = this.physics.add.staticGroup();

とあるように、platformsオブジェクトは静的ボディで、
大きさと位置情報のみを持ちます。
地面などのステージの作成に向いています。

var platforms;

function create ()
{
    this.add.image(400, 300, 'sky'); //画像を表示 

    platforms = this.physics.add.staticGroup();

//---いまここ-----------------------------------------------------------

    platforms.create(400, 568, 'ground').setScale(2).refreshBody();

    platforms.create(600, 400, 'ground');
    platforms.create(50, 250, 'ground');
    platforms.create(750, 220, 'ground');

//----------------------------------------------------------------------
}
hoge.criate(X座標,Y座標,'アセット引数名');

で画面上に物理演算を持ったオブジェクトを作成します。
大きさは画像そのもののサイズです。

.setScale(2).refreshBody();

は、オブジェクトのスケールを2倍にするというオプションです。
静的な物理オブジェクトのスケールを変更する場合refreshBody()が必要。

これで物理オブジェクトである緑の地面ができました。

アセットは基本的に中心点がxy座標に合わせる。



プレイヤーを作成する。

create(♥)部分に以下を追加します。
プレイヤーは動的物理オブジェクトにします。

player = this.physics.add.sprite(100, 450, 'dude');

player.setBounce(0.2);
player.setCollideWorldBounds(true);

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 } ],
    frameRate: 20
});

this.anims.create({
    key: 'right',
    frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
    frameRate: 10,
    repeat: -1
});

┗Dynamic(動)物理演算

Arcade物理エンジンは動静の2種類があります。

Dynamicボディはstaticに速度、加速度を足したものです。
これによりバウンドだとか衝突だとかを判定できます。

player = this.physics.add.sprite(X座標, Y座標, spriteの引数名);

phaserでは`this.physics.add`で自動的に動的オブジェクト(Dynamic Physics body)に切り替わります。

┗┗bodyが生える

この作成時点で、playerにbodyオプションが生えます。
bodyオプションはArcade Physics Bodyの参照値です。
例えば

player.body.setGravityY(300)

でPlayerにY軸方向に300の重力を与えます。
それだけ上昇しづらくなり、落下が速くなります。

┗┗---------------------------------------------------------------┘bodyが生える


player.setBounce(0.2);
player.setCollideWorldBounds(true);

`.setBounce`はバウンド、つまり弾む。着地したときに0.2だけ跳ねます。
`.setCollideWorldBounds`は弾む機能をTrueでONにします。


┗スプライトシートのアニメーション

this.anims.create({
    key: 'left',
    frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
    frameRate: 10,
    repeat: -1
});
32*48のキャラクターが9つ横並びになっている
これをスプライトシートという。

▶連続シートを区分けして読み込む
にてdude.pngを読み込みました。

このとき、dudeは32*48ごとに区切られ配列に渡されています。
一番左の画像が「0」、一番右の画像が「8」です。

this.anims.create({
//左キーを押したとき
    key: 'left',
//配列番号0から3番を
    frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
//10フレームごとに順繰りに表示して
    frameRate: 10,
//アニメーションをループします。
    repeat: -1
});

`repeat:-1`で0に戻るっていう意味です。

同じようにtrun,rightを設定します。◀

なお、Phaserには、スプライトを左右反転する補助機能があります。
また、Phaser3からはこのアニメーションマネージャーがグローバルになったため、1つ定義作成すれば使いまわせるようになりました。

この時点ではまだplayerは左を向いたまま動けません。
キーと関数を繋げてないからです。



▶コライダーオブジェクト

これをそのまま入力してもdudeは地面を突き抜け画面端に落下します。
地面との衝突判定がないからです。(地面をDynamicbodyで作成すると互いに反発して飛び散ります)

そこで衝突を検知するコライダーオブジェクトを作成します。
検知したとき新たにイベントを差し込むこともできます。

create(♥)部分に以下を追加します。

this.physics.add.collider(player, platforms);

playerというオブジェクトとplatformsというオブジェクトが重なったか衝突したかを検知します。

this.physics.add.collider(判定したい引数名1, 判定したい引数名2);

検知したとき、重なる前にそれぞれが衝突しない位置で停止させます。
こうしたときに、1度で済むのでstaticとdynamicのグループは1つの引数で持ってこれるようにしておくと賢いです。



update

updateは画面表示後の常に変化するものを入れます。
例えばキーコンフィグなど。


▶キーと関数を繋げる

矢印キーを押すことでPlayerが動くようにします。

本来であれば衝突検知の時にコールバックで呼び出すのだが、Phaserは操作キャラをキーで操作するピンポイントな関数があるためこれを利用します。

グローバル部分(preload,create,updateより外側)で

var = cursors

とグローバル変数へ宣言します。

update(♦)部分に以下を追加します。

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

これでcursorsにup,right,down,leftの4つのオプションが生えます。

それぞれのオプションと関数を繋げます。

update(♦)部分に以下を追加します。

if (cursors.left.isDown)
{
    player.setVelocityX(-160);

    player.anims.play('left', true);
}
else if (cursors.right.isDown)
{
    player.setVelocityX(160);

    player.anims.play('right', true);
}
else
{
    player.setVelocityX(0);

    player.anims.play('turn');
}

if (cursors.up.isDown && player.body.touching.down)
{
    player.setVelocityY(-330);
}

isDownは「押し続けている間」です。
左キーを押すとスプライトシートの0-3がループするアニメが流れて、
右キーを押すと略       の5-8が略、
X方向に1秒で0進むとスプライトシート4のtrunがループで流れます。

上キーを押している状態で、playerのbodyの下方面が何かに触れているとき
Y方向に1秒で-330進みます。(=ジャンプ)

.setVelocityX
.setVelocityY

でその方向に1秒で進むを指示するようです。



ここまでで、
ステージの作成、キャラクターの作成・操作ができるようになりました。


応用---------------------------


星を降らせる

いままでの基本を用いて、ゲームで触れるとスコアになる星を作成します。

星を呼び出し、配置

星は跳ねる動作を入れたいので、Dynamicボディで作成します。

create(♥)部分に以下を追加します。

stars = this.physics.add.group({
    key: 'star',
    repeat: 11,
    setXY: { x: 12, y: 0, stepX: 70 }
});

stars.children.iterate(function (child) {

    child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));

});

.groupはkey,repeat,setXY3つのオプションがあります。

stars = this.physics.add.group({
    key '表示させたい画像引数名',
    repeat: 11, //0から数えて12個画像生成する
    setXY: { x: 12, y: 0, stepX: 70 } //X12Y0の位置に作成したらXに70加えて作成
});

starsをオブジェクトで使用したとき、デフォでkeyに登録した画像を使用。
1回使用すると子要素を12個作成する。childrenオブジェクトが生えます。
0番目をX12Y0に作成、1番目をX12+70Y0に作成…。

上で作成したstarsの子要素(.children)に弾みを与えます。

stars.children.iterate(function (child) {

    child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));

});

.iterateで全てのchildrenに以下の処理を行うまで処理をループします。
地面に衝突したときに、0.4から0.8の間のランダムな数値を
BounceYに渡します。なおこれの最小値は0、最大値は1です。
BounceYは0.4から0.8の数値で跳ね続けますが、重力があるので影響を受けていずれ0になります。

星と地面の衝突判定

このままだと地面を突き抜けるので星と地面で衝突の検知をさせます。

criate(♥)に追記します。

this.physics.add.collider(stars, platforms);

星とプレイヤーが重なり判定

以下略

this.physics.add.overlap(player, stars, collectStar, null, this);

これで、playerとstarsが重なった時、collectStar関数(下記)が発火します。

criate(♥)に追記します。

function collectStar (player, star)
{
    star.disableBody(true, true);
}

starのdisableBodyをONに、つまり目に見えなくします。

これでplayerが走り回り星に触れると消える
というアクションができました。


スコアを表示する

テキストゲームオブジェクトを作成します。

グローバル部分(preload,create,updateより外側)で

var score = 0;
var scoreText;

を宣言します。

スコアを出現させる

create関数(♥)部分でscoreTextを定義します。

scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });

これは

scoreText = this.add.text(X座標, Y座標, '初期表示テキスト', { fontSize: '32px', fill: '塗りつぶしカラーコード' });

を表します。fontsizeを決めない場合、phaserのデフォであるCourierと同じ数値になります。

スコアを連動させる

星に触れたとき(=collectStar関数)にスコアの数値を増加させます。

collectStar関数に追記します。

function collectStar (player, star)
{
    star.disableBody(true, true);

//追記部分----------------------------------
    score += 10;
    scoreText.setText('Score: ' + score);
//------------------------------------------
}

星に触れたとき、scoreの数値を10増加、そのたびにscoreTextの表記全てを書き換えます。

爆弾を降らせる

触れるとゲームが停止する物体=爆弾を作成します。

爆弾は
・星を全て集めると発生
・1つ振ると星が再出現
・ランダムな方向にレベル(staticbodyとゲーム枠)を跳ねまわる
・触れるとゲームオーバー(playerは赤くなり画面が止まる)

という特徴を持ちます。

まずは爆弾の所属グループを決めます。
跳ねまわるのでDynamicです。
画面に表示するのでcreate(♥)に書きます。

bombs = this.physics.add.group();

this.physics.add.collider(bombs, platforms);

this.physics.add.collider(player, bombs, hitBomb, null, this);

bombsをDynamicボディグループで宣言。
爆弾と地面(platforms)の衝突検知。
playerと爆弾が重なる検知でhitBomb関数を発火。

直下にhitBomb関数を追記

function hitBomb (player, bomb) //playerと爆弾が重なった時
{
//画面を停止
    this.physics.pause();
//playerに0xff0000のフィルターを掛ける(赤)
    player.setTint(0xff0000);
//playerをturnのアニメにする(正面向き)
    player.anims.play('turn');
//gameOver変数をtrueにする。
    gameOver = true;
}

これで爆弾に触れたときの処理が終わりました。

爆弾を出現させる

爆弾出現のきっかけは「星を全て獲得すること」なので
collectStar関数に追記します。
2回目から星と一緒に爆弾も降ってくるという仕組みにします。

function collectStar (player, star)
{
    star.disableBody(true, true);

    score += 10;
    scoreText.setText('Score: ' + score);

//追記部分--------------------------------------------------

    if (stars.countActive(true) === 0)
    {
        stars.children.iterate(function (child) {

            child.enableBody(true, child.x, 0, true, true);

        });

        var x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);

        var bomb = bombs.create(x, 16, 'bomb');
        bomb.setBounce(1);
        bomb.setCollideWorldBounds(true);
        bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);

    }

//-----------------------------------------------------------
}


if (stars.countActive(true) === 0)

画面上で表示されているstarsが0(間違いなく0)のとき
===星を全て集めたとき

stars.children.iterate(function (child) {

iterateで星を出現する関数の引数を、+

child.enableBody(true, child.x, 0, true, true);

enableBodyでchildのY座標を0にボディを書き換えます。
これでiterateの処理をy0として行うことができます。

なので、新しい星を落とすのでなく、既存の見えなくしていた星を
上に持ち上げて見えるようにして再落下させています。

爆弾の挙動定義

var x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);

var bomb = bombs.create(x, 16, 'bomb');
bomb.setBounce(1);
bomb.setCollideWorldBounds(true);
bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);

変数xをplayerの現在位置Xが400以下の時、400から800を入れて、
違う場合は0から400を入れる。
つまり、画面を左右分割したときにplayerがいない側にxを設定します。

次に、bombという画像をx不定y16のところに出現させます。
Bounceを最大火力の1にします。
.setCollideWorldBoundsでゲーム画面の枠自体でも反射をON
.setVelocityで-200から200のランダムなXとY20の方角へ移動します。

これで星がなくなるたびにランダムな弾み方をする爆弾が1つずつ増えていくというイベントが完成しました。

これでサンプルゲームは完成です。



まとめ


いまのとこの所感(23/08/05)
・README.mdがある階層にhtmlファイルを作ってscriptタグの中に書く。
・configを設定して、preload/create/updateの3種の関数に色々書き込む。
・preloadは素材読込
・createは画面表示関連
・updateは表示後の変化物関連
・「おそらくこの機能がある!」が見つからない(本当にあると思う)

こんな感じ。

使用できるパラメーターまとめ

他のチュートリアル

サンプルゲーム(コード付き)



終わり


この記事が気に入ったらサポートをしてみませんか?