matter.jsでゲーム作り

思うより簡単にゲームがつくれる事をわかってもらえるよう、プラウザゲームを作ってみます。
簡単に遊べるようにしたいので、物理演算によるシュミレーションゲームを作る事にします。それで、物理演算エンジンmatter.js と相性がよさそうなゲームをいくつか作ってみます。
特徴としては、パソコンでもスマートフォンでも遊べる。 ゲーム性を損なう画面スクロールなどのデフォルトアクションをさせない。ゲーム画面上に文字列を表示する。そんなものにします。
①エアホッケーゲーム
このホームページの投稿「エアホッケー」に出来上がったゲームを載せました。
エアホッケー2がスタート
まず、matter.js をダウンロードします。 https://brm.io/matter-js/ で Latest Buildを選ぶとGitHubに行くので、GitHubに登録してサインインします。そうすると右の方のボタン「Clone or download」が選択可能になるので、「Download ZIP」を選択してZIPファイルをダウンロードします。解凍した「matter-js-master」の中のフォルダ「build」 のなかの「matter.min.js」だけを使います。これを自分のゲームフォルダにセットします。
新規作成で新しいフォルダを作り、「airhockey」など名前を付けて下さい。メモ帳を開き、下記のソースリスト2つをコピーして「index.html」、「main.js」の名前を付けて文字コードUTF8でペーストして、作成したフォルダ「airhockey」 に保存して下さい。もう一つ 「matter.min.js」もコピーして保存して下さい。
下記のソースリストは、画像やサウンドをコメントアウトしていますが、その他は出来上がったゲームと同じです。ローカル環境でフォルダ内の「index.html」を実行して動作確認してみてください。
index.html

<!doctype html>
<html>
<head>
<meta charset="UTF-8"/>
<script src="matter.min.js"></script>
<!-- cssの部分 -->
<style type="text/css">
* {margin: 0;padding: 0;}
html, body { width:100%; height:100%; }
.engine {
  position: absolute;
  left: 0;
  width: 700px;
  height: 1000px; }
  .engine canvas {
    width: 100%;
    height: 100%; }
</style>
</head>
<!-- スマートフォンなどでデフォルトアクションを行わない -->
<body ontouchmove="event.preventDefault()">
<!-- サウンドはmp3で鳴らなければwav -->
<audio id="hit" preload="auto"><source src="hit.wav"></audio>
<audio id="get" preload="auto"><source src="get.wav"></audio>
<audio id="end" preload="auto"><source src="end.wav"></audio>
<!-- ゲームは、このIDの中で実行 -->
<div id="js-engine" class="engine"></div>
<script src="main.js"></script>
</body>
</html>

main.js

