【Javascript】簡単なゲームを作る【HTML5】
はじめに
2020年の3月ぐらいからプログラミングをN予備校で勉強し、5月からHTML5ゲームをいくつか作りました。
ミトダッシュ(ランゲーム)
ミトとカエデのおいしい紅茶(アドベンチャーゲーム)
昔はフラッシュで作られた「フラッシュゲーム」が主流で、よく遊んでいました。そのフラッシュも2020年末にサポート終了予定。
次は「HTML5ゲーム」の時代だ!と言われています。(らしい)
HTML5ゲームを作って、最低限のやりたいことはできるようになったので、復習がてら共有したいと思います。
初心者が自分の理解で書いているので説明が不正確かもしれません。HTMLとJSを多少触った事があって、とりあえず動くゲーム作りたい人向けです。
今回は「動いている画像をクリックすると画像が止まるゲーム」を作ります。それってゲーム?と言われそうですが、まあまあ!
HTML編
ほぼ全ての処理はJavascriptで行うので、HTMLにはゲームの土台を作ります。まずHTML文書の基本を作成します。
index.htmlに次のように記述してください。<!--と-->で囲われているのはその行の説明です。実際のファイルには書かなくて良いです。
<!DOCTYPE html> <!--ドキュメントタイプがHTML-->
<html lang="ja"> <!--HTML文書ここから、言語はja(日本語)-->
<head> <!--このHTML文書に関するメタデータここから-->
<meta charset="UTF-8"> <!--文字コードはUTF-8-->
<title>ゲーム</title> <!--ページのタイトルは「ゲーム」-->
</head> <!--このHTML文書に関するメタデータここまで-->
<body> <!--このHTML文書の本体ここから-->
</body> <!--このHTML文書の本体ここまで-->
</html> <!--HTML文書ここまで-->
次は<body>と</body>の間、つまりbodyタグ内に
<canvas width="400" height="400"></canvas>
と一行追加してください。400×400のcanvas要素です。これがゲーム画面のサイズになります。
一度上書き保存し、index.htmlをGoogleChoromeで開いてみます。
画面にはまだ何も見えませんが、F12を押しデベロッパーツールを開いてElementsタブを見てみると……
透明ですが、400×400の何かがあるのが確認できます。ここに画像を表示していきましょう。
</body>と</html>の間にjavascriptの読み込みを書きます。
<script src="game.js"></script>
これでページが読み込まれた時に、一緒にgame.jsも読み込まれ実行されます。
今回、画像を表示したり、ゲームの処理を行うのはすべてjavascriptで行います。最後に、game.jsの方でこのcanvas要素を指定するために、idを設定します。
<canvas width="400" height="400"></canvas>
先ほど書いたこれに「id="game-area"」を追加して
こう書き換えてください
↓
<canvas id="game-area" width="400" height="400"></canvas>
これでHTML文書の準備は終わりました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ゲーム</title>
</head>
<body>
<canvas id="game-area" width="400" height="400"></canvas>
</body>
<script src="game.js"></script>
</html>
index.htmlの中身がこうなっているでしょうか?
次はgame.jsを編集していきましょう。
Javascript編
最初にgame.jsに次のように書いてください。
//から始まる部分はコメントです。どこが何の部分か見やすくするため、実際のファイルにも記述するのをオススメします。必須ではありません。
//準備
const gameArea = document.getElementById('game-area');
const ctx = gameArea.getContext('2d');
上の行は、gameAreaという変数に、idが"game-area"の要素を取得し、これから色々な処理を行う準備をします。
下の行は、gameAreaに'2d'つまり二次元で描写する準備をしています。
画像の表示
では画像を画面に出してみましょう。
ペイントなどを使って、一辺が100ピクセルの正方形を作ります。
それを、index.htmlとgame.jsと同じフォルダに「object.png」と名付けてPNG画像として保存します。
//画像の読み込み
let object = new Image();
object.src = 'object.png'
画像を読み込む時は、Imageクラスを使います。
一行目はobjectという名前の新しいImageクラスを変数に代入し、
二行目でobjectは何というファイル名の画像を使うかソースを指定します。
//画面の表示
function draw() {
ctx.drawImage(object, 0, 0);
window.requestAnimationFrame(draw); //毎秒60回のペースでdrawを実行する
}
draw(); //画面の描写をスタート
次に、先ほど書いた画像の読み込みの下にこれらを記述してください。
上書き保存してindex.htmlを開いてみます、下の画像のようにonject.pngが表示されているでしょうか?
ctx.drawImage( Imageクラスの何か, x座標, y座標);は「Imageクラスの何か」の画像を、(x座標, y座標)の点に左上を揃えて描写します。今は(0,0)つまり原点にそろえています。原点は画面左上、右に行くほどxが増加し、下にいくほどyが増加します。
色々数値を変えて試してみてください。
window.requestAnimationFrame(関数なんとか);は「関数なんとか」を大体1秒に60回のスピードで実行します。そして関数drawの実行内容に、このdraw関数を実行する命令が入っているので、関数drawは毎秒60回の頻度で実行され続けます。
これを利用して60fpsでゲーム画面を描写しています。動きが無いと分からないですが……
draw();は最初の実行を行います。一度スタートさせると実行され続けるので、普通に実行するだけでOKです。
画像を動かす
画像を動かしてみます。
「画像の読み込み」部分と「画面の表示」部分の間に、下の文を記述してください。
//画像を動かす
let gazo = { x: 100, y: 100 };//画像の初期位置
この宣言で二つの変数が作られます。
gazo.x = 100 と gazo.y = 100 の二つです。
先ほど、ctx.drawImage( Imageクラスの何か, x座標, y座標);のx座標とy座標の数値を変えると画像の位置が変化すると書きました。
つまり画像を動かすなら、この2つの数値を変える作業をプログラムで勝手にやってもらえればいいわけです。
その準備として(0,0)に表示していたonject.pngを(gazo.x , gazo.y)に表示する事にします。
やる事は簡単です。
//画面の表示
function draw() {
ctx.drawImage(object, 0, 0);
window.requestAnimationFrame(draw); //毎秒60回のペースでdrawを実行する
}
draw(); //画面の描写をスタート
これを…
//画面の表示
function draw() {
ctx.drawImage(object, gazo.x, gazo.y);
window.requestAnimationFrame(draw); //毎秒60回のペースでdrawを実行する
}
draw(); //画面の描写をスタート
こう書き換えます。
できたら再びindex.htmlを開いて表示を確認してください。
(0,0)だった時より、ちょっと右下(gazo.x, gazo.y)=(100,100)に表示されたでしょうか?
これで、gazo.x と gazo.y の数値を変えれば画像が描写される位置が変わるようになりました。
game.jsは今こうなっているハズです(確認用)
//準備
const gameArea = document.getElementById('game-area');
const ctx = gameArea.getContext('2d');
//画像の読み込み
let object = new Image();
object.src = 'object.png'
//画像を動かす
let gazo = { x: 100, y: 100 };//画像の初期位置
//画面の表示
function draw() {
ctx.drawImage(object, gazo.x, gazo.y);
window.requestAnimationFrame(draw); //毎秒60回のペースでdrawを実行する
}
draw(); //画面の描写をスタート
次はgazo.xとgazo.yの数値を変える方法です。
まずは「毎秒30回、右に5ピクセル移動する」動きをさせてみます。
//画像を動かす
let gazo = { x: 100, y: 100 };//画像の初期位置
function move() {
gazo.x = gazo.x + 5;
}
let moving = setInterval(move, 1000 / 30);//1000/30ミリ秒毎にmoveを実行する
画像を動かす部分の下に追加しました。
関数moveが実行されるたび、「gazo.x = gazo.x + 5」が実行され、gazo.xが5ずつ増えていきます。
最後の行のsetInterval(関数なんか, 間隔);は「関数なんか」を「間隔」ごとに何度も実行するプログラムです。「間隔」は1000=1秒なので「1000/30」は30分の1秒で一回実行、つまり秒間30回実行することになります。
上書き保存してindex.htmlを開いてみましょう。
えっ何………こわ………
動くはずなのににゅーーーんと伸びてしまいました。
これは、前に描写されたものが残っているので伸びて見えています。
付箋を5ピクセルずつずらして上から毎秒30枚貼っているような感じです。
これを解決するために、毎回、以前描写したものを消す処理を追加しましょう。
//画面の表示
function draw() {
ctx.clearRect(0,0,400,400);
ctx.drawImage(object, gazo.x, gazo.y);
window.requestAnimationFrame(draw);//毎秒60回の頻度でdrawを実行する
}
draw();//画面の描写をスタート
3行目が新しく追加されました。
clearRectは名前の通り、(始点x座標, 始点y座標, 終点x座標, 終点y座標)の範囲に描写されたものをクリアします。
今回は(0, 0, 400, 400)の範囲をクリアします。canvasのサイズが400*400なのでcanvasの隅から隅まで全部消す、という処理です。
上書き保存してindex.htmlを確認します。
画像が右に去っていったでしょうか?
描写する座標はcanvas範囲外も設定できます。
どっか行かれてしまうと寂しいので、右から左にループさせましょう。
//画像を動かす
let gazo = { x: 100, y: 100 };//画像の初期位置
function move() {
if (gazo.x >= 400) {//画像が画面から出てしまったらリセット
gazo.x = -100;
} else {//画面から出ていないならx方向に5進む
gazo.x = gazo.x + 5;
}
}
let moving = setInterval(move, 1000 / 30);//1000/30ミリ秒毎にmoveを実行する
色々と追加しました。説明します。
if (gazo.x >= 400){ ~~、これはgazo.xが400以上になった時の条件分岐を書いています。gazo.xが400以上、gazo.xは画像の左上の端のx座標でcanvasの大きさは一辺400なので、つまり画像が右へ消えた時です。
その時gazo.x = -100;、gazo.xを-100にします。つまり左側の見えない場所に画像を一瞬で移動させます。
else、これはifで指定した条件以外の時、つまりgazo.xが400未満、画像がまだ右に消えていない間は、
gazo.x = gazo.x + 5; さっきと同じように右に5ピクセル動かします。
こういう指示になりました。これを毎秒30回チェックして実行してくれるわけです。上書き保存して開いてみましょう。
右に消えて左からやってきたでしょうか?
これで画像が動くようになりました。
この関数moveをうまくやればおおよその動きは出来ると思います。
ここまでのgame.jsの全文です(確認用)
//準備
const gameArea = document.getElementById('game-area');
const ctx = gameArea.getContext('2d');
//画像の読み込み
let object = new Image();
object.src = 'object.png'
//画像を動かす
let gazo = { x: 100, y: 100 };//画像の初期位置
function move() {
if (gazo.x >= 400) {//画像が画面から出てしまったらリセット
gazo.x = -100;
} else {//画面から出ていないならx方向に5進む
gazo.x = gazo.x + 5;
}
}
let moving = setInterval(move, 1000 / 30);//1000/30ミリ秒毎にmoveを実行する
//画面の表示
function draw() {
ctx.clearRect(0, 0, 400, 400);
ctx.drawImage(object, gazo.x, gazo.y);
window.requestAnimationFrame(draw); //毎秒60回のペースでdrawを実行する
}
draw(); //画面の描写をスタート
ゲーム編
画像が動くようになりました。
最後にプレイヤーの操作を受けつけるようにします。
画面の表示の部分の下、一番下の部分に追加します。
//クリックした時の処理
gameArea.addEventListener('click', e => {
//クリックした位置を取得
const rect = gameArea.getBoundingClientRect();
mousepoint = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
}
})
最後の難関です。クリックおよびタップで反応するようにします。
2行目は「gameAreaについてclickイベントが起こるのを待って実行。その時にはclickイベントをeに格納する」という意味です(合ってるか不安)
4~8行目はクリックした位置のcanvas内での座標をmousepoint.xとmousepoint.yに代入します。
クリックした位置は「画面全体に対して上から何ピクセル、左から何ピクセルか」でしか取得できません。
なので、rectに「canvasが画面全体に対して左から何ピクセル、上から何ピクセルか」を取得して、それをクリックした位置から引く事で「canvas内で上から何ピクセル、左から何ピクセルの所をクリックしたか」を求めています。
クリックした座標が分かるようになりました。
次は、うまく画像をクリックできたか?を判定します。
//クリックした時の処理
gameArea.addEventListener('click', e => {
//クリックした位置を取得
const rect = gameArea.getBoundingClientRect();
mousepoint = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
}
//画像をクリックしたかを判定する
if (mousepoint.x >= gazo.x && mousepoint.x <= gazo.x + 100 && mousepoint.y >= gazo.y && mousepoint.y <= gazo.y + 100) {
clearInterval(moving);
}
})
後半に判定を付け足しました。
if文で&&を使い、4つの条件を同時に満たした時に「画像をクリックした」と判定するように条件を決めました。
一つ目の条件は mousepoint.x >= gazo.x(クリックしたx座標が画像の左上の隅より右にある時)
二つ目は mousepoint.x <= gazo.x + 100(クリックしたx座標が画像の左上の隅から右に100ピクセル(画像の一片の長さ)以内の時)
さらに三つ目、四つ目の条件で、y軸についても同じように画像の範囲内かを判定しています。
clearInterval(何か変数);は「何か変数」に代入されているsetInterval(一定間隔で実行するやつ)を停止させます。
今回はmovingを止めています。すると関数moveが実行されなくなります。moveは画像の座標を変化させていた関数なので、画像が止まります。
これでゲームの完成です!
game.js全文(確認用)
//準備
const gameArea = document.getElementById('game-area');
const ctx = gameArea.getContext('2d');
//画像の読み込み
let object = new Image();
object.src = 'object.png'
//画像を動かす
let gazo = { x: 100, y: 100 };//画像の初期位置
function move() {
if (gazo.x >= 400) {//画像が画面から出てしまったらリセット
gazo.x = -100;
} else {//画面から出ていないならx方向に5進む
gazo.x = gazo.x + 5;
}
}
let moving = setInterval(move, 1000 / 30);//1000/30ミリ秒毎にmoveを実行する
//画面の表示
function draw() {
ctx.clearRect(0, 0, 400, 400);
ctx.drawImage(object, gazo.x, gazo.y);
window.requestAnimationFrame(draw); //毎秒60回のペースでdrawを実行する
}
draw(); //画面の描写をスタート
//クリックした時の処理
gameArea.addEventListener('click', e => {
//クリックした位置を取得
const rect = gameArea.getBoundingClientRect();
mousepoint = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
}
//画像をクリックしたかを判定する
if (mousepoint.x >= gazo.x && mousepoint.x <= gazo.x + 100 && mousepoint.y >= gazo.y && mousepoint.y <= gazo.y + 100) {
clearInterval(moving);
}
})
お疲れさまでした!
(明らかにおかしい説明とか、書かれた通りにやったけどここ動かないとかあったらコメントもらえると助かります……)
この記事が気に入ったらサポートをしてみませんか?