enchant.jsで画面サイズより広いフィールドの作り方

この記事は、[株式会社Y's アドベントカレンダー](https://qiita.com/advent-calendar/2017/ysinc) 2日目の記事になります。



<img src="http://enchantjs.com/images/enchant.png" width="50%" alt="enchant.js">

#enchant.jsとは

「[enchant.js](http://enchantjs.com/ja/ "enchant.js")」は、[株式会社UEI](https://www.uei.co.jp/ "株式会社UEI")が開発している、JavaScriptのゲームエンジンです。
ゲーム開発に必要な最低限の機能を備えており、ファイルサイズが小さいことが特徴です。
また、様々なプラグインが存在しており、アニメーションや3D機能を追加できます。

#enchant.jsで作ろう

ゲームを簡単に作れます！
あなたのアイデアをガンガン実現させていきましょう！
以下のリンクから「enchant.js」をダウンロードしてみてください。

[enchant.jsのダウンロード](http://enchantjs.com/download/ "enchant.jsのダウンロード")

#さっそく作ってみる

各ファイルの構成は以下の通りです。

```text:ファイルの構成
├─ css
│   └─ common.css
├─ img
│   └─ sprite.png
├─ js
│   ├─ enchant.min.js
│   └─ my.js
└─ index.html
```

まずは、実物を見ていただくのが、一番理解しやすいと思っています。
そこで、jsdo.it上に、実際に動かせるサンプルを設置しました。
スイカをモチーフにした、2次元のフィールドになっています。
PCからは画面をドラッグ、スマホからは画面をフリックして、画面を上下左右に自由にスクロールさせてみてください。

[サンプル](http://jsrun.it/s.hayashi/I8Il "動作確認用のサンプル")

#HTML

HTMLでは、各cssファイルやjsファイルを読み込むだけです。
そのため、bodyタグの中に何かを記述する必要はありません。

```html:index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Suika</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="css/common.css" type="text/css">
        <script src="js/enchant.min.js"></script>
        <script src="js/my.js"></script>
    </head>
    <body>
    </body>
</html>
```

#CSS

CSSでは、ブラウザごとに異なる隙間をなくしておきます。

```css:common.css
* {
  margin: 0;
  padding: 0;
  border: 0;
}
```


#JavaScript

ここではまず、enchant.jsを動かす上で「最低限必要」なコードだけ抜粋します。

```javascript
enchant();

const SCREEN_WIDTH  = 612;
const SCREEN_HEIGHT = 714;

window.addEventListener("load", () => {

    const core = new Core(SCREEN_WIDTH, SCREEN_HEIGHT);

    core.addEventListener("load", () => {
        // ※ここで、自分が処理させたいロジックを書いていく
    }, false);

    core.start();

}, false);

```

軽く解説しますと
1.「enchant();」を呼ぶ
2.「Coreのインスタンスcore」を作って、coreの「loadイベント」の中で、自分の処理させたいロジックを書く
3.「core.start();」を実行する
という処理を行っています。

#フリックできるようにする

enchant.jsには、ちゃんと各種イベント処理が定義されています。
今回やりたいことは「スマホでフリックして、画面をスクロールさせたい」というものです。
そこで、touch系のイベント処理を使用します。

```javascript
// タッチイベント設定
map.addEventListener(enchant.Event.TOUCH_START, (e) => {
    e.target.originX = e.x - e.target.x;
    e.target.originY = e.y - e.target.y;
});
map.addEventListener(enchant.Event.TOUCH_MOVE, (e) => {
    const x = e.x - e.target.originX;
    const y = e.y - e.target.originY;
    e.target.moveField(x, y, e.target);
});

```

まずは「TOUCH_START」イベントで、最初の座標を保存しておきます。
そして「TOUCH_MOVE」イベントで、新しい座標に移動させます。

#スクロールを補正する

実は、スクロール処理は、際限なくできてしまいます。
そこで、画面で見えている範囲内で、スクロールが収まるように補正する必要があります。

```javascript
map.moveField = (x, y, map) => {
    const northEdge = 0;
    const southEdge = (SCREEN_ROW_COUNT - map.rowCount) * TILE_SIZE;
    const eastEdge  = (SCREEN_COL_COUNT - map.colCount) * TILE_SIZE;
    const westEdge  = 0;

    // 左にスクロール(マップを右にずらす)し過ぎたら、端まで戻す
    x = Math.min(x, westEdge);

    // 右にスクロール(マップを左にずらす)し過ぎたら、端まで戻す
    x = Math.max(x, eastEdge);

    // 上にスクロール(マップを下にずらす)し過ぎたら、端まで戻す
    y = Math.min(y, northEdge);

    // 下にスクロール(マップを上にずらす)し過ぎたら、端まで戻す
    y = Math.max(y, southEdge);

    map.moveTo(x, y);
}; 
```

最初は単純に「if文」で処理を書いておりました。
ただ何となく、リファクタリングできそうな気配を感じ取りました。

そこで、数学系のメソッド「min」「max」を利用してみました。
すると、比較的シンプルなコードで実装できたため、このまま正式採用となりました。


#コード全体

最後にコード全体です。

```javascript:my.js
enchant();

const TILE_SIZE        = 102;
const SCREEN_COL_COUNT = 6;
const SCREEN_ROW_COUNT = 7;
const SCREEN_WIDTH     = TILE_SIZE * SCREEN_COL_COUNT;
const SCREEN_HEIGHT    = TILE_SIZE * SCREEN_ROW_COUNT;
const IMAGE_FILE       = "img/sprite.png";

const MY_MAP = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0],
    [0, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
    [0, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
    [0, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
    [0, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
    [0, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
    [0, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
    [0, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
    [0, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
    [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];

window.addEventListener("load", () => {

    const core = new Core(SCREEN_WIDTH, SCREEN_HEIGHT);

    core.preload(IMAGE_FILE);

    core.addEventListener("load", () => {

        const mapRowCount = MY_MAP.length;
        const mapColCount = MY_MAP[0].length;
        
        // マップを作成
        const map = new Group();
        
        map.originX  = 0;
        map.originY  = 0;
        map.rowCount = mapRowCount;
        map.colCount = mapColCount;
        
        // 2次元配列をマップに直す
        for (let row=0; row<mapRowCount; row++) {
            for(let col=0; col<mapColCount; col++) {
                const s = new Sprite(TILE_SIZE, TILE_SIZE);
                s.image = core.assets[IMAGE_FILE];
                s.frame = MY_MAP[row][col];
                s.x     = TILE_SIZE * col;
                s.y     = TILE_SIZE * row;
                map.addChild(s);
            }
        }
        
        map.moveField = (x, y, map) => {
            const northEdge = 0;
            const southEdge = (SCREEN_ROW_COUNT - map.rowCount) * TILE_SIZE;
            const eastEdge  = (SCREEN_COL_COUNT - map.colCount) * TILE_SIZE;
            const westEdge  = 0;

            // 左にスクロール(マップを右にずらす)し過ぎたら、端まで戻す
            x = Math.min(x, westEdge);

            // 右にスクロール(マップを左にずらす)し過ぎたら、端まで戻す
            x = Math.max(x, eastEdge);

            // 上にスクロール(マップを下にずらす)し過ぎたら、端まで戻す
            y = Math.min(y, northEdge);

            // 下にスクロール(マップを上にずらす)し過ぎたら、端まで戻す
            y = Math.max(y, southEdge);

            map.moveTo(x, y);
        };      
        
        // タッチイベント設定
        map.addEventListener(enchant.Event.TOUCH_START, (e) => {
            e.target.originX = e.x - e.target.x;
            e.target.originY = e.y - e.target.y;
        });
        map.addEventListener(enchant.Event.TOUCH_MOVE, (e) => {
            const x = e.x - e.target.originX;
            const y = e.y - e.target.originY;
            e.target.moveField(x, y, e.target);
        });

        // マップをシーンに追加
        core.rootScene.addChild(map);

    }, false);

    core.start();
    
}, false);
```
#まとめ

「enchant.js」はとにかく楽しい！
サクサクゲームを作れる！

今回紹介したノウハウを活用して、オリジナルのブラウザGameを作りました。(3年ほど前ですが。。。)
もしよろしければ遊んでみてください。

<img src="http://jsrun.it/assets/g/O/O/v/gOOvT.png" width="50%" alt="SG TAP">

PC向け：[遊ぶ](http://jsdo.it/Games_Haya/un0xd "SG TAP")
SP向け：[遊ぶ](http://jsrun.it/Games_Haya/un0xd "SG TAP")

```text:[SG&nbsp;TAP]の説明
このゲームは、Start地点「S」から、Goal地点「G」まで、
オレンジ枠をタップしながら、移動していくゲームです。

赤枠が現在地セルで、オレンジ枠が移動可能なセルです。

オレンジ枠のセルをタップして、移動してください。
移動するとそのセルが赤枠に変わります。

セルには将棋の駒が書いてあり、
移動すると、その駒の移動能力を得ます。

そのため、白いセルに移動するとアウトです。

オレンジ枠が常に出ますので、
将棋の駒の動きを覚えなくても、
適当にタップしてゴールできます。
```

次は 3日目 @yanyan_ys さんの記事です。お楽しみに！