みどりのあくま

勉強したことをアウトプットしていきます。

parcel + phinaで簡単ゲーム開発環境構築 その2

前回、導入部分のみ書きました。

前の記事 orange634.hatenablog.com

本記事では早速開発環境を構築していきたいと思います。

最小構成

目標

はじめに最小構成を作成します。
以下の画面が表示されるのを目標にします。

f:id:orange634:20181106141317p:plain

サンプルコードは以下にあるので詳しい内容は以下のリポジトリから直接確認してください。

github.com

構築方法

アプリケーションの作成

まず、プロジェクトフォルダを作成してnpmの初期化を行います。

$ mkdir sample-project
$ cd sample-project
$ npm init
##### 以下は入力例です #####
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (code) sample-project
version: (1.0.0) 
description: sample project for parcel and phina.js
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) MIT
About to write to /xxxxxx/xxxxxx/sample-project/package.json:

{
  "name": "sample-project",
  "version": "1.0.0",
  "description": "sample project for parcel and phina.js",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT"
}


Is this OK? (yes) yes

次に parcel と phina.js をインストールします。

$ npm i parcel --save-dev
$ npm i phina.js --save

作業用ディレクトリを作成して、index.html を作成します。

$ mkdir src
$ touch src/index.html

作成した index.html に以下のコードを書きます。

<!DOCTYPE html>
<html lang=ja>
<head>
    <meta charset="utf-8">
    <meta http-equiv=X-UA-Compatible content="IE=edge">
    <meta name=viewport content="width=device-width,initial-scale=1">
    <title>Parcel + Phina.js sample</title>
</head>
<body>
    <script src="index.js"></script>
</body>
</html>

次に index.js を作業ディレクトリに作成します。
この index.js にメインのコードを書いていきます。

// phina.jsを有効化 <--- (1)
import 'phina.js'
phina.globalize()

// メインシーン定義 <--- (2)
phina.define('MainScene', {
    superClass: 'DisplayScene',
    init() {
        this.superInit()
        // 背景色指定
        this.backgroundColor = 'gray'
        // スプライト作成・表示
        this.label = Label({
            text: 'Hello Parcel + Phina.js',
            fill: 'white',
            x: this.gridX.center(),
            y: this.gridY.center()
        }).addChildTo(this)
    }
})

// アプリ実行 <--- (3)
phina.main(() => {
    const app = GameApp({
        startLabel: 'main'
    })
    app.run()
})

これで「Hello Parcel + Phina.js」という文字が画面に表示されます。

上のコードを部分ごとに解説していきます。

まず、(1) の箇所で phina.js を読み込んで有効化しています。
これは必ず始めに書かないと GameApp などでエラーになるので注意してください。

(2) の部分で MainScene を定義しています。
phina.js では普通の js と異なるクラス定義方法を取っています。
ここでは画面に「Hello Parcel + Phina.js」という文字を表示させる処理を書いています。
本記事では phina.js 自体の細かい説明は省かせて頂きます。

(3) の部分でアプリを実行させています。
今回は MainScene から起動させたいので、startlabel: 'main' を定義しています。

実行

では、実際に確認してみましょう。
npx parcel src/index.html でビルドしてください。
今回は parcel をローカルインストールしたので、実行させるには npx をつける必要があります。

$ npx parcel src/index.html 
Server running at http://localhost:1234 
✨  Built in 3.16s.

ビルドが完了したら、http://localhost:1234 にブラウザでアクセスしてください。

f:id:orange634:20181106141317p:plain

上記のように表示されたら完了です。
ビルドされたファイルはdist ディレクトリ以下の出力されます。
jsmap ファイルにつく数字はランダムなので別の数字になっていても問題ありません。

$ ls -l dist/
total 1864
-rw-r--r--  1 musashi  staff     342  8 22 16:49 index.html
-rw-r--r--  1 musashi  staff  398898  8 22 16:49 src.86dd6973.js
-rw-r--r--  1 musashi  staff  483932  8 22 16:49 src.86dd6973.map

ランダム数字で問題ないか確認してみましょう。 src/index.html では index.js を読み込む箇所を <script src="index.js"></script> と記載しています。
一方 dist/index.html では <script src="/src.86dd6973.js"></script> に書き換わっています。
なので、ビルド後も index.js は正しくインポートされます。

これで開発環境は整いました。
すごい簡単ですよね?私もそう思います。
正直これで開発環境の構築は終わってしまうのですが……私が開発する時に構成について紹介します。

おすすめ構成

この構成は私がおすすめというか、自分んがよく使っている構成について説明していきます。
必ずしもこの構成にする必要はありません。あくまで私の一例です。

目標

以下の画面が表示されるのを目標にします。

f:id:orange634:20181108003324p:plain

サンプルコードは以下にあるので詳しくはそちらを確認してください。

github.com

構成の解説

ディレクトリ構成