(function() {
    const STAGE = [
        [8,10],
        [9,10],
        [10,11],
        [11,11],
        [12,12],
        [13,12],
        [14,13],
        [15,13],
        [16,14],
        [17,14]
    ];
    const Engine = Matter.Engine,
        World = Matter.World,
        Constraint = Matter.Constraint;

    const container = document.getElementById('js-engine');
    let enex = 0;
    let eney = 0;
    let touchx = 0;
    let touchy = 0;
    let attack = false;
    let attack2 = false;
    let level = 0;
    let movex = STAGE[0][0];
    let movey = STAGE[0][1];
    let game = false;
    let sound = false;

    const engine = Engine.create(container, {
        render: {options: {wireframes: false,width: 700,height: 1000}//,background: 'floor.jpg'}画像があればセット
        }
    });
    //重力値
    engine.world.gravity.y = 0;

    const c1 = container;//ゲーム用の画面サイズを縦横比を維持した最大の大きさに設定
    if (window.innerHeight < window.innerWidth * 10 / 7) {
        h = window.innerHeight;
        w = window.innerHeight * 0.7;
    } else {
        w = window.innerWidth;
        h = window.innerWidth * 10 / 7;
    }
    c1.style.height = h + 'px';
    c1.style.width = w + 'px';
    const divHeight = 1000 / h;
    const divWidth = 700 / w;

    const player1 = Matter.Bodies.circle(350, 900, 70,{density:0.9,frictionAir: 0.2,collisionFilter: { group: -1 },inertia: Infinity,label: "player1"})//,render: {sprite: {texture: 'player1.png'}}});画像があればセット
    const body1 = Matter.Bodies.circle(350, 900, 70,{collisionFilter: { group: -1 },inertia: Infinity});
    const player2 = Matter.Bodies.circle(350, 90, 70,{density:0.9,frictionAir: 0.2,collisionFilter: { group: -2 },inertia: Infinity,label: "player2"})//,render: {sprite: {texture: 'player2.png'}}});画像があればセット
    const body2 = Matter.Bodies.circle(350, 90, 70,{collisionFilter: { group: -2 },inertia: Infinity});
    const ball = Matter.Bodies.circle(350,500, 35, {density:0.05,friction: 0,frictionAir: 0,restitution: 1,inertia: Infinity,label: "ball"})//,render: {sprite: {texture: 'Pack.png'}}});画像があればセット

    //マルチボディ拘束 これによりbody1、body2がplayer1、player2に引っ張られる形になってパックとぶつかったときインパクトが発生
    const constraint1 =  Constraint.create({
        bodyA: body1,
        bodyB: player1
    });
    const constraint2 = Constraint.create({
        bodyA: body2,
        bodyB: player2
    });
    World.add(engine.world, [ball,body1,constraint1,player1,body2,constraint2,player2]);//constraint1、constraint2 playerとbodyを同じ座標にセットしてあるので距離0で維持する

    World.add(engine.world, [
        Matter.Bodies.rectangle(72,-79,144,200,{isStatic:true,render: {fillStyle: 'green'}}),//{visible: false}}),緑に表示か透明 幅を厚くしてパックの飛び出し防止
        Matter.Bodies.rectangle(700-72,-79,144,200,{isStatic:true,render: {fillStyle: 'green'}}),//{visible: false}}),
        Matter.Bodies.rectangle(-74,500,200,1000,{isStatic:true,render: {fillStyle: 'green'}}),//{visible: false}}),
        Matter.Bodies.rectangle(774,500,200,1000,{isStatic:true,render: {fillStyle: 'green'}}),//{visible: false}}),
        Matter.Bodies.rectangle(72,1079,144,200,{isStatic:true,render: {fillStyle: 'green'}}),//{visible: false}}),
        Matter.Bodies.rectangle(700-72,1079,144,200,{isStatic:true,render: {fillStyle: 'green'}})//{visible: false}})
        ]);

    //衝突判定
    Matter.Events.on(engine, 'collisionStart', function(event) {
        pairs = event.pairs;
        for (i = 0; i < pairs.length; i++) {
            var pair = pairs[i];
            if (pair.bodyA.label == 'ball' && pair.bodyB.label == 'player1' || pair.bodyB.label == 'ball' && pair.bodyA.label == 'player1') {//player1とパックが衝突すると音が鳴る
                hit.play();}
            if (pair.bodyA.label == 'ball' && pair.bodyB.label == 'player2' || pair.bodyB.label == 'ball' && pair.bodyA.label == 'player2') {//player2とパックが衝突すると音が音が鳴りフラグattack2を立てる
                attack2 = true;hit.play();}               
        }
    });

    //フレーム毎に実行
    Matter.Events.on(engine, 'beforeUpdate', function() {
        if (game) {
            if (touchx > 0 && touchx < 700 && touchy > 500 && touchy < 1000) {Matter.Body.setPosition(player1,{x:touchx,y:touchy});}//自機の動ける範囲を設定
            if (eney == 0) {enex = 0};//もし敵position.yが一番上ならば、enex = 0にセットして敵が160 < position.x < 540に収まるように、一番上でなければ画面いっぱい移動
            if (player2.position.x < ball.position.x && player2.position.x < 540) {enex = Math.min(movex,ball.position.x - player2.position.x)};//movexと、敵とパックの横方向の差を比べて小さい方を敵横移動のスピードに設定
            if (player2.position.x > ball.position.x && player2.position.x > 160) {enex = -Math.min(movex,player2.position.x - ball.position.x)};
            if (player2.position.y < 90) {attack = false;eney = 0};//もし敵position.yが一番上ならば、eney = 0にセットして縦移動停止してアタック可能フラグを立てる
            if (attack == true ) {eney = - movey};//アタックを実行したら、敵は上に移動
            if (attack2 == true) {eney = movey;attack2 = false;attack = true};//敵とパックが衝突してattack2フラグが立ったら、敵は下に移動してattackフラグを立てそのあと上に移動
            if (player2.position.y > 400) {attack = true};//もし敵が真ん中まで進んだらattackフラグを立て上に移動
            if (attack == false && player2.position.y < ball.position.y + 70 && Math.abs(ball.velocity.y) < 7 && ball.position.y < 465) {eney = movey};//アタック可能で、敵がパックより上で、パックの縦の速さが遅くてパックがエリアに入っていたら敵は下に移動
            Matter.Body.setVelocity(player2,{x:enex,y:eney});//敵の速度を設定
            if (ball.position.y > 1000) {//パックが画面下にでたら
               level = 0;
               end.play();
               Reset();
               info.innerHTML = 'Sorry next<br>LEVEL ' + (level + 1);
            }
            if (ball.position.y < 0) {//パックが画面上にでたら
               level = level + 1;
               if (level == 10) {level = 0};
               get.play();
               Reset();
               info.innerHTML = 'Good next<br>LEVEL ' + (level + 1);
            }
          }
    });

    function Reset() {//パックが画面の外に出たあとリセット
           movex = STAGE[level][0];
           movey = STAGE[level][1];
           enex = 0;eney = 0;attack = false;
           Matter.Body.setVelocity(ball, {x:0, y:0});
           Matter.Body.setPosition(ball,{x:350,y:500});
           Matter.Body.setVelocity(player1, {x:0, y:0});
           Matter.Body.setPosition(player1,{x:350,y:900});
           Matter.Body.setVelocity(player2, {x:0, y:0});
           Matter.Body.setPosition(player2,{x:350,y:90});
           game = false;
    }

    //物理シュミレーションを実行
    Engine.run(engine);

    //スマホでタッチイベント発生
    window.addEventListener('touchstart',function(e) {
        e.preventDefault();
        touchx = e.changedTouches[0].pageX * divWidth;
        touchy = e.changedTouches[0].pageY * divHeight;
        isTouch = true;//タッチしている
        if (game == false) {//ゲームをスタート
            info.innerHTML = ' ';//文字をクリア
            if (sound == false) {hit.play();get.play();end.play();sound = true;}//スマホではユーザー操作があるまでオーディオが鳴らないので、その対策
            game = true;
        }
    },false)
    window.addEventListener('touchmove',function(e) {
        e.preventDefault();
        touchx = e.changedTouches[0].pageX * divWidth;
        touchy = e.changedTouches[0].pageY * divHeight;
    },false)
    window.addEventListener('touchend',function(e) {
        e.preventDefault();
        isTouch = false;//タッチ終了
    },false)

    //パソコンでマウスイベント発生
    window.addEventListener('mousedown',function(e) {
        e.preventDefault();
        if (game == false) {
            info.innerHTML = ' ';
            game = true;
        }
        touchx = e.clientX * divWidth;//画面タッチx
        touchy = e.clientY * divHeight;//画面タッチy
        isTouch = true;//タッチしている
    },false)
    window.addEventListener('mousemove',function(e) {
        e.preventDefault();
        if (isTouch) {
           touchx = e.clientX * divWidth;
           touchy = e.clientY * divHeight;
        }
    },false)
    window.addEventListener('mouseup',function(e) {
        e.preventDefault();
        isTouch = false;//タッチ終了
    },false)

    //タイトル
    const fontsize = Math.round(window.innerHeight / 4);
    const title = document.createElement( 'div' );
    document.body.appendChild( title );
    const info = document.createElement( 'div' );
    info.style.position = 'absolute';
    info.style.left = '10%'; 
    info.style.top = '5%';
    info.style.width = '80%';
    info.style.color = "blue";
    info.style.fontWeight = "bold";
    info.style.fontSize = String(fontsize) + "%";
    info.innerHTML = '緑のマレットを動かしてパックを相手ゴールに入れてください<br>PC:マウス<br>スマホ:ドラッグ';
    container.appendChild( info );
})();

