スカッシュゲームを作る(3) - ゲームとしての体裁を整える

公開:2015-07-26 06:24
更新:2020-02-15 04:37
カテゴリ:スカッシュゲーム,ゲーム製作,html5,webgl,three.js,js,コルーチン

昨日今日と文字列表示やゲームフローの実装を行っていた。

ゲームフローの実装

ES6のジェネレータを使って実装してみた。イテレータとしてではなくコルーチン的な使い方である。非常に簡潔にゲームフローを書くことができる。

文字列表示の作成

昔の粗いドット風の文字列表示を行うコードを書いた。このゲームは仮想座標として192×256の大きさを持っている。文字に関しては仮想座標上の2×2ピクセルもしくは4×4ピクセルを1ドットとしている。その1ドットにそれぞれメッシュを割り当てて表示しているのである。なんと贅沢な。メッシュとジオメトリの関係についてまだ知識が浅いので、もっと効率的かつリソース消費を抑えることができると思う。Three.jsのそのあたりについてもう少し勉強しないといけない。

ゲームの内容について

ゲーム・ルールは以下のとおりである。

今のところ、単純極まりないルールで、ゲームとしての最低限の内容である。全く面白くない。これにいろいろなルールを追加していくことでゲームを面白くしていく。まずは難易度を徐々に上げていくようにしないといけないだろう。

動作サンプル

新しいウィンドウで開く

ソースコード・リソース

/dev/squash/0002/README.md

### ゲームとしての体裁を整えた


* 文字列表示・スコア表示を追加

* ES6ジェネレータによるゲームフローの実装

/dev/squash/0002/index.html

<!DOCTYPE html>
<html>
<head>
    <title>スカッシュゲームを作る - スコア表示・ゲームスタート・ゲームオーバーの追加</title>
    <meta name="keywords" content="WebGL,HTML5,three.js" />
    <meta name="description" content="WebGL,HTML5,three.js" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
    <meta charset="UTF-8">
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.js"></script>
    <style>
        html {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        body {
            width: 100%;
            height: 100%;
            margin: 4px;
            padding: 0;
            border: 0;
            text-align: center;
            margin-left: auto;
            margin-right: auto;
        }

        #console {
            margin-left: auto;
            margin-right: auto;
            border: 0;
            padding: 0;
        }
    </style>
