jsで避けゲー作ってみた！～初心者向け～

### こんにちは
今回は、ちゃんとしたゲームつくっていきます
#### 下ごしらえ
ぱっぱとHTML・CSSは作っちゃいましょう
```index.html
<title>避けゲー</title>
<link rel="icon" href="https://raw.githubusercontent.com/sugoruru/site-material/main/favicon/Pg-v.png">
<link rel="stylesheet" href="main.css">
<body>
<canvas id="canvas" width="640" height="640"></canvas>
</body>
<script src="index.js"></script>
```

```main.css
canvas {
    display:block;
    background-color:#000000;
    margin:auto;
    padding:0;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
  }
  body {
    background-color: #cccccc;
    margin:0;
    padding:0;
  }
```
上がHTML、下がCSSです。
では、JSをつくっていきましょう

#### mainloopとcanvas読み込み
まず、canvasを下のコードを入れて読み込みます
```index.js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
```
そしたらmainloopです
ここでは`setInterval`を使っていきます
```index.js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function mainloop(){

};
const main = setInterval(mainloop,20);
```
ここまでは下ごしらえのようなものです
では、中身をつくっていきます
#### 変数の作成
いろいろな変数をここで作ります
```index.js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const pl = {
    "x":320,
    "y":320,
    "hp":100,
    "img":undefined,
    "score":0,
    "count":0,
    "time":0
}
const enemy = {
    "x":[],
    "y":[],
    "θ":[],
    "count":0
}
const font = {
    "gameOver":undefined
}
function mainloop(){

}
const main = setInterval(mainloop,20);
```
変数の説明

`pl変数`

`pl["x"]、pl["y"]`・・・プレイヤーの座標を入れる変数です
`pl[img]`・・・プレイヤーの画像を入れる変数です
`pl["score"]`・・・プレイヤーのスコア(敵が端に行った回数)を入れる変数です
`pl["count"]`・・・敵の出てくるスピードの調整変数です
`pl["time"]`・・・スコアを求める用の１秒に１増える変数です

`enemy変数`

`enemy["x"]、enemy["y"]`・・・すべての玉の座標を入れる変数です
`enemy["θ"]`・・・すべての玉の動く角度を入れる変数です
`enemy["count"]`・・・pl["time"]の制御用の変数です

`font変数`
`font["gameOver"]`・・・gameOver時の画像を入れる変数です

この３つの変数を使います

#### 画像の読み込み

ここでは、２つの画像を読み込みます
```index.js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const pl = {
    "x":320,
    "y":320,
    "img":undefined,
    "score":0,
    "count":0,
    "time":0
}
const enemy = {
    "x":[],
    "y":[],
    "θ":[],
    "count":0
}
const font = {
    "gameOver":undefined
}
pl["img"]=new Image();
pl["img"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/game/heart.png";
font["gameOver"]=new Image();
font["gameOver"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/font/game%20over.png";
function mainloop(){

}
const main = setInterval(mainloop,20);
```
`pl["img"]`には♡型の画像が入っていてこれがプレイヤーです
そして`font["gameOver"]`にはゲームオーバーの文字の画像が入っています
この２つの画像を読み込みました

#### canvasのclearと四角の枠
装飾用としての枠を関数化します
```index.js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const pl = {
    "x":320,
    "y":320,
    "img":undefined,
    "score":0,
    "count":0,
    "time":0
}
const enemy = {
    "x":[],
    "y":[],
    "θ":[],
    "count":0
}
const font = {
    "gameOver":undefined
}
pl["img"]=new Image();
pl["img"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/game/heart.png";
font["gameOver"]=new Image();
font["gameOver"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/font/game%20over.png";
function mainloop(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    style();
}
const main = setInterval(mainloop,20);
function style(){
    ctx.beginPath();
    ctx.rect(0,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(620,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,0,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,620,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
}
```
そしてこれをメインループに入れました