②エアホッケーゲームの基本部分

新規作成で新しいフォルダを作り、「testgame」など適当に名前を付けて下さい。メモ帳を開き、下記のソースリスト2つをコピーして「index.html」、「main.js」の名前を付けて文字コードUTF8でペーストして、作成したフォルダ「testgame」 に保存して下さい。もう一つ https://brm.io/matter-js/からダウンロードした「matter.min.js」もコピーして保存して下さい。
出来上がったものは下記から実行できます。スマートフォンでも動作確認してみてください。
testgame

ローカル環境でフォルダ内の「index.html」を実行することも出来ます。
試しに、上記リンクからゲーム画面を開き、ゲーム画面 の隅の余白を右クリックして表示されたメニューの「ページのソースを表示」を左クリックしてみてください。下記 「index.html」 と 「main.js」 が確認できると思います。
この エアホッケーゲーム は、分かりやすくするため骨組みだけです。

index.html

<!doctype html>
<html>
<head>
<meta name="apple-mobile-web-app-capable" content="yes">
<script src="matter.min.js"></script>
</head>
<body ontouchmove="event.preventDefault()">
<div id="js-engine"></div>
<script src="main.js"></script>
</body>
</html>

※スマートフォンでのデフォルトアクションを行わない
<body ontouchmove=”event.preventDefault()”>
ゲームは、このIDの中で実行
<div id=”js-engine”>

main.js

