前回の記事で作成したenchant.jsのゲームロジックを元に、three.jsで3D表現を行いました。
three.js
three.jsはWebGLをサポートしたJavaScriptの3D描画ライブラリです。Mr.Doob氏を中心にオープンソースで開発が進められていて、WebGLの3Dライブラリとしての実績も多く、現状ではデファクトスタンダードとなっています。この記事の寄稿時ではリビジョンはr64でした。
three.jsを使って3D表現を行う事は、ダウンロードしたパッケージに格納されているサンプルコードや、多くのthree.js関連のブログ記事を見ればいくらでも学習する事ができます。ただ、せっかくの3D表現なのでゲーム性を持たせたいと考えました。本格的な3Dゲームとなると三次元での物理演算等を行わなくてはなりません。もっと気軽に3D表現で楽しむことは出来ないかと思い、ゲームロジックはenchant.js、表示部分にthree.jsを利用して今回のサンプルを作成しました。
ソースコード
※操作方法 : 前進「↑」キー、後退「↓」キー、旋回「←→」キー
ゲームといっても、現状はただダンジョンの中をさまようだけですが、このゲームの基幹部分は左上のマップ部分となります。マップ部分の作成はenchant.jsのMapクラスを使用して、マップ上の障害物の判定としてcollisionDataプロパティに以下の配列を渡して、プレーヤーが「1」のマス目に侵入出来ないようにしています。
main.js(6行目~)
var MAP = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1],
[1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1],
[1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];
three.jsではマップ作成に使用した配列をそのまま利用して、壁と床と天井を作成しています。
main.js(129行目~)
// 壁
var geometry = new THREE.CubeGeometry(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
var texture = new THREE.ImageUtils.loadTexture("wall01.jpg");
var material = new THREE.MeshPhongMaterial({map: texture, bumpMap: texture, bumpScale: 0.2});
for (i = 0, max = MAP.length; i < max; i = i + 1) {
for (j = 0, max2 = MAP[i].length; j < max2; j = j + 1) {
if (MAP[i][j] == 1) {
var cube = new THREE.Mesh(geometry, material);
cube.position.set(BLOCK_SIZE * j, BLOCK_SIZE / 2, BLOCK_SIZE * i);
scene.add(cube);
}
}
}
// 床
var pGeometry = new THREE.PlaneGeometry(BLOCK_SIZE, BLOCK_SIZE);
var pTexture = new THREE.ImageUtils.loadTexture("land01.jpg");
var pMaterial = new THREE.MeshPhongMaterial({map: pTexture, side: THREE.DoubleSide, bumpMap: pTexture, bumpScale: 0.2});
for (i = 0, max = MAP.length; i < max; i = i + 1) {
for (j = 0, max2 = MAP[i].length; j < max2; j = j + 1) {
if (MAP[i][j] == 0) {
var plane = new THREE.Mesh(pGeometry, pMaterial);
plane.position.set(BLOCK_SIZE * j, 0, BLOCK_SIZE * i);
plane.rotation.x = 90 * Math.PI / 180;
scene.add(plane);
}
}
}
// 天井
var uGeometry = new THREE.PlaneGeometry(BLOCK_SIZE, BLOCK_SIZE);
var uTexture = new THREE.ImageUtils.loadTexture("wall01.jpg");
var uMaterial = new THREE.MeshPhongMaterial({map: uTexture, side: THREE.DoubleSide, bumpMap: pTexture, bumpScale: 0.2});
for (i = 0, max = MAP.length; i < max; i = i + 1) {
for (j = 0, max2 = MAP[i].length; j < max2; j = j + 1) {
if (MAP[i][j] == 0) {
var plane = new THREE.Mesh(uGeometry, uMaterial);
plane.position.set(BLOCK_SIZE * j, BLOCK_SIZE, BLOCK_SIZE * i);
plane.rotation.x = 90 * Math.PI / 180;
scene.add(plane);
}
}
}
左上のマップ部分がゲームのメインロジックとしてenchant.jsで作成して、three.jsではマップ部分のplayerインスタンスの位置と角度に応じて、カメラの位置と角度、ライトの位置と角度を一致させています。
main.js(188行目~)
game.rootScene.addEventListener(enchant.Event.ENTER_FRAME, function () {
camera.rotation.y = -((player.rotation + 90) * Math.PI / 180);
camera.position.z = player.y * (BLOCK_SIZE / MAP_BLOCK_SIZE);
camera.position.x = player.x * (BLOCK_SIZE / MAP_BLOCK_SIZE);
light.rotation.y = -((player.rotation + 90) * Math.PI / 180);
light.position.z = player.y * (BLOCK_SIZE / MAP_BLOCK_SIZE);
light.position.x = player.x * (BLOCK_SIZE / MAP_BLOCK_SIZE);
renderer.render(scene, camera);
});
このようにゲームロジックとしてはenchant.jsで作成した2Dのシンプルなものですが、表現方法を3Dにするだけで現代的な3Dゲームの雰囲気が出たかと思います。サンプル用に記述したJavaScriptも200行程度と、作り慣れてしまえば数時間で作成出来てしまうレベルです。本格的な3Dゲームとなると三次元物理演算が必要になり、three.jsも絡めてゲームロジックを設計する必要があるために、今回のようにシンプルにはいかないかと思いますが、パズルやシュミレーション、2Dアクションや2Dシューティング等のゲームをenchant.jsで作成して、3D表現をthree.jsに任せるなんて方法もありなのかなと思いました。
今回はWebGLの3Dライブラリとしてthree.jsを利用しましたが、個人的にはFlashStage3Dでも有名なAway3DがTypeScriptで発表された事に非常に注目しています。まだWebGLの3Dライブラリとしての実績がなく、TypeScript版を触っている人が少ないために、有用なブログ記事も少なく、学習コストがかかりそうですが、個人的にはTypeScriptで記述出来るのは嬉しい事です。
Away3Dに関しての説明は、ClockMakerさんのブログがわかりやすいかと思います。
HTML5で3Dを実現する本格派WebGLフレームワーク、Away3D TypeScriptの公式デモ
Away3Dに関しては非常に興味があるのですが、まずはthree.jsを使って3Dグラフィックスの世界の基礎を学んで行こうかと思います。
ピンバック: Box2DJS(二次元物理エンジン)とthree.js(3D表現)のシンプルな組み合わせ | KnockKnock
ピンバック: チームラマ 11/13日報 担当 石橋 | Fujie Lab Blog