RCカーグランプリ

「RCカーグランプリ」はスマートフォンでもプレイ出来ます。

このゲームは、enchant.jsとJavaScript 3D libraryであるthree.jsでつくりました。
このゲームは、PCのブラウザやスマートフォンで自作ゲームを公開することができるサイト「PLiCy」でもプレイできます。https://plicy.net/GamePlay/45508
ゲーム説明
キーボードの カーソルキー「→」、「←」で左右にハンドルを切ります。 カーソルキー「↑」、「z」でアクセル「↓」、「x」でブレーキです。
スマートフォンでの操作は、プレイ画面右側タップで右ハンドル画面左側タップで左ハンドルです。スピードは自動制御です。
クリックでゲームがスタートします
3Dコンテンツを制作できるJavaScriptライブラリthree.jsを使って手軽にゲームを作る方法について書きます。まずthree.jsはゲームライブラリではないのでゲームライブラリenchant.jsであらかじめゲームを作りthree.jsにレンダリングして3Dゲームにする事にします。
enchant.jsはカンタンにゲームをつくる事ができる JavaScriptフレームワークで2Dゲームをつくる上で有用な機能が備わっています。幾つかのシーンを切り替えるゲームがつくれます。スプライトに関する命令が充実しています。スプライトの当たり判定もあります。2次元配列を使ったゲームマップに関する命令があります。マップとの当たり判定もあります。
まずenchant.jsのサイトからlatest version of enchant.jsをダウンロードします。適当な名前のフォルダをつくりenchant.min.jsとimagesフォルダにあるmap0.pngおよびicon0.pngをそこにコピーします。次にメモ帳でindex.htmlとmain.jsを作ります。
index.html

<!DOCTYPE html>
<html>
<head>
<script src="enchant.min.js"></script>
<script src="main.js"></script>
</head>
<body>
</body>
</html>

main.js

