【Phaser】チュートリアルのコードをリファクタリングしてみる(スマホ向け)

あわせて読みたい
【Part10(最終回)】スマホで作るPhaserゲーム〜チュートリアル編〜 どうもです。タドスケです。 https://tadosuke.com/game/954/ スマホだけで作るPhaserゲームも今回でいよいよ完成です。 最後の仕上げに障害物とゲームオーバー処理を入...

先日Phaserのチュートリアルを終えたのですが、スマホで作業をする上でやりづらさを感じました。

今回はゲームを作るのではなく、迷いなく見やすいコードを書くために色々と試行錯誤したことをまとめます。

普通にPCで作業をする方には、一部おかしなルールに思えるかもしれませんが、参考になれば幸いです。

目次

問題点

コーディング中に以下の問題点が出てきました。

  • 一つの行が長くなってくると、折り返しが増えて見にくくなる
  • 関数が長くて一画面に収まらない
  • タブ幅が広いと見にくい
  • 引数の改行、カッコの位置などの書式ルールが統一されていない

今後も同様にスマホでコーディングを行うにあたり、見やすくなるようなルールがあった方が良いように思えました。

基本ルール

どんな言語、どんな環境においても共通で推奨されるルールというものがあります。

まずはそれらを適用していきます。

関数の分割

長い関数は処理のまとまりごとに別関数に分離します。

function create (){
// 足場
createPlatforms(this);

// プレイヤー
createPlayer(this);

// 星
createStars(this);

// ボム
bombs = this.physics.add.group();

// 衝突の設定
colliderSetting(this);
}

ゲーム内の要素ごとのcreateを個別で呼ぶようにしました。

プレイヤーのcreate処理を変えたい場合はcreatePlayer関数の中だけに注目すればよいので、修正がしやすくなります。

ローカル変数を使う

何度も呼び出しているメンバーはローカル変数にまとめることで、呼び出しを短くできます。

let physics = scene.physics;

// 星と足場
physics.add.collider(
stars,
platforms
);

毎回 scene.physics.add のように書かなくても済みます。

JavaScript固有のルール

カッコの位置や大文字/小文字の使い方は自由ですが、言語ごとに推奨のルールがあることが多いです。

JavaScriptに関してもそのようなものがあると思ったので調べてみました。

Japanese Team
JavaScript コーディング規約 JavaScript コーディング規約 今や JavaScript は WordPress コアだけでなく、テ…

いくつか見てみた中ではWordPressのコード規約がまとまっていてわかりやすかったので、こちらを基本に考えようと思います。

連続したメソッド呼び出し

連続したメソッド呼び出しが長く、行を折り返す場合、1行に1呼び出しを書いてください。

これに従うと、例えば足場の生成処理は以下のようになります。

// 足場1
platforms
.create(300, 150, 'ground')
.setScale(0.5)
.refreshBody();

カッコの位置

僕が仕事でC++/C#のコードを書くとき、

void function()
{
// 中身
}

のように中カッコの位置を揃えるクセがあるのですが、WordPressルールでは同じ行に中カッコを置くことが推奨されているようです。

今のところ特に強いこだわりも無いので、ひとまず合わせておきます。

スマホ向けの特別ルール

スマホで作業をする都合、画面幅が狭いという制約があります。

このため一行をできるだけ短くする書き方が望ましいです。

読みやすさを妨げない範囲で、以下のルールを考えてみました。

タブ幅

タブはスペースではなく「本物のタブ」で入れるのが推奨されているのですが、

  • スマホのキーボードでタブが使えない
  • タブ幅が4文字分あると改行が増える

という問題が出たので「スペース2つ」で統一します。

引数の改行

引数が複数あると行が長くなりがちなので、PCのとき以上に積極的に改行するようにしました。

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

ただし、start, endのようにひとまとまりで一行に収まる長さのものについては、改行するとかえって見にくくなるので、そのままにしています。

できあがったのがこちら!

以上をふまえて整理したコードが以下です。

//---------------------------
// グローバル変数
//---------------------------
// ゲーム設定
var config = {
type: Phaser.AUTO,
width: 300,
height: 400,
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false
}
},
scene: {
preload: preload,
create: create,
update: update
}
};
// ゲーム本体
var game = new Phaser.Game(config);
// プレイヤー
var player;
// 足場グループ
var platforms;
// 星グループ
var stars;
// ボムグループ
var bombs;
// スコア
var score = 0;
// スコア表示テキスト
var scoreText;

//---------------------------
// ロード
//---------------------------
function preload (){
this.load.image(
'star',
'img/20210425182551.png'
);
this.load.image(
'sky',
'img/20210425175550.png'
);
this.load.image(
'ground',
'img/20210425175525.png'
);
this.load.image(
'bomb',
'img/20210425182611.png'
);
this.load.spritesheet(
'dude',
'img/20210425182602.png',
{
frameWidth: 32,
frameHeight: 48
}
);
}

