今まで授業で習ってきた内容を活用して、JavaScriptで簡単なゲームを作ってみました。
#ゲームについて
###ダウンロード
こちらからダウンロードできます。
https://drive.google.com/file/d/1zp9tvFvftziMUGV-gTdD2AS15D4vkZM9/view?usp=sharing
「Game.zip」を解凍し、その中にある「index.html」を開くことで、ゲームをプレイすることができます。
※「CodePen」を利用してこちらの記事にソースコードを埋め込むこともできたのですが、ゲームが上手く動作しませんでした・・・。
###概要
このゲームは、画面上に一定時間の間隔で移動する1つのボタンを、単純にクリックしていくという、とてもシンプルなゲームです。クリックしていく度に、その間隔は短くなっていきます。FPSゲームのエイム練習にはもってこいのゲームなのではないかと思います。
####起動画面
・「START」ボタンを押すと、ゲームが始まります。
・「-LEVEL-」では、自分で難易度を選択することができます。(Hard:難しい、Normal:普通、Easy:易しい)
####ゲーム画面
・数字が書かれているボタンをクリックしていきます。画像では「01」となっていますが、クリックすると数字が1増えます。「20」をクリックしたら、ゲーム終了となります(ソースコード「main.js」の変数LOOPで簡単に変更可)。
・「0:05」は、タイムを表しています。
・「Give up」ボタンを押すことで、ゲームを強制終了させることができます。
・ボタンの移動する間隔は、難易度によって異なります。仕様は以下の通りです(「01」→「20」まで、一定の間隔で速くなっていきます)。
- Hard:難しい
- 最初(「01」):0.7秒
- 最後(「20」):0.4秒
- Normal:普通
- 最初(「01」):0.85秒
- 最後(「20」):0.6秒
- Easy:易しい
- 最初(「01」):1秒
- 最後(「20」):0.8秒
const LOOP = 20; //数字ボタンの出現回数
let passSec = 0;
let count;
let num;
let cdiv;
let selectLevel;
//背景生成
function chgImg(){
const pdiv = document.getElementById("parent-div");
//canvasの描画
const canvas = document.createElement("canvas");
canvas.width = 460;
canvas.height = 345;
pdiv.appendChild(canvas);
const context = canvas.getContext("2d");
context.fillStyle = "#000000";
context.rect(0,0,canvas.width,canvas.height);
context.fill();
//canvasからimgに変換
const jpg = canvas.toDataURL();
const newImg = document.createElement("img");
newImg.src = jpg;
pdiv.replaceChild(newImg, canvas);
}
//画面内消去
function Clear(){
while(cdiv.firstChild){
cdiv.removeChild(cdiv.firstChild);
}
}
//起動画面
function Start(){
//タイトル
const titleText = document.createElement("h1");
const titleContent = document.createTextNode("Simple Game");
titleText.appendChild(titleContent);
titleText.id = "title";
cdiv.appendChild(titleText);
//スタートボタン
const startButton = document.createElement("button");
const startContent = document.createTextNode("START");
startButton.appendChild(startContent);
startButton.id = "start";
cdiv.appendChild(startButton);
//難易度選択(ラジオボタン)
const levelText = document.createElement("p");
const levelContent = document.createTextNode("-LEVEL-");
levelText.appendChild(levelContent);
levelText.id = "level";
cdiv.appendChild(levelText);
const levelDiv = document.createElement("div");
levelDiv.id = "level-div";
cdiv.appendChild(levelDiv);
//難易度:難しい
const hardDiv = document.createElement("div");
levelDiv.appendChild(hardDiv);
const hardRadio = document.createElement("input");
const hardAtt = {
type: "radio",
name: "select",
value: "hard",
id: "hard"
}
for(let i in hardAtt){
hardRadio[i] = hardAtt[i];
}
hardDiv.appendChild(hardRadio);
const hardLabel = document.createElement("label");
const hardText = document.createTextNode("Hard");
hardLabel.appendChild(hardText);
hardLabel.htmlFor = "hard";
hardDiv.appendChild(hardLabel);
//難易度:普通
const normalDiv = document.createElement("div");
levelDiv.appendChild(normalDiv);
const normalRadio = document.createElement("input");
const normalAtt = {
type: "radio",
name: "select",
value: "normal",
id: "normal"
}
for(let i in normalAtt){
normalRadio[i] = normalAtt[i];
}
normalDiv.appendChild(normalRadio);
const normalLabel = document.createElement("label");
const normalText = document.createTextNode("Normal");
normalLabel.appendChild(normalText);
normalLabel.htmlFor = "normal";
normalDiv.appendChild(normalLabel);
//難易度:易しい
const easyDiv = document.createElement("div");
levelDiv.appendChild(easyDiv);
const easyRadio = document.createElement("input");
const easyAtt = {
type: "radio",
name: "select",
value: "easy",
id: "easy",
checked: "checked"
}
for(let i in easyAtt){
easyRadio[i] = easyAtt[i];
}
easyDiv.appendChild(easyRadio);
const easyLabel = document.createElement("label");
const easyText = document.createTextNode("Easy");
easyLabel.appendChild(easyText);
easyLabel.htmlFor = "easy";
easyDiv.appendChild(easyLabel);
}
//タイマー処理
function showPassage(){
const watch = document.getElementById("wat-style");
passSec++;
let sec = passSec % 60; //秒
let min = parseInt(passSec / 60); //分
if(sec <= 9){
sec = "0" + sec;
}
if(min >= 10){
const watLayout = window.getComputedStyle(watch);
watch.style.left = watLayout.left;
watch.style.left = 395 + "px";
}
count = min + ":" + sec;
watch.innerHTML = count;
}
//数字ボタンの位置決定
function Move(){
let x, y;
do{
x = Math.random(); //imgの左上隅画素の中心を原点(0,0)とした時の、右方向へ正のX座標を決める乱数
y = Math.random(); //imgの左上隅画素の中心を原点(0,0)とした時の、下方向へ正のY座標を決める乱数
}while((x * 429 > 352 && y * 324 > 287) || (x * 429 > 356 && y * 324 < 30)); //終了ボタンやタイマーと重なったらやり直し
const numberBut = document.getElementById("num-but");
const numStyle = window.getComputedStyle(numberBut);
numberBut.style.left = numStyle.left;
numberBut.style.top = numStyle.top;
numberBut.style.left = x * 429 + "px";
numberBut.style.top = y * 324 + "px";
}
//終了画面
function End(issue){
const name = document.createElement("h1");
name.id = "nametag";
const result = document.createElement("h2");
result.id = "resulttag";
//1:ゲームクリア, 2:ゲームオーバー
switch(issue){
case 1: const winContent = document.createTextNode("-CLEAR TIME-");
name.appendChild(winContent);
cdiv.appendChild(name);
const timeResult = document.createTextNode(count);
result.appendChild(timeResult);
cdiv.appendChild(result);
break;
case 2: const loseContent = document.createTextNode("-SCORE-");
name.appendChild(loseContent);
cdiv.appendChild(name);
const scoreResult = document.createTextNode(num);
result.appendChild(scoreResult);
cdiv.appendChild(result);
const max = document.createElement("h3");
max.id = "maxtag";
const maxContent = document.createTextNode("/" + LOOP);
max.appendChild(maxContent);
cdiv.appendChild(max);
}
//リターンボタン(ページリロード)
const returnButton = document.createElement("button");
const returnContent = document.createTextNode("Return");
returnButton.appendChild(returnContent);
returnButton.id = "return";
cdiv.appendChild(returnButton);
returnButton.onclick = function(){
window.location.reload();
}
//設定難易度の表示
const selectText = document.createElement("p");
const myLevel = selectLevel.charAt(0).toUpperCase() + selectLevel.slice(1);
selectText.innerHTML = "LEVEL: <span id=\"select-" + selectLevel + "\">" + myLevel + "</span>"
selectText.id = "select-text";
cdiv.appendChild(selectText);
}
window.onload = function(){
cdiv = document.getElementById("child-div");
chgImg();
Start();
document.getElementById("start").addEventListener("click", function(){
let first, next;
const select = document.getElementsByName("select");
//ラジオボタンで難易度設定
for(let i = 0; i < select.length; i++){
if(select[i].checked){
selectLevel = select[i].value;
break;
}
}
//hard:難しい, normal:普通, easy:易しい
switch(selectLevel){
case "hard": first = 700;
next = 300 / (LOOP - 1);
break;
case "normal": first = 850;
next = 250 / (LOOP - 1);
break;
case "easy": first = 1000;
next = 200 / (LOOP - 1);
}
Clear();
//タイマー生成
const watch = document.createElement("p");
watch.id = "wat-style";
cdiv.appendChild(watch);
watch.innerHTML = "0:00";
passSecID = setInterval("showPassage()", 1000);
//数字ボタンの処理
const numButton = document.createElement("input");
numButton.type = "button";
numButton.value = "01";
num = 1;
numButton.id = "num-but";
cdiv.appendChild(numButton);
Move();
timer = setInterval("Move()", first);
numButton.onclick = function(){
clearInterval(timer);
if(num < 9){
numButton.value = "0" + ++num;
}else{
numButton.value = ++num;
}
Move();
timer = setInterval("Move()", first - next * (num - 1));
if(numButton.value > LOOP){
clearInterval(timer);
clearInterval(passSecID);
Clear();
End(1);
}
}
//終了ボタン
const endButton = document.createElement("button");
const endContent = document.createTextNode("Give up");
endButton.appendChild(endContent);
endButton.id = "give-up";
cdiv.appendChild(endButton);
endButton.onclick = function(){
clearInterval(timer);
clearInterval(passSecID);
Clear();
End(2);
}
});
}
###HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="parent-div">
<div id="child-div"></div>
</div>
<script src="main.js"></script>
</body>
</html>
###CSS
img{
user-select: none;
-webkit-user-drag: none;
-khtml-user-drag: none;
-moz-user-drag: none;
-o-user-drag: none;
}
#parent-div{
position: relative;
width: 460px;
height: 345px;
}
#title{
color: #00bfff;
font-family: sans-serif;
font-size: 45px;
text-align: center;
position: absolute;
top: 70px;
left: 0;
right: 0;
margin: auto;
user-select: none;
}
#start{
position: absolute;
top: 230px;
left: 0;
right: 0;
margin: auto;
}
#give-up{
position: absolute;
top: 315px;
left: 390px;
}
#num-but{
position: absolute;
left: 0;
top: 0;
}
#wat-style{
position: absolute;
left: 405px;
top: 5px;
color: yellow;
margin: 0;
user-select: none;
}
#nametag{
color: red;
font-family: sans-serif;
font-size: 50px;
text-align: center;
position: absolute;
top: 70px;
left: 0;
right: 0;
margin: auto;
user-select: none;
}
#resulttag{
color: yellow;
font-family: sans-serif;
font-size: 40px;
text-align: center;
position: absolute;
top: 180px;
left: 0;
right: 0;
margin: auto;
user-select: none;
}
#maxtag{
color: white;
font-family: sans-serif;
font-size: 25px;
position: absolute;
top: 180px;
left: 280px;
user-select: none;
}
#return{
position: absolute;
top: 315px;
left: 396px;
}
#level{
font-family: sans-serif;
position: absolute;
top: 2px;
left: 5px;
color: white;
margin: 0;
user-select: none;
font-size: 90%;
}
label[for="hard"]{
font-family: sans-serif;
color: #ff6347;
font-size: 85%;
font-weight: bold;
user-select: none;
padding: 0;
}
label[for="normal"]{
font-family: sans-serif;
color: yellow;
font-size: 85%;
font-weight: bold;
user-select: none;
}
label[for="easy"]{
font-family: sans-serif;
color: #1e90ff;
font-size: 85%;
font-weight: bold;
user-select: none;
}
#level-div{
position: absolute;
top: 18px;
left: 0;
line-height: 10px;
}
#select-hard{
color: #ff6347;
font-weight: bold;
}
#select-normal{
color: yellow;
font-weight: bold;
}
#select-easy{
color: #1e90ff;
font-weight: bold;
}
#select-text{
position: absolute;
top: 2px;
left: 5px;
color: white;
margin: 0;
user-select: none;
}
###varとletとconstの使い分けについて
再宣言も再代入もできるのがvar、再宣言はできないが再代入ができるのがlet、再宣言も再代入もできないのがconstですが、再宣言も再代入もできるvarが一番便利だから、「変数は基本varでいいんじゃないの?」と思って、このゲームをプログラミングした当初は基本varを使っていました。
しかし、変数が意図せず上書きされてしまったり、思わぬバグが発生したりするケースがあるようです。
なので変数は基本constで宣言し、for文等で使用する再代入が必要な変数はletで宣言するようにしてみました。
上記のJavaScriptのソースコードから分かるように、varは一度も使用していません。
これからも何かJavaScriptでプログラミングする際には、そのように意識してみたいと思います。
#感想
今回、なるべくHTMLはコーディングせず、基本JavaScriptによるDOM操作でプログラミングしてみました。結果として、DOM操作に慣れることができたので良かったと思いました。ただ、非常にシンプルなゲームであっても、イメージを形にすることに時間がかかり、改めて難しいと感じました。まだまだJavaScriptの知識は足りないので、これからも学習を続けていきたいと思います。
#参考サイト
JavaScript - ページを再読み込みする
文字列を整数に変換!JavaScriptのparseInt( )の使い方【初心者向け】
canvasを画像に変換する
JavaScriptで書く「var,let,const」の違いと使い分け方法
window.getComputedStyle() で要素のスタイルを取得する