#### countアップと画像の表示
次にcount変数のアップと画像を表示していきます
```index.js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const pl = {
    "x":320,
    "y":320,
    "img":undefined,
    "score":0,
    "count":0,
    "time":0
}
const enemy = {
    "x":[],
    "y":[],
    "θ":[],
    "count":0
}
const font = {
    "gameOver":undefined
}
pl["img"]=new Image();
pl["img"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/game/heart.png";
font["gameOver"]=new Image();
font["gameOver"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/font/game%20over.png";
function mainloop(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    style();
    pl["count"]++;
    enemy["count"]++;
    img_show();
}
const main = setInterval(mainloop,20);
function img_show(){
    ctx.drawImage(pl["img"],0,0,16,16,pl["x"],pl["y"],32,32);
}
function style(){
    ctx.beginPath();
    ctx.rect(0,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(620,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,0,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,620,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
}
```
これで、プレイヤーが表示されたと思います

#### プレイヤーのキーボード操作
```index.js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const pl = {
    "x":320,
    "y":320,
    "img":undefined,
    "score":0,
    "count":0,
    "time":0
}
const enemy = {
    "x":[],
    "y":[],
    "θ":[],
    "count":0
}
const font = {
    "gameOver":undefined
}
pl["img"]=new Image();
pl["img"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/game/heart.png";
font["gameOver"]=new Image();
font["gameOver"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/font/game%20over.png";
function mainloop(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    style();
    pl["count"]++;
    enemy["count"]++;
    img_show();
}
window.onkeydown=(function(e){
    if(e.key==="ArrowRight" && pl["x"]<590)pl["x"] += 3;
    if(e.key==="ArrowLeft" && pl["x"]>20)pl["x"] -= 3;
    if(e.key==="ArrowUp" && pl["y"]>20)pl["y"] -= 3;
    if(e.key==="ArrowDown" && pl["y"]<590)pl["y"] += 3;
});
const main = setInterval(mainloop,20);
function img_show(){
    ctx.drawImage(pl["img"],0,0,16,16,pl["x"],pl["y"],32,32);
}
function style(){
    ctx.beginPath();
    ctx.rect(0,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(620,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,0,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,620,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
}
```
キーボード操作を追加しました
if文の条件はキーが押されたかつ枠からはみ出さないというものです

#### ランダム関数と敵の作成
```index.js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const pl = {
    "x":320,
    "y":320,
    "img":undefined,
    "score":0,
    "count":0,
    "time":0
}
const enemy = {
    "x":[],
    "y":[],
    "θ":[],
    "count":0
}
const font = {
    "gameOver":undefined
}
pl["img"]=new Image();
pl["img"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/game/heart.png";
font["gameOver"]=new Image();
font["gameOver"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/font/game%20over.png";
function mainloop(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    style();
    pl["count"]++;
    enemy["count"]++;
    img_show();
    enemy_edit();
}
window.onkeydown=(function(e){
    if(e.key==="ArrowRight" && pl["x"]<590)pl["x"] += 3;
    if(e.key==="ArrowLeft" && pl["x"]>20)pl["x"] -= 3;
    if(e.key==="ArrowUp" && pl["y"]>20)pl["y"] -= 3;
    if(e.key==="ArrowDown" && pl["y"]<590)pl["y"] += 3;
});
const main = setInterval(mainloop,20);
function img_show(){
    ctx.drawImage(pl["img"],0,0,16,16,pl["x"],pl["y"],32,32);
}
function style(){
    ctx.beginPath();
    ctx.rect(0,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(620,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,0,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,620,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
}
function random(min,max){
    return Math.floor( Math.random() * (max-min+1) ) + min;
}
function enemy_edit(){
    if(pl["count"]==1){
        let a = random(0,3);
        let b = random(20,620);
        let c = random(0,180);
        if(a===0){
            enemy["x"].push(b);
            enemy["y"].push(40);
            enemy["θ"].push(c);
        }
        if(a===1){
            enemy["x"].push(40);
            enemy["y"].push(b);
            enemy["θ"].push(c);
        }
        if(a===2){
            enemy["x"].push(600);
            enemy["y"].push(b);
            enemy["θ"].push(c);
        }aa
        if(a===3){
            enemy["x"].push(b);
            enemy["y"].push(600);
            enemy["θ"].push(c);
        }
        pl["count"]=0;
    }
}
```
ランダム関数と敵の追加を実装しました
`ランダム関数について`・・・
```
random(最小値,最大値)
```
と書くだけで乱数が生成される優れもの
`敵の作成について`・・・
変数a,b,cというものがあります
変数aとは・・・右下・右上・左下・左上のどこから生成するか(0~4)
変数bとは・・・どの位置から開始するか
変数cとは・・・どの角度で行くか
この3つの変数をenemy["x"]・enemy["y"]・enemy["θ"]に保存します

