この記事は、株式会社Y's アドベントカレンダー 2日目の記事になります。
#enchant.jsとは
「enchant.js」は、株式会社UEIが開発している、JavaScriptのゲームエンジンです。
ゲーム開発に必要な最低限の機能を備えており、ファイルサイズが小さいことが特徴です。
また、様々なプラグインが存在しており、アニメーションや3D機能を追加できます。
#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タグの中に何かを記述する必要はありません。
<!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では、ブラウザごとに異なる隙間をなくしておきます。
* {
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」を利用してみました。
すると、比較的シンプルなコードで実装できたため、このまま正式採用となりました。
#コード全体
最後にコード全体です。
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年ほど前ですが。。。)
もしよろしければ遊んでみてください。
このゲームは、Start地点「S」から、Goal地点「G」まで、
オレンジ枠をタップしながら、移動していくゲームです。
赤枠が現在地セルで、オレンジ枠が移動可能なセルです。
オレンジ枠のセルをタップして、移動してください。
移動するとそのセルが赤枠に変わります。
セルには将棋の駒が書いてあり、
移動すると、その駒の移動能力を得ます。
そのため、白いセルに移動するとアウトです。
オレンジ枠が常に出ますので、
将棋の駒の動きを覚えなくても、
適当にタップしてゴールできます。
次は 3日目 @yanyan_ys さんの記事です。お楽しみに!