src ディレクトリ以下は以下のようになります。

.
├── GameClass
│   ├── MainScene.js
│   ├── PixcelSrite.js
│   └── index.js
├── assets
│   └── parcelPhina.png
├── config.js
├── index.html
└── index.js

各ファイルとその役割は以下のようになります。

ディレクトリ名・ファイル名 用途
GameClass ゲームクラスを定義するフォルダです。
簡単なゲームでもそれなりの量のクラスを作るのでクラスごとにフォルダを分けています。
必要があればさらに階層を設けていいと思います。
assets アセットデータを配置するディレクトリです。
必要があればさらに階層を設けていいと思います。
config.js ゲーム内の設定やアセットデータを定義します。
設定はマジックナンバーを避けたり、ハードコーディングを避けるに使用します。
index.html エントリーポイントとなるファイルです。
最小構成と全く同じ内容です。
index.js 最小構成と異なっています。
詳細に関しては後ほど説明します。

クラス定義

ゲームで使用するクラスは GameClass ディレクトリ以下に定義します。
例えば MainScene.js は以下のように定義してます。

// メインシーン定義
export default {
    superClass: 'DisplayScene',
    init() {
        this.superInit()
        // 背景色指定
        this.backgroundColor = 'gray'
        // スプライト作成・表示
        this.parcelPhina = PixcelSprite('parcelPhina')
            .setPosition(this.gridX.center(), this.gridY.center() - 50)
            .setScale(7)
            .addChildTo(this)
        // ラベル作成・表示
        this.label = Label({
            text: 'Hello Parcel + Phina.js',
            fill: 'white',
            x: this.gridX.center(),
            y: this.gridY.center() + 200
        }).addChildTo(this)
    }
}

phina.define() の第二引数に当たる object のみ記述しています。
各クラスファイルで phina.define() を使って定義する方法もあるとは思いますが、少なくとも私が試した限り上手く出来ませんでした。

phina にクラスと登録するのは別の場所で行なっています。
GameClass 内で定義したクラスを GameClass/index.js で一つのオブジェクトにまとめます。

import MainScene from './MainScene'
import PixcelSprite from './PixcelSrite'
export default {
    MainScene,
    PixcelSprite
}

index.jsGameClass オブジェクトを元に phina 向けのクラス定義をします。

import GameClass from './GameClass'

// *** 省略 ***

// クラスを定義
for (const className in GameClass) {
    phina.define(className, GameClass[className])
}

このような構成にすることで、クラスをファイルごとに分割しながらも効率的に phina にクラスを登録出来ます。

アセットの定義

parcel でビルドする際に、各アセットにランダムな文字列が追加されます。

ランダム文字列付きのアセット名は import xxx from '<assetsのパス>' のよすると変数xxx にランダム文字列付きのアセット名が格納されています。

今回の構成では config.js にアセット関連の定義を行なっています。 config.js は以下のようなコードになります。

import parcelPhina from './assets/parcelPhina.png'
export default {
    // アセットを定義
    assets: {
        image: {
            parcelPhina: parcelPhina
        }
    }
}

定義した物を index.jsGameApp に設定をしています。

import config from './config'

// *** 省略 ***

// アプリを実行
phina.main(() => {
    const app = GameApp({
        startLabel: 'main',
        assets: config.assets // <-- ここでアセットを読み込ませている
    })
    app.run()
})

package.json

よく使うコマンドは scripts に定義しておきます。

"scripts": {
  "clear": "rm -rf ./dist && rm -rf ./.cache && rm -rf build",
  "dev": "parcel src/index.html",
  "build": "npm run clear && parcel build src/index.html"
}

parcel はキャッシュに関連して正しくビルドが出来ない場合があります。
なので、npm run cleardist.cachebuild ディレクトリを消せるようにしておきます。
上手くビルド出来ない時などに実行します。

ビルドする際は必ずキャッシュをクリアしてます。
ビルドする際、出力先のパスを指定しないと dist へ出力されるので -d build のオプションをつけて出力先ディレクトリを開発用と分けておきましょう。

その他

今回のサンプルで PixelSrite というクラスを作成しています。
これはドット絵をスプライトで表示させる時、拡大してもアンチエリアスを無効にするクラスです。

実行

開発する際は npm run dev を実行します。
問題がなければビルドが完了するので、ブラウザで http://localhost:1234 を開いてください。
ファイルを編集したら、自動でビルド・ブラウザをリロードされます。

ビルドする際は npm run build を実行します。
ビルドが走り、生成物は build ディレクトリへ出力されます。
これは次の章で web に公開する際に使います。

まとめ

最小構成とおすすめの構成の2種類を紹介しました。
おすすめの構成はあまり作り込みすぎず、使いやすさを考えてみました。
これで開発環境は整いました。
次の記事では web への公開方法について説明していきます。


次の記事 orange634.hatenablog.com