こんにちは
今回は、ちゃんとしたゲームつくっていきます
下ごしらえ
ぱっぱとHTML・CSSは作っちゃいましょう
<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>
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を下のコードを入れて読み込みます
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
そしたらmainloopです
ここではsetInterval
を使っていきます
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function mainloop(){
};
const main = setInterval(mainloop,20);
ここまでは下ごしらえのようなものです
では、中身をつくっていきます
変数の作成
いろいろな変数をここで作ります
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つの画像を読み込みます
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と四角の枠
装飾用としての枠を関数化します
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変数のアップと画像を表示していきます
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();
}
これで、プレイヤーが表示されたと思います
プレイヤーのキーボード操作
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文の条件はキーが押されたかつ枠からはみ出さないというものです
ランダム関数と敵の作成
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["θ"]に保存します
敵の表示
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$$
と表せます
これは動く長さなので、元の位置を足してこうなりました
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
の画像とスコアを表示させます
おしまい
これで、ゲームの完成です。
最後まで読んでくださりありがとうございました。
できないや質問があればコメント待っています。