(function() {
    var Engine = Matter.Engine,
        World = Matter.World,
        Body = Matter.Body,
        Bodies = Matter.Bodies,
        Constraint = Matter.Constraint,//使用される構成の複合体を作成
        Vertices = Matter.Vertices;

    var container = document.getElementById('js-engine');//ID'js-engine'にマッチするドキュメント要素をcontainerとする
    var S_WIDTH = window.innerWidth;//フラウザのコンテンツ領域の幅
    var S_HEIGHT = window.innerHeight;//フラウザのコンテンツ領域の高さ
    if (S_WIDTH > S_HEIGHT) {//コンテンツの幅がコンテンツの高さより大きければPC、小さければスマホ
        Scale = 32;//円の直径
        goal = 100//ゴールの幅
        speed = 4;//速度の倍率
        MAXvelocity = 40;//最高速度
        weight = 0.0000001;//密度
    } else {
        Scale = 56;
        goal = 60;
        speed = 4.5;
        MAXvelocity = 55;
        weight = 0.0000001;
    }
    touchx = 0;//画面タッチx
    touchy = 0;//画面タッチy
    isTouch = false;
    disptitle = true;

    var engine = Engine.create(container, {//containerをMatter.jsのゲーム画面にする
        render: {
            options: {
                wireframes: false,
                width: S_WIDTH,
                height: S_HEIGHT,
                background: 'cornsilk'
            }
        }
    });
    //重力値
    engine.world.gravity.y = 0;//重力ナシ

    World.add(engine.world, [
              Bodies.rectangle(S_WIDTH/2,S_HEIGHT/2,S_WIDTH,3,{collisionFilter:true,isStatic:true,render:{fillStyle:'gold'}}),//画面中央の線。衝突判定ナシ
              Bodies.rectangle(S_WIDTH/2,Scale/2,S_WIDTH,Scale,{friction:1,isStatic:true,label:'wall1',render:{fillStyle:'maroon'}}),//画面上の壁
              Bodies.rectangle(Scale/2,S_HEIGHT/2,Scale,S_HEIGHT,{friction:1,isStatic:true,label:'wall2',render:{fillStyle:'maroon'}}),//画面左側の壁
              Bodies.rectangle(S_WIDTH-Scale/2,S_HEIGHT/2,Scale,S_HEIGHT,{friction:1,isStatic:true,label:'wall3',render:{fillStyle:'maroon'}}),//画面右側の壁
              Bodies.rectangle(S_WIDTH/4-goal,S_HEIGHT-Scale/2,S_WIDTH/2-goal*2,Scale,{friction:1,isStatic:true,label:'wall4',render:{fillStyle:'maroon'}}),//画面下左の壁
              Bodies.rectangle(S_WIDTH/4*3+goal,S_HEIGHT-Scale/2,S_WIDTH/2-goal*2,Scale,{friction:1,isStatic:true,label:'wall5',render:{fillStyle:'maroon'}}),//画面下右の壁
    ]);
    ballA = Bodies.circle(S_WIDTH/2,S_HEIGHT*5/6, Scale*1.2, {//自機マレット
            friction: 1,//摩擦係数
            restitution: 1,//反発係数
            collisionFilter: { group: -1 },
            label: 'ballA',//名前
            render:{fillStyle:'red'}
            });

    //回転マルチボディ拘束追加
    var body = Bodies.circle(S_WIDTH/2,S_HEIGHT*5/6, Scale*1.2, { collisionFilter: { group: -1 } });
    var constraint = Constraint.create({
        bodyA: body,
        bodyB: ballA
    });

    ballB = Bodies.circle(S_WIDTH/2, S_HEIGHT/2, Scale, {//パック
            density: weight,
            frictionAir: 0,
            friction: 1,
            restitution: 0.9,
            label: 'ballB',
            render:{fillStyle:'green'}
            });

    World.add(engine.world, [body, ballA, constraint, ballB]);

    //フレーム毎に実行
    Matter.Events.on(engine, 'beforeUpdate', function() {
        if (touchx > Scale && touchx < S_WIDTH - Scale && touchy > Scale && touchy < S_HEIGHT - Scale) {
           Matter.Body.setPosition(ballA,{x:touchx,y:touchy});//自機マレットを移動
        }
	// 速度チェック
	Matter.Body.setVelocity(ballB, {//フレーム毎にパックのx速度とy速度を調べて、最高速度以上なら最高速度にする
	         x: Math.max(Math.min(ballB.velocity.x, MAXvelocity), -MAXvelocity),
                 y: Math.max(Math.min(ballB.velocity.y, MAXvelocity), -MAXvelocity)
	});
        if (ballB.position.y > S_HEIGHT) {//パックが画面下に出たら終了
           info.innerHTML = 'START';
           disptitle = true;
           Matter.Body.setPosition(ballB,{x:S_WIDTH / 2,y:S_HEIGHT / 2});
           Matter.Body.setVelocity(ballB, {x:0, y:0});
        } 
    });

    //物理シュミレーションを実行
    Engine.run(engine);

    //ページ全体が読み込まれたときに一度発生
    window.addEventListener("load", function(){
             var target = document.getElementById("js-engine");//ID'js-engine'にマッチするドキュメント要素をtargetとして、cssをフラウザの幅と高さにする
             target.css({
             position: "absolute",
             height: window.innerHeight,
             width: window.innerWidth
             });
    }, false)

    //スマホでタッチイベント発生
    window.addEventListener('touchstart',function(e) {
        e.preventDefault();//デフォルトアクションを行わない
        touchx = e.changedTouches[0].pageX;//画面タッチx
        touchy = e.changedTouches[0].pageY - Scale * 1.2;//画面タッチyのマレット分上
        isTouch = true;//タッチしている
        if (disptitle) {info.innerHTML = ' ';disptitle = false;}
    },false)
    window.addEventListener('touchmove',function(e) {
        e.preventDefault();
        touchx = e.changedTouches[0].pageX;
        touchy = e.changedTouches[0].pageY - Scale * 2;
    },false)
    window.addEventListener('touchend',function(e) {
        e.preventDefault();
        isTouch = false;//タッチ終了
    },false)

    //パソコンでマウスイベント発生
    window.addEventListener('mousedown',function(e) {
        e.preventDefault();//デフォルトアクションを行わない
        touchx = e.clientX;//画面タッチx
        touchy = e.clientY;//画面タッチy
        isTouch = true;//タッチしている
        if (disptitle) {info.innerHTML = ' ';disptitle = false;}
    },false)
    window.addEventListener('mousemove',function(e) {
        e.preventDefault();
        if (isTouch) {
           touchx = e.clientX;touchy = e.clientY;
        }
    },false)
    window.addEventListener('mouseup',function(e) {
        e.preventDefault();
        isTouch = false;//タッチ終了
    },false)

    //文字列を表示する
    container = document.createElement( 'div' );
    document.body.appendChild( container );
    var info = document.createElement( 'div' );
    info.style.position = 'absolute';
    info.style.left = '50%';
    info.style.bottom = '50%';
    info.style.width = '50%';
    info.style.color = "blue";
    info.style.fontWeight = "bold";
    info.style.fontSize = "300%";
    info.innerHTML = 'START';
    container.appendChild( info );
})();