//---------------------------
// 足場の生成
//---------------------------
function createPlatforms(scene){
// 足場グループを生成
platforms = scene.physics.add.staticGroup();

// 足場1
platforms
.create(300, 150, 'ground')
.setScale(0.5)
.refreshBody();

// 足場2
platforms
.create(0, 250, 'ground')
.setScale(0.5)
.refreshBody();

// 地面
platforms.create(
150,
385,
'ground'
);
}

//---------------------------
// プレイヤーアニメーションの生成
//---------------------------
function createPlayerAnims(anims){
// 正面
anims.create({
key: 'turn',
frameRate: 20,
frames:
[{ key: 'dude', frame: 4 }],
});

// 左
anims.create({
key: 'left',
frameRate: 10,
repeat: -1,
frames:
anims.generateFrameNumbers(
'dude',
{ start: 0, end: 3 }
),
});

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

//---------------------------
// プレイヤーの生成
//---------------------------
function createPlayer(scene){
// プレイヤー
player = scene.physics.add.sprite(
150,
200,
'dude'
);
player.setBounce(0.2);
player.setCollideWorldBounds(true);

// アニメーション
createPlayerAnims(scene.anims);
}

//---------------------------
// 星の生成
//---------------------------
function createStars(scene){
// グループの生成
stars = scene.physics.add.group({
key: 'star',
repeat: 4,
setXY: {
x: 13,
y: 0,
stepX: 68
}
});

// 星の物理挙動を設定
stars.children.iterate(
function (child) {
child.setBounceY(
Phaser.Math.FloatBetween(0.4, 0.8)
);
}
);
}

//---------------------------
// 新しいボムを生成する
//---------------------------
function createNewBomb(){
// プレイヤーから離れたランダムな場所
let x = (player.x < 150) ?
Phaser.Math.Between(150, 300) :
Phaser.Math.Between(0, 150);

// ボムの生成
let bomb = bombs.create(
x,
16,
'bomb'
);
bomb.setBounce(1);
bomb.setCollideWorldBounds(true);
bomb.setVelocity(
Phaser.Math.Between(-200, 200),
20
);
}

//---------------------------
// 衝突の設定
//---------------------------
function colliderSetting(scene){
let physics = scene.physics;

// 星と足場
physics.add.collider(
stars,
platforms
);

// プレイヤーと星
physics.add.overlap(
player,
stars,
collectStar,
null,
this
);

// プレイヤーと足場
physics.add.collider(
player,
platforms
);

// ボムと足場
physics.add.collider(
bombs,
platforms
);

// プレイヤーとボム
physics.add.collider(
player,
bombs,
hitBomb,
null,
scene
);
}

//---------------------------
// プレイヤーと星の衝突時に呼ばれる
//---------------------------
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
);
}
);

// 新しいボムを生成する
createNewBomb();
}
}

//---------------------------
// プレイヤーとボムの衝突時に呼ばれる
//---------------------------
function hitBomb (player, bomb){
this.physics.pause();
player.setTint(0xff0000);
player.anims.play('turn');
gameOver = true;
}

//---------------------------
// ロード後処理
//---------------------------
function create (){
// this = scene

// マウス入力を有効にする
this.input.mouse.capture = true;

// 背景
this.add.image(
300,
300,
'sky'
);

// スコア
scoreText = this.add.text(
16,
16,
'Score: 0',
{ fontSize: '32px', fill: '#000' }
);

// 足場
createPlatforms(this);

// プレイヤー
createPlayer(this);

// 星
createStars(this);

// ボム
bombs = this.physics.add.group();

// 衝突の設定
colliderSetting(this);
}

//---------------------------
// 更新処理
//---------------------------
function update (){
let pointer = this.input.activePointer;

if(pointer.isDown) {
// 左移動
if(pointer.x < 150) {
player.setVelocityX(-160);
player.anims.play('left',true);
}
// 右移動
else if(150 < pointer.x) {
player.setVelocityX(160);
player.anims.play('right',true);
}

// ジャンプ
if(
pointer.y < 200 &&
player.body.touching.down
){
player.setVelocityY(-330);
}
}
// 停止
else {
player.setVelocityX(0);
player.anims.play('turn');
}
}

正解はひとつではない

JavaScriptによらず、コーディングルールというのは、開発を効率よく進めるために考えられるものです。

ルールに従うことで開発効率が下がったり、不要なミスを生んだりしてしまうなら、ルールを見直すべきなのかもしれません。

今回僕が決めたルールも、今後運用しながら適宜見直していければと思います。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次