window.focus();
enchant();
var STAGE_WIDTH = 800;
var STAGE_HEIGHT = 600;
// 配列でマップデータを用意する
var MAP = [
    [7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7],
    [7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7],
    [7, 0, 3, 3, 3, 3, 0, 3, 3, 3, 0, 7],
    [7, 0, 3, 0, 0, 0, 0, 3, 0, 0, 0, 7],
    [7, 0, 3, 0, 3, 3, 0, 0, 0, 3, 0, 7],
    [7, 0, 3, 0, 3, 0, 0, 3, 3, 3, 0, 7],
    [7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7],
    [7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
];
var BLOCK_SIZE = 100;
var PLAYER_ROTATION_SPEED = 1.8;
var MAP_BLOCK_SIZE = 16;
var PLAYER_MOVE_SPEED = 0.5;
var PLAYER_ROTATION_SPEED = Math.PI / 2;
window.onload = function() {
    // ゲーム開始
    game = new Game(STAGE_WIDTH, STAGE_HEIGHT);
    game.preload("map0.png","icon0.png");
    game.fps = 60;
    game.onload = function() {
        /* ---------- ゲームクラス ---------- */
        // マップ
        var Field = Class.create(Map, {
            initialize: function(image, loadData, collisionData) {
                Map.call(this, MAP_BLOCK_SIZE, MAP_BLOCK_SIZE);
                this.image = image;
                this.loadData(loadData);
                if (collisionData) this.collisionData = collisionData;
            }
        });
        // プレーヤー
        var Player = Class.create(Sprite, {
            initialize: function(image, x, y) {
                Sprite.call(this, MAP_BLOCK_SIZE, MAP_BLOCK_SIZE);
                this.x = x;
                this.y = y;
                this.image = image;
                this.frame = 58;
                this.rotation = 0;
                this.addEventListener(enchant.Event.ENTER_FRAME, this.onEnterFrame);
            },
            onEnterFrame: function() {
                moveX = 0;
                moveY = 0;
                this.isMove = false;
                if (game.input.left) {
                    this.rotation -= PLAYER_ROTATION_SPEED;
                }
                if (game.input.right) {
                    this.rotation += PLAYER_ROTATION_SPEED;
                }
                if (game.input.up) {
                    moveX = Math.cos(this.rotation * Math.PI / 180) * PLAYER_MOVE_SPEED;
                    moveY = Math.sin(this.rotation * Math.PI / 180) * PLAYER_MOVE_SPEED;
                    this.isMove = true;
                }
                if (game.input.down) {
                    moveX = Math.cos(this.rotation * Math.PI / 180) * -PLAYER_MOVE_SPEED;
                    moveY = Math.sin(this.rotation * Math.PI / 180) * -PLAYER_MOVE_SPEED;
                    this.isMove = true;
                }
                if (field.hitTest(this.x + moveX + 4, this.y + moveY + 4)) {
                    this.isMove = false;
                } else if (field.hitTest(this.x + moveX + 6, this.y + moveY + 4)) {
                    this.isMove = false;
                } else if (field.hitTest(this.x + moveX + 6, this.y + moveY + 6)) {
                    this.isMove = false;
                } else if (field.hitTest(this.x + moveX + 4, this.y + moveY + 6)) {
                    this.isMove = false;
                }
                if (this.isMove) {
                    this.x += moveX;
                    this.y += moveY;
                }
            }
        });
        // シーン
        var mapGroup = new Group();
        mapGroup.x = 3;
        mapGroup.y = 3;
        game.rootScene.addChild(mapGroup);
        // マップ
        var field = new Field(game.assets["map0.png"], MAP, MAP);
        mapGroup.addChild(field);
        field.opacity = 1;
        // プレーヤー
        var player = new Player(game.assets["icon0.png"], MAP_BLOCK_SIZE * 6, MAP_BLOCK_SIZE);
        game.rootScene.addChild(player);
        player.opacity = 1;
    };
    game.start();
};

上記main.jsの説明です。
マップとスプライトに関する命令を使うためMapクラスとSpriteクラスを使っています。
スプライトを使うためSpriteクラスを継承

var Player = Class.create(Sprite, {
initialize:function(image, x, y) {
//最初に一回だけ呼び出す関数
},
onenterframe:function() {
//毎フレームごとに呼びだす関数
}
});

マップを使うためMapクラスを継承

var Field = Class.create(Map, {
initialize: function(image, loadData, collisionData) {
Map.call(this, MAP_BLOCK_SIZE, MAP_BLOCK_SIZE);
this.image = image;
this.loadData(loadData);
if (collisionData) this.collisionData = collisionData;
}
});

単純なゲームマップと自機だけのサンプルが出来ます。問題なく動きます。
このフォルダにgithubからダウンロードしたthree.js-masterのbuildフォルダにあるthree.min.jsをコピーします。そしてindex.htmlとmain.jsを下記ソースに変更します。
index.html

<!DOCTYPE html>
<html>
<head>
<script src="enchant.min.js"></script>
<script src="three.min.js"></script>
<script src="main.js"></script>
</head>
<body>
</body>
</html>

main.js

window.focus();
enchant();
var STAGE_WIDTH = 800;
var STAGE_HEIGHT = 600;
// 配列でマップデータを用意する
var MAP = [
    [7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7],
    [7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7],
    [7, 0, 3, 3, 3, 3, 0, 3, 3, 3, 0, 7],
    [7, 0, 3, 0, 0, 0, 0, 3, 0, 0, 0, 7],
    [7, 0, 3, 0, 3, 3, 0, 0, 0, 3, 0, 7],
    [7, 0, 3, 0, 3, 0, 0, 3, 3, 3, 0, 7],
    [7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7],
    [7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
];
var BLOCK_SIZE = 100;
var PLAYER_ROTATION_SPEED = 1.8;
var MAP_BLOCK_SIZE = 16;
var PLAYER_MOVE_SPEED = 0.5;
var PLAYER_ROTATION_SPEED = Math.PI / 2;
window.onload = function() {
    // ゲーム開始
    game = new Game(STAGE_WIDTH, STAGE_HEIGHT);
    game.preload("map0.png","icon0.png");
    game.fps = 60;
    game.onload = function() {
        /* ---------- ゲームクラス ---------- */
        // マップ
        var Field = Class.create(Map, {
            initialize: function(image, loadData, collisionData) {
                Map.call(this, MAP_BLOCK_SIZE, MAP_BLOCK_SIZE);
                this.image = image;
                this.loadData(loadData);
                if (collisionData) this.collisionData = collisionData;
            }
        });
        // プレーヤー
        var Player = Class.create(Sprite, {
            initialize: function(image, x, y) {
                Sprite.call(this, MAP_BLOCK_SIZE, MAP_BLOCK_SIZE);
                this.x = x;
                this.y = y;
                this.image = image;
                this.frame = 58;
                this.rotation = 0;
                this.addEventListener(enchant.Event.ENTER_FRAME, this.onEnterFrame);
            },
            onEnterFrame: function() {
                moveX = 0;
                moveY = 0;
                this.isMove = false;
                if (game.input.left) {
                    this.rotation -= PLAYER_ROTATION_SPEED;
                }
                if (game.input.right) {
                    this.rotation += PLAYER_ROTATION_SPEED;
                }
                if (game.input.up) {
                    moveX = Math.cos(this.rotation * Math.PI / 180) * PLAYER_MOVE_SPEED;
                    moveY = Math.sin(this.rotation * Math.PI / 180) * PLAYER_MOVE_SPEED;
                    this.isMove = true;
                }
                if (game.input.down) {
                    moveX = Math.cos(this.rotation * Math.PI / 180) * -PLAYER_MOVE_SPEED;
                    moveY = Math.sin(this.rotation * Math.PI / 180) * -PLAYER_MOVE_SPEED;
                    this.isMove = true;
                }
                if (field.hitTest(this.x + moveX + 4, this.y + moveY + 4)) {
                    this.isMove = false;
                } else if (field.hitTest(this.x + moveX + 6, this.y + moveY + 4)) {
                    this.isMove = false;
                } else if (field.hitTest(this.x + moveX + 6, this.y + moveY + 6)) {
                    this.isMove = false;
                } else if (field.hitTest(this.x + moveX + 4, this.y + moveY + 6)) {
                    this.isMove = false;
                }
                if (this.isMove) {
                    this.x += moveX;
                    this.y += moveY;
                }
            }
        });
        // シーン
        var mapGroup = new Group();
        mapGroup.x = 3;
        mapGroup.y = 3;
        game.rootScene.addChild(mapGroup);
        // マップ
        var field = new Field(game.assets["map0.png"], MAP, MAP);
        mapGroup.addChild(field);
        field.opacity = 1; //これを0に設定するとマップが非表示となります。
        // プレーヤー
        var player = new Player(game.assets["icon0.png"], MAP_BLOCK_SIZE * 6, MAP_BLOCK_SIZE);
        game.rootScene.addChild(player);
        player.opacity = 1; //これを0に設定すると自機が非表示となります。
        /* ---------- 3Dアクション ---------- */
        // シーン
        var scene = new THREE.Scene();
        // 壁1
        var geometry = new THREE.CubeGeometry(BLOCK_SIZE, BLOCK_SIZE / 3, BLOCK_SIZE);
        var loader = new THREE.TextureLoader();
        texture=loader.load("wall01.png");
        var material = new THREE.MeshBasicMaterial({
            color: "darkgray"
            //map: texture //画像があれば上のcolorの行を削除してコメントアウト解除
        });
        for (i = 0, max = MAP.length; i < max; i = i + 1) {
            for (j = 0, max2 = MAP[0].length; j < max2; j = j + 1) {
                if (MAP[i][j] == 7) {
                    cube = new THREE.Mesh(geometry, material);
                    cube.position.set(BLOCK_SIZE * j, BLOCK_SIZE / 6, BLOCK_SIZE * i);
                    scene.add(cube);
                }
            }
        }

        // 壁no2
        geometry = new THREE.CubeGeometry(BLOCK_SIZE, BLOCK_SIZE / 7, BLOCK_SIZE);
        loader = new THREE.TextureLoader();
        texture=loader.load("wall02.png");
        material = new THREE.MeshBasicMaterial({
            color: "darkgreen"
            //map: texture //画像があれば上のcolorの行を削除してコメントアウト解除
        });
        for (i = 0, max = MAP.length; i < max; i = i + 1) {
            for (j = 0, max2 = MAP[0].length; j < max2; j = j + 1) {
                if (MAP[i][j] == 3) {
                    cube = new THREE.Mesh(geometry, material);
                    cube.position.set(BLOCK_SIZE * j, BLOCK_SIZE / 14, BLOCK_SIZE * i);
                    scene.add(cube);
                }
            }
        }
        // 床
        var pGeometry = new THREE.PlaneGeometry(MAP[0].length * BLOCK_SIZE, MAP.length * BLOCK_SIZE);
        loader = new THREE.TextureLoader();
        texture=loader.load("course.png");
        var pMaterial = new THREE.MeshBasicMaterial({
            color: "palegreen",
            //map: texture, //画像があれば上のcolorの行を削除してコメントアウト解除
            side: THREE.BackSide
        });
        var plane = new THREE.Mesh(pGeometry, pMaterial);
        plane.position.set(MAP[0].length * BLOCK_SIZE / 2,0,MAP.length * BLOCK_SIZE / 2);
        plane.rotation.x = 90 * Math.PI / 180;
        scene.add(plane);
        //自機
        geometry = new THREE.CylinderGeometry(10, 14, 14, 4);
        loader = new THREE.TextureLoader();
        texture=loader.load("treasure.png");
        var side_material = new THREE.MeshPhongMaterial({
            color: "brown"
            //map: texture //画像があれば上のcolorの行を削除してコメントアウト解除
        });
        var end_material = new THREE.MeshPhongMaterial({
            color: "salmon"
        });
        material = new THREE.MeshFaceMaterial([side_material, end_material]);
        var machine = new THREE.Mesh(geometry, material);
        scene.add(machine);
        // light
        var ambient = new THREE.AmbientLight(0xFFFFF0);
        scene.add(ambient);
        // camera
        var camera = new THREE.PerspectiveCamera(45, STAGE_WIDTH / STAGE_HEIGHT, 1, 10000);
        camera.position.set(0, BLOCK_SIZE / 2, 0);
        // rendering
        var renderer = new THREE.WebGLRenderer();
        renderer.setSize(STAGE_WIDTH, STAGE_HEIGHT);
        renderer.setClearColor(0x000000, 1);
        renderer.shadowMapEnabled = true;
        document.getElementById("enchant-stage").appendChild(renderer.domElement);
        /* ---------- ゲームイベント ---------- */
        game.rootScene.addEventListener(enchant.Event.ENTER_FRAME, function() {
            machine.position.y = 7;
            machine.position.z = (player.y - 3) * (BLOCK_SIZE / MAP_BLOCK_SIZE);
            machine.position.x = (player.x - 3) * (BLOCK_SIZE / MAP_BLOCK_SIZE);
            machine.rotation.y = -(player.rotation * Math.PI / 180);
            camera.position.y = 100;
            camera.position.z = (player.y - 3) * (BLOCK_SIZE / MAP_BLOCK_SIZE) - Math.sin(player.rotation * Math.PI / 180) * 120;
            camera.position.x = (player.x - 3) * (BLOCK_SIZE / MAP_BLOCK_SIZE) - Math.cos(player.rotation * Math.PI / 180) * 120;
            camera.lookAt(machine.position);
            renderer.render(scene, camera);
        });
    };
    game.start();
};

問題なく動きます。マップはfield.opacity = 0自機はplayer.opacity = 0にすれば非表示となります。
このようにenchant.jsでできたゲーム本体と実際に表示をするthree.jsを分けて管理できます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です