※Bodyに動きを設定すれば、かってに動きます。そこに、衝突した時のアクション、毎フレームごとに実行する処理、タッチイベントやマウスイベントの処理を付ければ出来上がりです。
matter.jsでは、物体が早すぎると壁をすり抜けてしまいます。ネット検索で見つけた、それを防ぐ方法です。
Matter.Body.setVelocity(ball, {//フレーム毎にx速度とy速度を調べて、最高速度以上なら最高速度にする
x: Math.max(Math.min(ball.velocity.x, MAXvelocity), -MAXvelocity),
y: Math.max(Math.min(ball.velocity.y, MAXvelocity), -MAXvelocity)
}) ;
※ゲーム画面をフラウザのコンテンツ領域いっぱいにする方法。
var target = document.getElementById(“js-engine”);//ID’js-engine’にマッチするドキュメント要素をtargetとして、cssをフラウザの幅と高さにする
target.css({
position: “absolute”,
height: window.innerHeight,
width: window.innerWidth
});
※ゲーム画面に、位置を指定して、文字列を表示する 方法。
container = document.createElement( ‘div’ );
document.body.appendChild( container );
var info = document.createElement( ‘div’ );
info.style.position = ‘absolute’;
info.style.left = ‘50%’;
info.style.bottom = ‘50%’;
info.style.width = ‘50%’;
info.style.color = “blue”;
info.style.fontWeight = “bold”;
info.style.fontSize = “300%”;
info.innerHTML = ‘START’;
container.appendChild( info );

③RCカーゲーム
このホームページのRCカーグランプリに出来上がったゲームを載せました。物理演算はmatter.jsで、画面表示はthree.jsでおこなっています。ここにはそのゲームの骨組み部分だけを抜き出して載せます。これだけで動きます。

まず「matter.min.js」に続いて、「three.min.js」を用意します。
https://threejs.org/のメニュー「download]リンクからライブラリをダウンロードします。ダウンロードしたthree.js-master.zipを展開して、その中のフォルダ「build」のthree.min.jsをコピーします。

新規作成で新しいフォルダを作り、「testgame」など適当に名前を付けて下さい。メモ帳を開き、下記の3つをコピーして、「index.html」、「index.css」、「main.js」の名前で、文字コードUTF8で作成したフォルダ「testgame」 に保存して下さい。 「matter.min.js」、「three.min.jsもコピーして保存して下さい。このフォルダだけでゲームが完結出来ます。
出来上がったものは下記から実行できます。スマートフォンでも動作確認してください。
testgame3D

index.html

<!doctype html>
<html>
<head>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="index.css">
</head>
<body ontouchmove="event.preventDefault()">
<script src="matter.min.js"></script>
<script src="three.min.js"></script>
<div id="js-engine" class="engine"></div>
<script src="main.js"></script>
</body>
</html>

index.css

.engine {
  position: absolute;
  left: 0;
  right: 0;
  //visibility:hidden;
  width: 180px;
  height: 180px; }
  .engine canvas {
    width: 100%;
    height: 100%; }

main.js