### 敵の表示

```index.js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const pl = {
    "x":320,
    "y":320,
    "img":undefined,
    "score":0,
    "count":0,
    "time":0
}
const enemy = {
    "x":[],
    "y":[],
    "θ":[],
    "count":0
}
const font = {
    "gameOver":undefined
}
pl["img"]=new Image();
pl["img"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/game/heart.png";
font["gameOver"]=new Image();
font["gameOver"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/font/game%20over.png";
function mainloop(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    style();
    pl["count"]++;
    enemy["count"]++;
    img_show();
    enemy_edit();
    enemy_show();
}
window.onkeydown=(function(e){
    if(e.key==="ArrowRight" && pl["x"]<590)pl["x"] += 3;
    if(e.key==="ArrowLeft" && pl["x"]>20)pl["x"] -= 3;
    if(e.key==="ArrowUp" && pl["y"]>20)pl["y"] -= 3;
    if(e.key==="ArrowDown" && pl["y"]<590)pl["y"] += 3;
});
const main = setInterval(mainloop,20);
function img_show(){
    ctx.drawImage(pl["img"],0,0,16,16,pl["x"],pl["y"],32,32);
}
function style(){
    ctx.beginPath();
    ctx.rect(0,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(620,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,0,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,620,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
}
function random(min,max){
    return Math.floor( Math.random() * (max-min+1) ) + min;
}
function enemy_edit(){
    if(pl["count"]==1){
        let a = random(0,3);
        let b = random(20,620);
        let c = random(0,180);
        if(a===0){
            enemy["x"].push(b);
            enemy["y"].push(40);
            enemy["θ"].push(c);
        }
        if(a===1){
            enemy["x"].push(40);
            enemy["y"].push(b);
            enemy["θ"].push(c);
        }
        if(a===2){
            enemy["x"].push(600);
            enemy["y"].push(b);
            enemy["θ"].push(c);
        }aa
        if(a===3){
            enemy["x"].push(b);
            enemy["y"].push(600);
            enemy["θ"].push(c);
        }
        pl["count"]=0;
    }
function enemy_show(){
    for(let i = 0;i<enemy["x"].length;i++){
        ctx.beginPath();
        ctx.arc(enemy["x"][i],enemy["y"][i],10,0*Math.PI/180,360*Math.PI/180,false);
        ctx.fillStyle = "#4169e1";
        ctx.fill();
    }
}
}
```
これの仕組みは、for文を使って敵のデータをすべて読み込んで
それを青色の玉で表示という感じです