</head>
<body>
    <div id="content"></div>
    <script type="text/javascript">
        const ASCII_CHARS = [
            [
                0,0,0,0,0,
                0,0,0,0,0,
                0,0,0,0,0,
                0,0,0,0,0,
                0,0,0,0,0
            ],
            [
                0,0,1,0,0,
                0,0,1,0,0,
                0,0,1,0,0,
                0,0,0,0,0,
                0,0,1,0,0
            ],
            [
                0,1,0,1,0,
                0,1,0,1,0,
                0,0,0,0,0,
                0,0,0,0,0,
                0,0,0,0,0
            ],
            [
                0,1,0,1,0,
                1,1,1,1,1,
                0,1,0,1,0,
                1,1,1,1,1,
                0,1,0,1,0
            ],
            [
                1,1,1,1,1,
                1,0,1,0,0,
                1,1,1,1,1,
                0,0,1,0,1,
                1,1,1,1,1
            ],
            [
                1,1,0,0,1,
                1,1,0,1,0,
                0,0,1,0,0,
                0,1,0,1,1,
                1,0,0,1,1
            ],
            [
                1,1,1,0,0,
                1,0,1,0,0,
                0,1,1,0,1,
                1,0,0,1,0,
                1,1,1,0,1
            ],
            [
                0,0,1,0,0,
                0,0,1,0,0,
                0,0,0,0,0,
                0,0,0,0,0,
                0,0,0,0,0
            ],
            [
                0,0,1,1,0,
                0,1,0,0,0,
                0,1,0,0,0,
                0,1,0,0,0,
                0,0,1,1,0
            ],
            [
                0,1,1,0,0,
                0,0,0,1,0,
                0,0,0,1,0,
                0,0,0,1,0,
                0,1,1,0,0
            ],
            [
                1,0,1,0,1,
                0,1,1,1,0,
                1,1,1,1,1,
                0,1,1,1,0,
                1,0,1,0,1
            ],
            [
                0,0,1,0,0,
                0,0,1,0,0,
                1,1,1,1,1,
                0,0,1,0,0,
                0,0,1,0,0
            ],
            [
                0,0,0,0,0,
                0,0,0,0,0,
                0,0,0,0,0,
                0,0,1,0,0,
                0,1,0,0,0
            ],
            [
                0,0,0,0,0,
                0,0,0,0,0,
                1,1,1,1,1,
                0,0,0,0,0,
                0,0,0,0,0
            ],
            [
                0,0,0,0,0,
                0,0,0,0,0,
                0,0,0,0,0,
                0,0,0,0,0,
                0,0,1,0,0
            ],
            [
                0,0,0,0,1,
                0,0,0,1,0,
                0,0,1,0,0,
                0,1,0,0,0,
                1,0,0,0,0
            ],
            [1,1,1,1,1,
             1,1,0,0,1,
             1,0,1,0,1,
             1,0,0,1,1,
             1,1,1,1,1],
            [0,1,1,0,0,
             0,0,1,0,0,
             0,0,1,0,0,
             0,0,1,0,0,
             0,1,1,1,0],
            [1,1,1,1,1,
             1,0,0,0,1,
             0,0,1,1,0,
             0,1,0,0,0,
             1,1,1,1,1],
            [1,1,1,1,1,
             0,0,0,0,1,
             1,1,1,1,1,
             0,0,0,0,1,
             1,1,1,1,1],
            [1,0,0,1,0,
             1,0,0,1,0,
             1,0,0,1,0,
             1,1,1,1,1,
             0,0,0,1,0],
            [1,1,1,1,1,
             1,0,0,0,0,
             1,1,1,1,1,
             0,0,0,0,1,
             1,1,1,1,1],
            [1,1,1,1,1,
             1,0,0,0,0,
             1,1,1,1,1,
             1,0,0,0,1,
             1,1,1,1,1],
            [1,1,1,1,1,
             1,0,0,1,0,
             0,0,1,0,0,
             0,1,0,0,0,
             1,0,0,0,0],
            [1,1,1,1,1,
             1,0,0,0,1,
             1,1,1,1,1,
             1,0,0,0,1,
             1,1,1,1,1],
            [1,1,1,1,1,
             1,0,0,0,1,
             1,1,1,1,1,
             0,0,0,0,1,
             1,1,1,1,1],
            [
                0,0,0,0,0,
                0,0,1,0,0,
                0,0,0,0,0,
                0,0,1,0,0,
                0,0,0,0,0
            ],
            [
                0,0,0,0,0,
                0,0,1,0,0,
                0,0,0,0,0,
                0,0,1,0,0,
                0,1,0,0,0
            ],
            [
                0,0,0,1,0,
                0,0,1,0,0,
                0,1,0,0,0,
                0,0,1,0,0,
                0,0,0,1,0
            ],
            [
                0,0,0,0,0,
                1,1,1,1,1,
                0,0,0,0,0,
                1,1,1,1,1,
                0,0,0,0,0
            ],
            [
                0,1,0,0,0,
                0,0,1,0,0,
                0,0,0,1,0,
                0,0,1,0,0,
                0,1,0,0,0
            ],
            [
                1,1,1,1,1,
                1,0,0,0,1,
                0,0,1,1,1,
                0,0,0,0,0,
                0,0,1,0,0
            ],
            [
                0,1,1,1,0,
                1,0,0,0,1,
                1,0,1,1,1,
                1,0,0,0,0,
                0,1,1,1,1
            ],
            [
                1,1,1,1,1,
                1,0,0,0,1,
                1,1,1,1,1,
                1,0,0,0,1,
                1,0,0,0,1
            ],
            [
                1,1,1,1,1,
                1,0,0,0,1,
                1,1,1,1,0,
                1,0,0,0,1,
                1,1,1,1,1
            ],
            [
                1,1,1,1,1,
                1,0,0,0,0,
                1,0,0,0,0,
                1,0,0,0,0,
                1,1,1,1,1
            ],
            [
                1,1,1,1,0,
                1,0,0,0,1,
                1,0,0,0,1,
                1,0,0,0,1,
                1,1,1,1,0
            ],
            [
                1,1,1,1,1,
                1,0,0,0,0,
                1,1,1,1,1,
                1,0,0,0,0,
                1,1,1,1,1
            ],
            [
                1,1,1,1,1,
                1,0,0,0,0,
                1,1,1,1,1,
                1,0,0,0,0,
                1,0,0,0,0
            ],
            [
                1,1,1,1,1,
                1,0,0,0,0,
                1,0,1,1,1,
                1,0,0,0,1,
                1,1,1,1,1
            ],
            [
                1,0,0,0,1,
                1,0,0,0,1,
                1,1,1,1,1,
                1,0,0,0,1,
                1,0,0,0,1
            ],
            [
                0,0,1,0,0,
                0,0,1,0,0,
                0,0,1,0,0,
                0,0,1,0,0,
                0,0,1,0,0
            ],
            [
                1,1,1,1,1,
                0,0,1,0,0,
                0,0,1,0,0,
                1,0,1,0,0,
                1,1,1,0,0
            ],
            [
                1,0,0,,1,
                1,0,0,1,0,
                1,1,1,0,0,
                1,0,0,1,0,
                1,0,0,0,1
            ],
            [
                1,0,0,0,0,
                1,0,0,0,0,
                1,0,0,0,0,
                1,0,0,0,0,
                1,1,1,1,1
            ],
            [
                1,1,0,1,1,
                1,0,1,0,1,
                1,0,0,0,1,
                1,0,0,0,1,
                1,0,0,0,1
            ],
            [
                1,0,0,0,1,
                1,1,0,0,1,
                1,0,1,0,1,
                1,0,0,1,1,
                1,0,0,0,1
            ],
            [
                1,1,1,1,1,
                1,0,0,0,1,
                1,0,0,0,1,
                1,0,0,0,1,
                1,1,1,1,1
            ],
            [
                1,1,1,1,1,
                1,0,0,0,1,
                1,1,1,1,1,
                1,0,0,0,0,
                1,0,0,0,0
            ],
            [
                1,1,1,1,1,
                1,0,0,0,1,
                1,0,0,0,1,
                1,0,0,1,1,
                1,1,1,1,1
            ],
            [
                1,1,1,1,1,
                1,0,0,0,1,
                1,1,1,1,0,
                1,0,0,0,1,
                1,0,0,0,1
            ],
            [
                1,1,1,1,1,
                1,0,0,0,0,
                1,1,1,1,1,
                0,0,0,0,1,
                1,1,1,1,1
            ],
            [
                1,1,1,1,1,
                0,0,1,0,0,
                0,0,1,0,0,
                0,0,1,0,0,
                0,0,1,0,0
            ],
            [
                1,0,0,0,1,
                1,0,0,0,1,
                1,0,0,0,1,
                1,0,0,0,1,
                1,1,1,1,1
            ],
            [
                1,0,0,0,1,
                1,0,0,0,1,
                0,1,0,1,0,
                0,1,0,1,0,
                0,0,1,0,0
            ],
            [
                1,0,1,0,1,
                1,0,1,0,1,
                1,0,1,0,1,
                1,0,1,0,1,
                1,1,1,1,1
            ],
            [
                1,0,0,0,1,
                0,1,0,1,0,
                0,0,1,0,0,
                0,1,0,1,0,
                1,0,0,0,1
            ],
            [
                1,0,0,0,1,
                0,1,0,1,0,
                0,0,1,0,0,
                0,0,1,0,0,
                0,0,1,0,0
            ],
            [
                1,1,1,1,1,
                0,0,0,1,0,
                0,0,1,0,0,
                0,1,0,0,0,
                1,1,1,1,1
            ]
        ];

        window.addEventListener('load',
        function () {

            'use strict';
            const WIDTH = 192;
            const HEIGHT = 256;
            var screen_width;
            var screen_height;
            var remain = 3;
            var score = 0;
            var renderer;
            var x = 0;
            var y = 0;
            var dx = 3;
            var dy = 3;
            var px;// paddle x pos

            function calcScreenSize() {
                screen_width = document.body.clientWidth - 8;
                screen_height = document.body.clientHeight - 8;
                if (screen_width >= screen_height) {
                    screen_width = screen_height * WIDTH / HEIGHT;
                } else {
                    screen_height = screen_width * HEIGHT / WIDTH;
                }
            }

            calcScreenSize();

            renderer = new THREE.WebGLRenderer({ antialias: false /*, sortObjects: true */ });
            renderer.setSize(screen_width, screen_height);
            renderer.setClearColor(0x000000, 1);
            renderer.domElement.id = 'console';
            renderer.domElement.style.zIndex = 0;
            document.body.appendChild(renderer.domElement);
            renderer.clear();

            // カメラを工夫し、Z座標が0の時座標指定が仮想画面サイズの位置となるようにする
            var camera = new THREE.PerspectiveCamera(90, WIDTH / HEIGHT, 0.1, 1000);
            camera.position.z = HEIGHT / 2;
            var scene = new THREE.Scene();
            var geometry = new THREE.PlaneBufferGeometry(4, 4);
            var material = new THREE.MeshBasicMaterial({ color: 0xffffff });
            var ball = new THREE.Mesh(geometry, material);
            var paddle = new THREE.Mesh(new THREE.PlaneBufferGeometry(32, 4), new THREE.MeshBasicMaterial({ color: 0xffffff }));
            paddle.position.y = -100;

            // 文字コード -> mesh 変換
            var asciiCharObjs4 = [];
            var asciiCharObjs2 = [];
            var asciiGeometry2 = new THREE.PlaneBufferGeometry(2, 2);
            var asciiGeometry4 = new THREE.PlaneBufferGeometry(4,4);

            for(var i = 0,l = ASCII_CHARS.length;i < l;++i ){
                var c2 = new THREE.Object3D();
                var c4 = new THREE.Object3D();
                asciiCharObjs2.push(c2);
                asciiCharObjs4.push(c4);
                for(var cy = 0;cy < 5;++cy){
                    for(var cx = 0;cx < 5;++cx){
                        if(ASCII_CHARS[i][cy * 5 + cx]){
                            var mesh = new THREE.Mesh(asciiGeometry2,material);
                            mesh.position.x = cx * 2;
                            mesh.position.y = 10 - cy * 2;
                            c2.add(mesh);
                            var mesh = new THREE.Mesh(asciiGeometry4,material);
                            mesh.position.x = cx * 4;
                            mesh.position.y = 10 - cy * 4;
                            c4.add(mesh);

                        }
                    }
                }
            }

            function createStringMesh(str,size){
                if(!size)  size = 2;
                var strObj = new THREE.Object3D();
                var asciiChars = size == 4?asciiCharObjs4:asciiCharObjs2;
                for(var i = 0,l = str.length;i < l;++i){
                    var sx = i * 6 * size;
                    var c = str.charCodeAt(i) - 0x20;
                    var co = asciiChars[c].clone();
                    co.position.x = sx;
                    strObj.add(co);
                }
                return strObj;
            }


            // PRESS_MOUSE 文字列
            const PRESS_MOUSE = 'PRESS MOUSE BTN';
            var pressMouse = createStringMesh(PRESS_MOUSE);
            pressMouse.position.x = - PRESS_MOUSE.length  * 2 * 6 / 2;
            pressMouse.position.y = 0;
            scene.add(pressMouse);

            // Title
            const TITLE = 'SQUASH';
            var titleObj = createStringMesh(TITLE,4);
            titleObj.position.x = - TITLE.length  * 4 * 6 / 2;
            titleObj.position.y = 70;
            scene.add(titleObj);

            // GAME OVER
            const GAME_OVER = 'GAME OVER';
            var gameOverObj = createStringMesh(GAME_OVER,2);
            gameOverObj.position.x = - GAME_OVER.length  * 2 * 6 / 2;
            gameOverObj.position.y = 40;
            scene.add(gameOverObj);



            // スコア表示用
            var scoreObj = new THREE.Object3D();
            for(var i = 0;i < 5;++i){
                var sx = i * 6 * 2;
                var digit = new THREE.Object3D();
                scoreObj.add(digit);
                for(var j = 0;j < 10;++j){
                    var n = asciiCharObjs2[0x10 + j].clone();
                    n.position.x = sx;
                    n.visible = false;
                    digit.add(n);
                }
            }
            scoreObj.position.y = 110;
            scoreObj.position.x = - 6 * 2 * 5 / 2;
            scoreObj.children[0].children[0].visible = true;
            scoreObj.children[1].children[0].visible = true;
            scoreObj.children[2].children[0].visible = true;
            scoreObj.children[3].children[0].visible = true;
            scoreObj.children[4].children[0].visible = true;

            scene.add(scoreObj);
            var scoreBackup = score;
            function updateScore(){
                if(score > 99999){
                    score = 99999;
                }

                var c5 = parseInt(score / 10000) % 10;
                var c4 = parseInt(score / 1000) % 10;
                var c3 = parseInt(score / 100) % 10;
                var c2 = parseInt(score / 10) % 10;
                var c1 = parseInt(score) % 10;

                var b5 = parseInt(scoreBackup / 10000) % 10;
                var b4 = parseInt(scoreBackup / 1000) % 10;
                var b3 = parseInt(scoreBackup / 100) % 10;
                var b2 = parseInt(scoreBackup / 10) % 10;
                var b1 = parseInt(scoreBackup) % 10;

                scoreObj.children[0].children[b5].visible = false;
                scoreObj.children[0].children[c5].visible = true;
                scoreObj.children[1].children[b4].visible = false;
                scoreObj.children[1].children[c4].visible = true;
                scoreObj.children[2].children[b3].visible = false;
                scoreObj.children[2].children[c3].visible = true;
                scoreObj.children[3].children[b2].visible = false;
                scoreObj.children[3].children[c2].visible = true;
                scoreObj.children[4].children[b1].visible = false;
                scoreObj.children[4].children[c1].visible = true;

                scoreBackup = score;

            }

            // 残数表示
            var remainObj = new THREE.Object3D();
            for(var j = 0;j < 10;++j){
                var n = asciiCharObjs2[j + 0x10].clone();
                n.visible = false;
                remainObj.add(n);
            }
            remainObj.position.y = -124;
            remainObj.position.x = 70;
            var remainBackup = 0;
            function updateRemain(){
                remainObj.children[remain].visible = true;
                remainObj.children[remainBackup].visible = false;
                remainBackup = remain;
            }
            scene.add(remainObj);


            renderer.domElement.addEventListener('mousemove', function (e) {
                var ex = e.clientX;
                var ey = e.clientY;
                var rect = e.target.getBoundingClientRect();
                ex -= rect.left;
                ey -= rect.top;
                px = ex * WIDTH / screen_width - WIDTH / 2;
                //paddle.position.x = x * WIDTH / screen_width - WIDTH / 2;
            });

            var mousedown = false;

            renderer.domElement.addEventListener('mousedown',function(e){
                mousedown = true;
            });

            function mouseCheck(){
                var ret = mousedown;
                mousedown = false;
                return ret;
            }


            window.addEventListener('resize', function () {
                calcScreenSize();
                renderer.setSize(screen_width, screen_height);
            });

            scene.add(ball);
            scene.add(paddle);

            // ジェネレータによるゲームメインの実装
            function* game(){
                while(true){
                    // init
                    remain = 3;
                    updateRemain();
                    x = 0;
                    y = 0;
                    dx = 2;
                    dy = 2;
                    mousedown = false;
                    titleObj.visible = true;
                    paddle.visible = false;
                    ball.visible = false;
                    pressMouse.visible = true;
                    remainObj.visible = false;
                    gameOverObj.visible = false;
                    // game start wait
                    var start = false;
                    while(!mouseCheck() && !start){
                        for(var i = 0;i < 10;++i ){
                            if(mouseCheck()){
                                start = true;
                                break;
                            }
                            yield;
                        }
                        pressMouse.visible = !pressMouse.visible;
                    }
                    score = 0;
                    updateScore();
                    titleObj.visible = false;
                    pressMouse.visible = false;
                    paddle.visible = true;
                    ball.visible = true;
                    remainObj.visible = true;

                    // game play
                    while(remain > 0){
                        if(!play()){
                            x = 0;
                            y = 0;
                            dx = 2;
                            dy = 2;
                            remain--;
                            updateRemain();
                        } else {
                            yield;
                        };
                    }
                    // game over
                    gameOverObj.visible = true;
                    for(var i = 0;i < 5 * 20;++i){
                        yield;
                    }
                    gameOverObj.visible =false;
                    continue;
                }
            }

                function play(){
                    // ボールの動き
                    var bx = x, by = y;
                    x += dx;
                    y += dy;
                    if (x > (WIDTH / 2) || x < (-WIDTH / 2)) {
                        dx = -dx;
                        x += dx;
                    }

                    if (y > (HEIGHT / 2) ) {
                        dy = -dy;
                        y += dy;
                    }

                    if(y < (-HEIGHT / 2)){
                        return false;
                    }

                    ball.position.x = x;
                    ball.position.y = y;

                    // パドルとの衝突判定
                    var sx, sy, ex, ey;
                    if (x >= bx) {
                        sx = bx - 2;
                        ex = x + 2;
                    } else {
                        sx = x - 2;
                        ex = bx + 2;
                    }

                    if (y <= by) {
                        sy = by - 2;
                        ey = y + 2;
                    } else {
                        sy = y - 2;
                        ey = by + 2;
                    }
                    paddle.position.x = px;
                    var psx = paddle.position.x - 16, pex = paddle.position.x + 16, psy = paddle.position.y - 2, pey = paddle.position.y + 2;
                    if (sy <= pey && psy <= ey && sx <= pex && psx <= sx) {
                        var cx = -100 * dy / dx;
                        var cy = -100;
                        y += 2;
                        dy = -dy;
                        y += dy;
                        ++score;
                        updateScore();
                    }
                    return true;
                }

                //
                var g = game();

                function render() {
                    requestAnimationFrame(render);
                    renderer.render(scene, camera);
                    g.next();
                }
                render();

            });
    </script>
</body>
</html>