(function() {
var MAP = [//コースの2次元配列
    [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [ 1, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 1],
    [ 1, 0, 3, 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, 5, 0, 0, 1],
    [ 1, 3, 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, 5, 0, 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, 5, 1],
    [ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 0, 0, 0, 0, 0, 1],
    [ 1, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 6, 0, 0, 0, 0, 1],
    [ 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 3, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 6, 0, 0, 0, 1],
    [ 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 0, 4, 2, 6, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 2, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 4, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 4, 2, 2, 6, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 0, 0, 0, 4, 2, 2, 2, 3, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 0, 0, 0, 2, 2, 3, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 2, 0, 0, 0, 5, 2, 2, 3, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 4, 2, 2, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1],
    [ 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 4, 0, 1],
    [ 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 4, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 1],
    [ 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 1],
    [ 1, 0, 0, 0, 5, 3, 0, 0, 0, 0, 0, 5, 2, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 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, 5, 1],
    [ 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 2, 2, 2, 2, 6, 0, 0, 0, 0, 1],
    [ 1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 6, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    ];

    var center_x = window.innerWidth / 2;//画面の縦方向の真ん中

    //three.js用3D外壁
    var geobrock1 = new THREE.CubeGeometry(26, 5, 26);
    //loader = new THREE.TextureLoader();texture = loader.load("wall01.jpg");画像を用意すれば、それを表示する
    var matebrock1 = new THREE.MeshBasicMaterial({color: "sienna"});//MeshBasicMaterial({map: texture});
    //three.js用3D植え込み
    var matebrock2 = new THREE.MeshBasicMaterial({color: "green"});
    //three.js用3Dコーナー
    var geobrock3 = new THREE.CubeGeometry(37, 4,24);
    var matebrock3 = new THREE.MeshBasicMaterial({color: "white"});//MeshBasicMaterial({map: texture});
    //three.js自機
    geometry = new THREE.CylinderGeometry(7,10, 8, 4);
    var side_material = new THREE.MeshBasicMaterial({color: "red"});
    var end_material = new THREE.MeshBasicMaterial({color: "crimson"});
    material = new THREE.MeshFaceMaterial([side_material, end_material]);
    var machine = new THREE.Mesh(geometry, material);
    machine.position.y = 4;
    //three.js用3D地面
    geometry = new THREE.PlaneGeometry(60 * 26,60 * 26);
    //loader = new THREE.TextureLoader();texture = loader.load("floor.jpg");画像を用意すれば、それを表示する
    material = new THREE.MeshBasicMaterial({color: "gray",side: THREE.BackSide});//裏返しに張り付けなければ、透明になってしまう
    var plane = new THREE.Mesh(geometry, material);

    //three.jsシーン
    var scene = new THREE.Scene();

    //matter.js物理演算エンジン
    var Engine = Matter.Engine,
        Render = Matter.Render,
        World = Matter.World,
        Bodies = Matter.Bodies;
    //matter.js物理演算ワールドスペース
    var container = document.getElementById('js-engine');//cssでvisibility:hiddenにすれば非表示
    var engine = Engine.create(container, {
        render: {options: {wireframes: true,width: 832,height: 832}
        }
    });

    //重力ゼロにして、落下防止する
    engine.world.gravity.y = 0;

    //衝突チェック(自機が壁にぶつかればアクセルをゼロ)
    Matter.Events.on(engine, 'collisionStart', function(event) {
        pairs = event.pairs;
        for (i = 0; i < pairs.length; i++) {
            var pair = pairs[i];
            if (pair.bodyA.label == 'player' && pair.bodyB.label == 'block') {
                accel = 0;
            }
            if (pair.bodyA.label == 'block' && pair.bodyB.label == 'player') {
                accel = 0;
            }
        }
    });

    //キー入力
    document.onkeydown = function(e) {
        e.preventDefault();//規定の動作をキャンセル
        if (e.keyCode == 16) {down = true;}
        if (e.keyCode == 39) {right = true;}
        if (e.keyCode == 37) {left = true;}
    };
    document.onkeyup = function(e) {
        e.preventDefault();//規定の動作をキャンセル
        if (e.keyCode == 16) {down = false;}
        if (!game) {game = true;info.innerHTML = ' ';}//ゲームスタートと文字を消す
        if (e.keyCode == 39) {right = false;}
        if (e.keyCode == 37) {left = false;}
    };

    //フレーム毎に繰り返し実行
    Matter.Events.on(engine, 'beforeUpdate', function() {
        if (game) {//ゲームの開始フラグ
            if (right) {Matter.Body.setAngularVelocity(player, Math.PI / 125);//右に曲がる
            } else if  (left) {Matter.Body.setAngularVelocity(player, -Math.PI / 125);}//左に曲がる
            if (down) {accel = accel * 0.96;}//時間と共に掛け算でブレーキ
            Matter.Body.applyForce(player, {x: player.position.x,y: player.position.y}, {x: Math.cos(player.angle) * accel,y: Math.sin(player.angle) * accel});//特定のの位置からボディにフォースを与える。
            if (accel < 0.00023) {accel = accel + 0.0000027};//時間と共に足し算で加速する
            machine.position.z = player.position.y;//matter.jsのplayerの位置を、three.jsのmachineに変換する
            machine.position.x = player.position.x;
            machine.rotation.y = - player.angle - Math.PI / 4;//matter.jsのplayerは、three.jsのmachineでは、上が底になる
            oldcamex5 = oldcamex4;//視覚表現のため、カメラの位置を5つ前の場所に
            oldcamez5 = oldcamez4;
            oldcamex4 = oldcamex3;
            oldcamez4 = oldcamez3;
            oldcamex3 = oldcamex2;
            oldcamez3 = oldcamez2;
            oldcamex2 = oldcamex;
            oldcamez2 = oldcamez;
            oldcamex = machine.position.x - Math.cos(player.angle) * 100;//カメラをthree.jsのmachineの後ろ100の位置にセットする
            oldcamez = machine.position.z - Math.sin(player.angle) * 100;
        }
        camera.position.x = oldcamex5;//カメラの位置を5つ前の場所にセットする
        camera.position.z = oldcamez5;
        camera.lookAt(machine.position);//カメラをthree.jsのmachineに向ける
        renderer.render(scene, camera);
    });

    //最初にここから実行する
    plane.position.set(30 * 26, 0,30 * 26);//three.js用3D地面をセットする
    plane.rotation.x = Math.atan2(1,0);
    scene.add(plane);
    player = Bodies.rectangle( 0, 0,13,13,{density: 0.00058,friction: 0,frictionAir: 0.13,restitution: 0.3,label: 'player'});
    World.add(engine.world, [player]);//matter.js物理演算ワールドにplayerをセットする
    scene.add(machine);//three.jsのシーンにmachineを表示
    Initialset();//ファンクションInitialset()を実行
    refresh();//ファンクションrefresh()を実行、あとはフレーム毎実行が繰り返される

    //matter.js物理演算ワールドでマップ配列をセットして、同時にthree.jsでシーンに表示する
    function Initialset() {
        for (i = 0, max = MAP.length; i < max; i++) {
            for (j = 0, max2 = MAP[i].length; j < max2; j++) {
                //matter.js用の外壁
                if (MAP[j][i] == 1) {
                    brock1 = Bodies.rectangle(i * 26 + 13, j * 26 + 13, 26, 26, {label: 'block',isStatic: true});
                    World.add(engine.world, [brock1]);
                    //three.js用の3D外壁
                    thbrock1 = new THREE.Mesh(geobrock1, matebrock1);
                    scene.add(thbrock1);
                    thbrock1.position.z = brock1.position.y;
                    thbrock1.position.x = brock1.position.x;
                    thbrock1.position.y = 2;
                }
                //matter.js用の植え込み
                if (MAP[j][i] == 2) {
                    brock2 = Bodies.rectangle(i * 26 + 13, j * 26 + 13, 26, 26, {label: 'block',isStatic: true});
                    World.add(engine.world, [brock2]);
                    //three.js用の3D植え込み
                    thbrock2 = new THREE.Mesh(geobrock1, matebrock2);
                    scene.add(thbrock2);
                    thbrock2.position.z = brock2.position.y;
                    thbrock2.position.x = brock2.position.x;
                    thbrock2.position.y = 2;
                }
                //matter.js用のコーナー1
                if (MAP[j][i] == 3) {
                    brock3 = Bodies.rectangle(i * 26 + 4.3, j * 26 + 4.3, 37, 24, {label: 'block',isStatic: true});
                    World.add(engine.world, [brock3]);
                    Matter.Body.rotate(brock3,Math.PI*3/4);
                    //three.js用の3Dコーナー1
                    thbrock3 = new THREE.Mesh(geobrock3, matebrock3);
                    scene.add(thbrock3);
                    thbrock3.position.z = brock3.position.y;
                    thbrock3.position.x = brock3.position.x;
                    thbrock3.position.y = 2;
                    thbrock3.rotation.y = - brock3.angle + Math.PI;
                }
                //matter.js用のコーナー2
                if (MAP[j][i] == 4) {
                    brock4 = Bodies.rectangle(i * 26 + 21.3, j * 26 + 21.3, 36, 24, {label: 'block',isStatic: true});
                    World.add(engine.world, [brock4]);
                    Matter.Body.rotate(brock4,Math.PI*3/4);
                    //three.js用の3Dコーナー2
                    thbrock4 = new THREE.Mesh(geobrock3, matebrock3);
                    scene.add(thbrock4);
                    thbrock4.position.z = brock4.position.y;
                    thbrock4.position.x = brock4.position.x;
                    thbrock4.position.y = 2;
                    thbrock4.rotation.y = - brock4.angle;
                }
                //matter.js用のコーナー3
                if (MAP[j][i] == 5) {
                    brock5 = Bodies.rectangle(i * 26 + 21.3, j * 26 + 4.3, 36, 24, {label: 'block',isStatic: true});
                    World.add(engine.world, [brock5]);
                    Matter.Body.rotate(brock5,Math.PI*1/4);
                    //three.js用の3Dコーナー3
                    thbrock5 = new THREE.Mesh(geobrock3, matebrock3);
                    scene.add(thbrock5);
                    thbrock5.position.z = brock5.position.y;
                    thbrock5.position.x = brock5.position.x;
                    thbrock5.position.y = 2;
                    thbrock5.rotation.y = - brock5.angle;
                }
                //matter.js用のコーナー4
                if (MAP[j][i] == 6) {
                    brock6 = Bodies.rectangle(i * 26 + 4.7, j * 26 + 21.3, 36, 24, {label: 'block',isStatic: true});
                    World.add(engine.world, [brock6]);
                    Matter.Body.rotate(brock6,Math.PI*1/4);
                    //three.js用の3Dコーナー4
                    thbrock6 = new THREE.Mesh(geobrock3, matebrock3);
                    scene.add(thbrock6);
                    thbrock6.position.z = brock6.position.y;
                    thbrock6.position.x = brock6.position.x;
                    thbrock6.position.y = 2;
                    thbrock6.rotation.y = - brock6.angle + Math.PI;
                }
            }
        }
    }

    //リフレッシュ
    function refresh() {//ゲームクリア時に再度読み込み用
       accel = 0;//アクセル
       game = false;//ゲーム開始
       right = false;//右折
       left = false;//左折
       down = false;//ブレーキ
       Matter.Body.setPosition(player,{x:26 * 4,y:26 * 4});//matter.jsのplayerポジション
       Matter.Body.setAngle(player, 0);
       Matter.Body.setVelocity(player, {x:0, y:0});
       Matter.Body.setAngularVelocity(player, 0);
       machine.position.z = player.position.y;//matter.jsのplayerポジションを、three.jsのmachineに変換する
       machine.position.x = player.position.x;
       machine.rotation.y = - player.angle - Math.PI / 4;//THREE.CylinderGeometry(上半径,底半径,高さ,分割数4)を45度回して配置
       oldcamex = player.position.x - Math.cos(player.angle) * 100;////カメラをmatter.jsのplayerの後ろ100の位置にセットする
       oldcamez = player.position.y - Math.sin(player.angle) * 100;
       oldcamex2 = oldcamex;
       oldcamez2 = oldcamez;
       oldcamex3 = oldcamex;
       oldcamez3 = oldcamez;
       oldcamex4 = oldcamex;
       oldcamez4 = oldcamez;
       oldcamex5 = oldcamex;
       oldcamez5 = oldcamez;
    }

    //three.jsのlight
    var ambient = new THREE.AmbientLight(0xFFFFF0);//環境光源を生成
    scene.add(ambient);
    //three.jsのcamera
    var camera = new THREE.PerspectiveCamera(45, 1, 1, 2200);//THREE.PerspectiveCamera(視野角45度, アスペクト比, near, far)
    camera.position.y = 30;//カメラの地面からの高さ(固定)
    //three.js用rendering
    var renderer = new THREE.WebGLRenderer();//ウェブの3D表現
    renderer.setClearColor( 0x87CEEB, 1 );
    renderer.setSize(window.innerWidth, window.innerHeight);//ウインドウ全体をthree.jsの大きさにする
    renderer.shadowMapEnabled = false;//影は付けない
    document.body.appendChild(renderer.domElement);

    //run the engine=matter.js物理演算エンジン開始
    Engine.run(engine);

    //スマホでタッチイベント
    window.addEventListener('touchstart',function(e) {//タッチが開始された瞬間に発生
        e.preventDefault();//規定の動作をキャンセル
        var touch = e.changedTouches[0];
        if (touch.pageX > center_x) {right = true;down = true;//画面の右タッチでブレーキを掛けつつ右
        } else {left = true;down = true;}//画面の左タッチでブレーキを掛けつつ左
    },false);
    window.addEventListener('touchend',function(e) {//タッチされた後、画面から離した時に発生
        e.preventDefault();//規定の動作をキャンセル
        if (!game) {game = true;info.innerHTML = ' ';}//ゲームスタートと文字を消す
        var touch = e.changedTouches[0];
        if (touch.pageX > center_x) {right = false;down = false;
        } else {left = false;down = false;}
    },false);

    //three.js表示の上に文字を表示する
    container = document.createElement( 'div' );
    document.body.appendChild( container );
    var info = document.createElement( 'div' );
    info.style.position = 'absolute';
    info.style.left = '10%'; 
    info.style.top = '20%';
    info.style.width = '80%';
    info.style.color = "navy";
    info.style.fontWeight = "bold";
    info.style.fontSize = '5vw';
    info.innerHTML = 'PC:左右カーソルで左右に曲がり、シフトキーでブレーキです。<br>スマホ:画面を左右タップで、ブレーキを掛けながら左右に曲がります。';
    container.appendChild( info );
})();