思うより簡単にゲームがつくれる事をわかってもらえるよう、プラウザゲームを作ってみます。
簡単に遊べるようにしたいので、物理演算によるシュミレーションゲームを作る事にします。それで、物理演算エンジン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 charset="utf-8"/>
<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自機
geometry = new THREE.CylinderGeometry(7,10, 8, 4);
var side_material = new THREE.MeshPhongMaterial({color: "tomato"});
var end_material = new THREE.MeshPhongMaterial({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.key == 'Shift') {down = true;}
if (e.key == 'ArrowRight') {right = true;}
if (e.key == 'ArrowLeft') {left = true;}
};
document.onkeyup = function(e) {
e.preventDefault();//規定の動作をキャンセル
if (e.key == 'Shift') {down = false;}
if (!game) {game = true;info.innerHTML = ' ';}//ゲームスタートと文字を消す
if (e.key == 'ArrowRight') {right = false;}
if (e.key == 'ArrowLeft') {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外壁
loader = new THREE.TextureLoader();texture = loader.load("wall01.jpg");画像を用意すれば、それを表示する*/
const thbrock1 = new THREE.Mesh(new THREE.BoxBufferGeometry(26, 8, 26), [
new THREE.MeshLambertMaterial({ color: "saddlebrown" }),
new THREE.MeshLambertMaterial({ color: "saddlebrown" }),
new THREE.MeshBasicMaterial({ color: "chocolate" }),,
new THREE.MeshLambertMaterial({ color: "saddlebrown" }),
new THREE.MeshLambertMaterial({ color: "saddlebrown" })
]);
scene.add(thbrock1);
thbrock1.position.z = brock1.position.y;
thbrock1.position.x = brock1.position.x;
thbrock1.position.y = 4;
}
//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植え込み
loader = new THREE.TextureLoader();texture = loader.load("wall02.jpg");画像を用意すれば、それを表示する*/
const thbrock2 = new THREE.Mesh(new THREE.BoxBufferGeometry(26, 8, 26), [
new THREE.MeshLambertMaterial({ color: "darkgreen" }),
new THREE.MeshLambertMaterial({ color: "darkgreen" }),
new THREE.MeshBasicMaterial({ color: "green" }),,
new THREE.MeshLambertMaterial({ color: "darkslategray" }),
new THREE.MeshLambertMaterial({ color: "darkslategray" })
]);
scene.add(thbrock2);
thbrock2.position.z = brock2.position.y;
thbrock2.position.x = brock2.position.x;
thbrock2.position.y = 4;
}
//matter.js用のコーナー1
if (MAP[j][i] == 3) {
brock3 = Bodies.rectangle(i * 26 + 4.3, j * 26 + 4.3, 36, 24, {label: 'block',isStatic: true});
World.add(engine.world, [brock3]);
Matter.Body.rotate(brock3,Math.PI*3/4);
/*three.js用の3Dコーナー1
loader = new THREE.TextureLoader();texture = loader.load("wall02.jpg");画像を用意すれば、それを表示する*/
const thbrock3 = new THREE.Mesh(new THREE.BoxBufferGeometry(36, 7, 24), [
new THREE.MeshLambertMaterial({ color: "orange" }),
new THREE.MeshLambertMaterial({ color: "orange" }),
new THREE.MeshBasicMaterial({ color: "gold" }),,
new THREE.MeshLambertMaterial({ color: "sandybrown" }),
new THREE.MeshLambertMaterial({ color: "sandybrown" })
]);
scene.add(thbrock3);
thbrock3.position.z = brock3.position.y;
thbrock3.position.x = brock3.position.x;
thbrock3.position.y = 3.5;
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
loader = new THREE.TextureLoader();texture = loader.load("wall02.jpg");画像を用意すれば、それを表示する*/
const thbrock4 = new THREE.Mesh(new THREE.BoxBufferGeometry(36, 6, 24), [
new THREE.MeshLambertMaterial({ color: "sandybrown" }),
new THREE.MeshLambertMaterial({ color: "sandybrown" }),
new THREE.MeshBasicMaterial({ color: "gold" }),,
new THREE.MeshLambertMaterial({ color: "orange" }),
new THREE.MeshLambertMaterial({ color: "orange" })
]);
scene.add(thbrock4);
thbrock4.position.z = brock4.position.y;
thbrock4.position.x = brock4.position.x;
thbrock4.position.y = 3;
thbrock4.rotation.y = - brock4.angle + Math.PI;
}
//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
loader = new THREE.TextureLoader();texture = loader.load("wall02.jpg");画像を用意すれば、それを表示する*/
const thbrock5 = new THREE.Mesh(new THREE.BoxBufferGeometry(36, 5, 24), [
new THREE.MeshLambertMaterial({ color: "midnightblue" }),
new THREE.MeshLambertMaterial({ color: "midnightblue" }),
new THREE.MeshBasicMaterial({ color: "mediumblue" }),,
new THREE.MeshLambertMaterial({ color: "darkblue" }),
new THREE.MeshLambertMaterial({ color: "darkblue" })
]);
scene.add(thbrock5);
thbrock5.position.z = brock5.position.y;
thbrock5.position.x = brock5.position.x;
thbrock5.position.y = 2.5;
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
loader = new THREE.TextureLoader();texture = loader.load("wall02.jpg");画像を用意すれば、それを表示する*/
const thbrock6 = new THREE.Mesh(new THREE.BoxBufferGeometry(36, 4, 24), [
new THREE.MeshLambertMaterial({ color: "midnightblue" }),
new THREE.MeshLambertMaterial({ color: "midnightblue" }),
new THREE.MeshBasicMaterial({ color: "mediumblue" }),,
new THREE.MeshLambertMaterial({ color: "darkblue" }),
new THREE.MeshLambertMaterial({ color: "darkblue" })
]);
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);
var dirLight = new THREE.DirectionalLight(0xffffff, 1.0);//light平行光源
dirLight.position.set(32 * 26, 7000, 32 * 26);
scene.add(dirLight);
//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 );
})();
④スペースデブリ
もう一つ、matter.jsのマルチボディ拘束を使ったゲームのソースリストです。
matter.js をダウンロードします。 https://brm.io/matter-js/ で Latest Buildを選ぶとGitHubに行くので、登録してサインインします。そうすると右の方のボタン「Clone or download」が選択可能になるので、「Download ZIP」を選択してZIPファイルをダウンロードします。解凍した「matter-js-master」の中の「matter.min.js」だけ使います。
新規作成で新しいフォルダを作り、「testgame」など名前を付けて下さい。そこに「matter.min.js」をコピーして保存して下さい。メモ帳を開き、下記のソースリスト2つもコピーして「index.html」、「main.js」の名前を付けて文字コードUTF8でペーストして、作成したフォルダに保存して下さい。
「index.html」でサンプルゲームを実行できます。
地球を回る宇宙ごみの話題を見て、宇宙船がぐるぐる回るゲームが出来ないかと試してみました。ゲームは結局アイデアだと思います。
出来上がったものは下記からも実行できます。スマートフォンでも動作します。
mattertest
index.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8"/>
<script src="matter.min.js"></script>
<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()">
<div id="js-engine" class="engine"></div>
<script src="main.js"></script>
</body>
</html>
main.js
(function () {
const Engine = Matter.Engine,//matter.js設定
World = Matter.World,
Constraint = Matter.Constraint;
let container = document.getElementById('js-engine');
let one_third = window.innerWidth / 3;//スマホの縦1/3座標
let accel = 0;//自機加速
let upaccel = 0;//自機上昇下降
let enemy = [];//人工衛星
let enemcon = [];//人工衛星とearthマルチボディ拘束
let eneno = 30;//人工衛星の数
var engine = Engine.create(container, {//matterエンジン700x1000ワイヤーフレームでない
render: {
options: { wireframes: false, width: 700, height: 1000, background: 'black' }//background: 'sky.jpg'
}
});
//重力ゼロ
engine.world.gravity.x = 0;
engine.world.gravity.y = 0;
let 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';
divHeight = 1000 / h;
divWidth = 700 / w;
let player = Matter.Bodies.rectangle(350, 700, 40, 25, { friction: 0, frictionAir: 0.02, label: "player", render: { fillStyle: 'skyblue' } });//render: {sprite: {texture: 'player.png'}}
let earth = Matter.Bodies.circle(350, 460, 90, { friction: 0, frictionAir: 0, label: "earth", render: { fillStyle: 'blue' } });
let gps1 = Matter.Bodies.rectangle(350, 590, 40, 20, { friction: 0, frictionAir: 0, label: "gps", render: { fillStyle: 'maroon' } });
let gps2 = Matter.Bodies.rectangle(350, 330, 40, 20, { friction: 0, frictionAir: 0, label: "gps", render: { fillStyle: 'brown' } });
let gps3 = Matter.Bodies.rectangle(220, 460, 40, 20, { friction: 0, frictionAir: 0, label: "gps", render: { fillStyle: 'darkgreen' } });
Matter.Body.setAngle(gps3, Math.PI / 2);//gps3を90度回転
let gps4 = Matter.Bodies.rectangle(480, 460, 40, 20, { friction: 0, frictionAir: 0, label: "gps", render: { fillStyle: 'green' } });
Matter.Body.setAngle(gps4, Math.PI / 2);//gps4を90度回転
//マルチボディ拘束追加
let constraint0 = Constraint.create({//earthを固定、画像あれば回転させるため
pointA: { x: 350, y: 460 },
bodyB: earth,
render: { visible: false }
});
let constraint1 = Constraint.create({//player一か所固定
pointA: { x: 350, y: 460 },
bodyB: player,
pointB: { x: -20, y: 0 },
stiffness: 0.01,
damping: 0.6,//0.9
render: { visible: false }
});
let constraint2 = Constraint.create({//playerもう一か所固定
pointA: { x: 350, y: 460 },
bodyB: player,
pointB: { x: 20, y: 0 },
stiffness: 0.01,
damping: 0.6,
render: { visible: false }
});
let constraint3 = Constraint.create({//gps1一か所固定
pointA: { x: 350, y: 460 },
bodyB: gps1,
pointB: { x: -10, y: 0 },
stiffness: 0.01,
damping: 0.9,
render: { visible: false }
});
let constraint4 = Constraint.create({//gps1もう一か所固定
pointA: { x: 350, y: 460 },
bodyB: gps1,
pointB: { x: 10, y: 0 },
stiffness: 0.01,
damping: 0.9,
render: { visible: false }
});
let constraint5 = Constraint.create({//gps2一か所固定
pointA: { x: 350, y: 460 },
bodyB: gps2,
pointB: { x: -10, y: 0 },
stiffness: 0.01,
damping: 0.9,
render: { visible: false }
});
let constraint6 = Constraint.create({//gps2もう一か所固定
pointA: { x: 350, y: 460 },
bodyB: gps2,
pointB: { x: 10, y: 0 },
stiffness: 0.01,
damping: 0.9,
render: { visible: false }
});
let constraint7 = Constraint.create({//gps3一か所固定
pointA: { x: 350, y: 460 },
bodyB: gps3,
pointB: { x: 0, y: -10 },
stiffness: 0.01,
damping: 0.9,
render: { visible: false }
});
let constraint8 = Constraint.create({//gps3もう一か所固定
pointA: { x: 350, y: 460 },
bodyB: gps3,
pointB: { x: 0, y: 10 },
stiffness: 0.01,
damping: 0.9,
render: { visible: false }
});
let constraint9 = Constraint.create({//gps4一か所固定
pointA: { x: 350, y: 460 },
bodyB: gps4,
pointB: { x: 0, y: -10 },
stiffness: 0.01,
damping: 0.9,
render: { visible: false }
});
let constraint10 = Constraint.create({//gps4もう一か所固定
pointA: { x: 350, y: 460 },
bodyB: gps4,
pointB: { x: 0, y: 10 },
stiffness: 0.01,
damping: 0.9,
render: { visible: false }
});
World.add(engine.world, [player, earth, gps1, gps2, gps3, gps4, constraint0, constraint1, constraint2, constraint3, constraint4, constraint5, constraint6, constraint7, constraint8, constraint9, constraint10]);
Initialset();
//衝突判定
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 === 'gps' || pair.bodyB.label === 'gps') {
if (pair.bodyA == gps1 || pair.bodyB == gps1) {//gps1が何かに衝突したらマルチボディ拘束を切る
World.remove(engine.world, [constraint3]);
World.remove(engine.world, [constraint4]);
}
if (pair.bodyA == gps2 || pair.bodyB == gps2) {//gps2が何かに衝突したらマルチボディ拘束を切る
World.remove(engine.world, [constraint5]);
World.remove(engine.world, [constraint6]);
}
if (pair.bodyA == gps3 || pair.bodyB == gps3) {//gps3が何かに衝突したらマルチボディ拘束を切る
World.remove(engine.world, [constraint7]);
World.remove(engine.world, [constraint8]);
}
if (pair.bodyA == gps4 || pair.bodyB == gps4) {//gps4が何かに衝突したらマルチボディ拘束を切る
World.remove(engine.world, [constraint9]);
World.remove(engine.world, [constraint10]);
}
}
}
})
//フレーム毎に実行
Matter.Events.on(engine, 'beforeUpdate', function () {
Matter.Body.applyForce(gps1, { x: gps1.position.x, y: gps1.position.y }, { x: Math.cos(gps1.angle) * 0.000057, y: Math.sin(gps1.angle) * 0.000057 });//gps1に前進力を与える
Matter.Body.applyForce(gps2, { x: gps2.position.x, y: gps2.position.y }, { x: -Math.cos(gps2.angle) * 0.000057, y: -Math.sin(gps2.angle) * 0.000057 });//gps2に後進力を与える
Matter.Body.applyForce(gps3, { x: gps3.position.x, y: gps3.position.y }, { x: Math.cos(gps3.angle) * 0.000057, y: Math.sin(gps3.angle) * 0.000057 });//gps3に前進力を与える
Matter.Body.applyForce(gps4, { x: gps4.position.x, y: gps4.position.y }, { x: -Math.cos(gps4.angle) * 0.000057, y: -Math.sin(gps4.angle) * 0.000057 });//gps4に後進力を与える
centang = Math.atan2(player.position.y - 460, player.position.x - 350)//playerとearthの中心との角度
Matter.Body.applyForce(player, { x: player.position.x, y: player.position.y }, { x: Math.cos(player.angle) * (0.0016 + accel) + Math.cos(centang) * upaccel, y: Math.sin(player.angle) * (0.0016 + accel) + Math.sin(centang) * upaccel });
for (let i = 0; i < enemy.length; i++) {//衛星の数だけ繰り返し
if (enemy[i] != undefined) {//削除された衛星でない
let distance = Math.sqrt(Math.pow(enemy[i].position.x - 350, 2) + Math.pow(enemy[i].position.y - 460, 2));//earthの中心から衛星までの距離
if (distance < 140 || distance > 430) {
World.remove(engine.world, [enemy[i], enemcon[i]]);//衛星がearthに近すぎるか遠すぎれば取り除いて削除
delete enemy[i];
eneno--;//衛星の数を一つ減らす
}
}
}
if (eneno == 0) {
Matter.Body.setPosition(gps1,{x:350, y:590});
Matter.Body.setPosition(gps2,{x:350, y:330});
Matter.Body.setPosition(gps3,{x:220, y:460});
Matter.Body.setAngle(gps3, Math.PI / 2);//gps3を90度回転
Matter.Body.setPosition(gps4,{x:480, y:460});
Matter.Body.setAngle(gps4, Math.PI / 2);//gps4を90度回転
World.add(engine.world, [ constraint3, constraint4, constraint5, constraint6, constraint7, constraint8, constraint9, constraint10]);
Initialset()
}
});
//物理シュミレーションを実行
Engine.run(engine);
window.addEventListener('keydown', (e) => {//パソコンキー入力
e.preventDefault();
if (e.key == 'ArrowLeft') { upaccel = - 0.06 }
if (e.key == 'ArrowRight') { upaccel = 0.06 }
if (e.key == 'ArrowUp') { accel = 0.001 }
});
window.addEventListener('keyup', (e) => {
e.preventDefault();
if (e.key == 'ArrowLeft' || e.key == 'ArrowRight') { upaccel = 0 }
if (e.key == 'ArrowUp') { accel = 0 }
});
window.addEventListener('touchstart', (e) => {//スマホ入力
e.preventDefault();
for (let i = 0; i < e.changedTouches.length; i++) {
if (e.changedTouches[i].pageX > one_third && e.changedTouches[i].pageX < one_third * 2) { accel = 0.001 }
if (e.changedTouches[i].pageX > one_third * 2) { upaccel = 0.06 }
if (e.changedTouches[i].pageX < one_third) { upaccel = - 0.06 }
}
}, { passive: false });
window.addEventListener('touchend', (e) => {
e.preventDefault();
for (let i = 0; i < e.changedTouches.length; i++) {
if (e.changedTouches[i].pageX > one_third && e.changedTouches[i].pageX < one_third * 2) { accel = 0 } else { upaccel = 0 }
}
}, { passive: false });
//人工衛星
function Initialset() {
enemy = [];//人工衛星
enemcon = [];//人工衛星とearthマルチボディ拘束
eneno = 30;//人工衛星の数
for (var i = 0; i < 30; i++) {
enemy[i] = Matter.Bodies.circle(350, 612 + Math.floor(Math.random() * (770 - 612)), 12,{restitution: 1,frictionAir: 0,collisionFilter: { group: -1 }});//{ group: -1 }衛星同士がぶつからない
enemy[i].render.fillStyle = 'darkorange'//enemy[i].render.sprite.texture = 'space1.png'
Matter.Body.setAngularVelocity(enemy[i], 0.01);//人工衛星を回転
enemcon[i] = Constraint.create({
pointA: { x: 350, y: 460 },
bodyB: enemy[i],
stiffness: 0.0001,//マルチボディ拘束で弱いバネの様に振動
render: { visible: false }
});
World.add(engine.world, [enemy[i], enemcon[i]]);
Matter.Body.applyForce(enemy[i], { x: enemy[i].position.x, y: enemy[i].position.y }, { x: (Math.random() * (4 + 1 - 1) + 1) * 0.001, y: 0 });//x軸方向にランダムに力を加える
};
}
//タイトル
let fontsize = Math.round(window.innerHeight / 4);
container = document.createElement('div');
document.body.appendChild(container);
const info = document.createElement('div');
info.style.position = 'absolute';
info.style.top = '30%';
info.style.width = '70%';
info.style.color = "yellow";
info.style.fontWeight = "bold";
info.style.fontSize = String(fontsize) + "%";
info.innerHTML = 'PC: [←][↑][→]<br>スマホ:画面タッチ[左][中][右]';
container.appendChild(info);
window.setTimeout(function () { info.innerHTML = ' ' }, 4000);//一定時間で表示を消す
})();