2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

jsで避けゲー作ってみた!~初心者向け~

Last updated at Posted at 2022-03-21

こんにちは

今回は、ちゃんとしたゲームつくっていきます

下ごしらえ

ぱっぱと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"]・・・スコアを求める用の1秒に1増える変数です

enemy変数

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

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

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

画像の読み込み

ここでは、2つの画像を読み込みます

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"]にはゲームオーバーの文字の画像が入っています
この2つの画像を読み込みました

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の画像とスコアを表示させます

おしまい

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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?