JavaScript
enchant.js
es6
Y'sDay 2

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

この記事は、株式会社Y's アドベントカレンダー 2日目の記事になります。

enchant.js

enchant.jsとは

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

enchant.jsで作ろう

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

enchant.jsのダウンロード

さっそく作ってみる

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

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

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

サンプル

HTML

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

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では、ブラウザごとに異なる隙間をなくしておきます。

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

JavaScript

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

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系のイベント処理を使用します。

// タッチイベント設定
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」イベントで、新しい座標に移動させます。

スクロールを補正する

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

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」を利用してみました。
すると、比較的シンプルなコードで実装できたため、このまま正式採用となりました。

コード全体

最後にコード全体です。

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年ほど前ですが。。。)
もしよろしければ遊んでみてください。

SG TAP

PC向け:遊ぶ
SP向け:遊ぶ

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

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

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

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

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

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

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