#### 敵の動きと死亡時
敵の動きは、初めに角度を決めてその分動かすというものです
角度の動かし方は数学になりますが、
動く歩数はこうあらわされます
横移動が
$$x\cos\theta$$
縦移動が
$$x\sin\theta$$
と表せます
これは動く長さなので、元の位置を足してこうなりました
```index.js
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const pl = {
    "x":320,
    "y":320,
    "img":undefined,
    "score":0,
    "count":0,
    "time":0
}
const enemy = {
    "x":[],
    "y":[],
    "θ":[],
    "count":0
}
const font = {
    "gameOver":undefined
}
pl["img"]=new Image();
pl["img"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/game/heart.png";
font["gameOver"]=new Image();
font["gameOver"].src="https://raw.githubusercontent.com/sugoruru/site-material/main/font/game%20over.png";
function mainloop(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    style();
    pl["count"]++;
    enemy["count"]++;
    img_show();
    enemy_edit();
    enemy_show();
    enemy_move();
    time();
}
window.onkeydown=(function(e){
    if(e.key==="ArrowRight" && pl["x"]<590)pl["x"] += 3;
    if(e.key==="ArrowLeft" && pl["x"]>20)pl["x"] -= 3;
    if(e.key==="ArrowUp" && pl["y"]>20)pl["y"] -= 3;
    if(e.key==="ArrowDown" && pl["y"]<590)pl["y"] += 3;
});
const main = setInterval(mainloop,20);
function img_show(){
    ctx.drawImage(pl["img"],0,0,16,16,pl["x"],pl["y"],32,32);
}
function style(){
    ctx.beginPath();
    ctx.rect(0,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(620,0,20,640);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,0,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
    ctx.beginPath();
    ctx.rect(0,620,640,20);
    ctx.fillStyle="#fff";
    ctx.fill();
}
function random(min,max){
    return Math.floor( Math.random() * (max-min+1) ) + min;
}
function time(){
    if(enemy["count"]==50){
        pl["time"]++;
        enemy["count"]=0;
    }
}
function enemy_edit(){
    if(pl["count"]==1){
        let a = random(0,3);
        let b = random(20,620);
        let c = random(0,180);
        if(a===0){
            enemy["x"].push(b);
            enemy["y"].push(40);
            enemy["θ"].push(c);
        }
        if(a===1){
            enemy["x"].push(40);
            enemy["y"].push(b);
            enemy["θ"].push(c);
        }
        if(a===2){
            enemy["x"].push(600);
            enemy["y"].push(b);
            enemy["θ"].push(c);
        }
        if(a===3){
            enemy["x"].push(b);
            enemy["y"].push(600);
            enemy["θ"].push(c);
        }
        pl["count"]=0;
    }
}
function enemy_show(){
    for(let i = 0;i<enemy["x"].length;i++){
        ctx.beginPath();
        ctx.arc(enemy["x"][i],enemy["y"][i],10,0*Math.PI/180,360*Math.PI/180,false);
        ctx.fillStyle = "#4169e1";
        ctx.fill();
    }
}
function enemy_move(){
    for(let i = 0;i<enemy["x"].length;i++){
        enemy["x"][i]=enemy["x"][i]+5*Math.cos(enemy["θ"][i]);
        enemy["y"][i]=enemy["y"][i]+5*Math.sin(enemy["θ"][i]);
        if(pl["x"]-16<enemy["x"][i]&&pl["x"]+32>enemy["x"][i]&&pl["y"]-16<enemy["y"][i]&&pl["y"]+32>enemy["y"][i]){
            clearInterval(main);
            ctx.clearRect(0,0,canvas.width,canvas.height);
            ctx.drawImage(font["gameOver"],0,0,430,107,160,200,320,80);
            ctx.fillStyle="#ffffff";
            ctx.font="50px bold monospace";
            ctx.fillText(`score:${pl["score"] * pl["time"]}`,180,350);
            style();
        }else{
            if(enemy["x"][i]<20||enemy["x"][i]>620||enemy["y"][i]<20||enemy["y"][i]>620){
                enemy["x"].splice(i,1);
                enemy["y"].splice(i,1);
                enemy["θ"].splice(i,1);
                pl["score"]++;
            }
        }
    }
}
```
`原理`
まず、for文で敵をすべて読み込みます
そして、さっきのことを書いて、移動させます
そして、もし、`敵にあたっていない`かつ`端についた`ら
スコアを増やし、その玉を削除します
そして、敵に当たったらメインループを止めて
死亡シーンに切り替えるというものです

#### 死亡シーン
死んでしまったら、初めに読み込んだ`gameOver`の画像とスコアを表示させます

#### おしまい
これで、ゲームの完成です。
最後まで読んでくださりありがとうございました。
できないや質問があればコメント待っています。