ゲームエンジンとしてのenchant.jsと3D表現としてのthree.jsのシンプルな組み合わせ

前回の記事で作成したenchant.jsのゲームロジックを元に、three.jsで3D表現を行いました。

three.js

20140114_img

three.jsはWebGLをサポートしたJavaScriptの3D描画ライブラリです。Mr.Doob氏を中心にオープンソースで開発が進められていて、WebGLの3Dライブラリとしての実績も多く、現状ではデファクトスタンダードとなっています。この記事の寄稿時ではリビジョンはr64でした。

three.jsを使って3D表現を行う事は、ダウンロードしたパッケージに格納されているサンプルコードや、多くのthree.js関連のブログ記事を見ればいくらでも学習する事ができます。ただ、せっかくの3D表現なのでゲーム性を持たせたいと考えました。本格的な3Dゲームとなると三次元での物理演算等を行わなくてはなりません。もっと気軽に3D表現で楽しむことは出来ないかと思い、ゲームロジックはenchant.js、表示部分にthree.jsを利用して今回のサンプルを作成しました。

20140114_img2
ソースコード
※操作方法 : 前進「↑」キー、後退「↓」キー、旋回「←→」キー

ゲームといっても、現状はただダンジョンの中をさまようだけですが、このゲームの基幹部分は左上のマップ部分となります。マップ部分の作成は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グラフィックスの世界の基礎を学んで行こうかと思います。

コメント

  1. […] 前回の記事ではenchant.jsとthree.jsを併用しましたが、今回は3D表現としてthree.jsを使う事は変わりませんが、メインのロジック部分にBox2DJSを使用しました。 […]