スカッシュゲームを作る(5) - Pointer Lock API を試す

公開:2015-07-29 06:26
更新:2017-11-15 21:58
カテゴリ:スカッシュゲーム,ゲーム製作,html5,webgl,three.js,js

マウス操作を改善するために、Pointer Lock APIを使ってみた。

Pointer Lock APIを使うとマウス座標が移動量で検出できて、マウスカーソルも消える。おかげでパドルコントロールにおけるストレスがかなり解消された。しかし依然として面白くはない。これは一人スカッシュという遊びそのものが面白くないせいかもしれないけども、改良すればもう少し面白くできるのではないかと思う。

で、改良のための参考として2人用だけどPongを観てみると、ボールの跳ね返す角度が、パドルでコントロールできるようになっている。

ボールをパドルでコントロールできれば楽しそうだ。次はそれを実装することにしよう。

YoutubeでPongの動画をチェックしていると下の古い動画を見つけた。ボールの動きが可笑しいね。。

動作サンプル

新しいウィンドウで開く

ソースコード・リソース

/dev/squash/0004/index.html

<!DOCTYPE html>
<html>
<head>
    <title>スカッシュゲームを作る - Pointer Lock APIを使用してみる。</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();
            // Pointer Lock API
            var isPointerLocked = false;
            var isPointerRequesting = false;
            var elm = renderer.domElement;//document.body;
            function pointerLockChange() {
                if (
                    document.pointerLockElement === elm ||
                    document.mozPointerLockElement === elm ||
                    document.webkitPointerLockElement === elm) {
                    isPointerLocked = true;
                } else {
                    isPointerLocked = false;
                }
                isPointerRequesting = false;
                
            }
            document.addEventListener('pointerlockchange',pointerLockChange,false);
            document.addEventListener('mozpointerlockchange',pointerLockChange,false);
            document.addEventListener('webkitpointerlockchange',pointerLockChange,false);
            elm.requestPointerLock = elm.requestPointerLock    ||
                                      elm.mozRequestPointerLock ||
                                      elm.webkitRequestPointerLock;



            // カメラを工夫し、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);


            elm.addEventListener('mousemove', function (e) {
                if(isPointerLocked){
                    var movementX = e.movementX       ||
                                    e.mozMovementX    ||
                                    e.webkitMovementX ||
                                    0,
                        movementY = e.movementY       ||
                                    e.mozMovementY    ||
                                    e.webkitMovementY ||
                                    0;
                    px += movementX;
                    if(px < ( -WIDTH / 2)) px = - WIDTH / 2;
                    if(px > (  WIDTH  / 2)) px = WIDTH / 2;
                } else {
                    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 click = false;
            elm.addEventListener('click',function(){
                click = true;
                if((!isPointerLocked) &&  elm.requestPointerLock){
                    isPointerRequesting = true;
                    elm.requestPointerLock();
                } else {
                    isPointerRequesting = false;
                }
            });

            function mouseCheck(){
                var ret = click;
                click = 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;
                    click =  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;
                                while(isPointerRequesting){
                                    yield;
                                }
                                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 = Math.abs(dx);
                            dy = Math.abs(dy);
                            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;
                        // 徐々に難易度を上げていく
                        
                        if (dx < 3.8){
                            dx = Math.sign(dx) * (Math.abs(dx) + 0.025);
                            dy = Math.sign(dy) * (Math.abs(dy) + 0.025);
                        }
                        updateScore();
                    }
                    return true;
                }

                //
                var g = game();

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

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

/dev/squash/0004/README.md

### Pointer Lock APIを使ってみる

* Pointer Lock APIを使って、マウスを占有してみた。
* ESCを押すとPointer Lockを停止します。
* EdgeではPointer Lock APIが未実装のため、Pointer Lockできません。
* iPhoneでは今のところ動作しません。理由は不明ですが、おそらくES6 Generatorを使っているせいだと思われます。

/dev/squash/0004/screen.png

/dev/squash/0004/thumbnail.png