ゲームエンジンやライブラリを使わずに、HTMLとJavaScriptだけでゲームを作ります。
以下のような人のためにあります。
ゲームを作ってみたいけど、何をどうしていいか分からない。
単純なゲームを作りたいだけなのに、UnityとかUnreal Engineは面倒くさい。
ある程度プログラミングの知識がある人を対象にしています。
この記事で作るもの
なんの味付けもない壁打ちテニスですが、遊んでみてください。
先に読んでおくもの
この記事は以下の記事の続きです。読んでいない方は、先に下記をお読みください。
全ソースコード
<html>
<body>
<canvas id="gameCanvas" width="800" height="400"
style="border:1px solid #000000; background-color: #000;"></canvas>
<script>
JavaScriptコード
</script>
</body>
</html>
Java Scriptの部分以外は前回から変わりません。JavaScriptのコードだけを抽出します。
const canvas = document.getElementById('gameCanvas');
const context = canvas.getContext('2d');
const State = {
STANBY: 0,
GAME: 1,
GAME_OVER: 2
}
// パドル
let padX = canvas.width / 2;
const padY = canvas.height - 60;
const padWidth = 100;
const padHeight = 20;
let padSpeed = 0;
// ボール
const ballRadius = 10;
let ballX;
let ballY;
let ballSpeedX;
let ballSpeedY;
// ゲーム状態管理
let gameState = State.STANBY;
let score = 0;
let high_score = 0;
// 入力ハンドラー
document.addEventListener('keydown', keyDownHandler, false);
document.addEventListener('keyup', keyUpHandler, false);
function drawBall() {
context.fillStyle = '#fff';
context.fillRect(ballX-ballRadius/2, ballY-ballRadius/2, ballRadius*2, ballRadius*2);
}
function drawPaddle() {
context.fillStyle = '#fff';
context.fillRect(padX, padY, padWidth, padHeight);
}
function keyDownHandler(event) {
if (event.key === 'Left' || event.key === 'ArrowLeft') {
padSpeed = -10;
}
if (event.key === 'Right' || event.key === 'ArrowRight') {
padSpeed = +10;
}
if (event.key == 's') {
if (gameState == State.STANBY) gameState = State.GAME;
}
if (gameState == State.GAME_OVER) gameState = State.STANBY;
}
function keyUpHandler(event) {
}
function isHitPaddle() {
if (ballX+ballRadius < padX) {
return false;
}
if (ballX-ballRadius > padX+padWidth) {
return false;
}
if (ballY+ballRadius < padY) {
return false;
}
if (ballY-ballRadius > padY+padHeight) {
return false;
}
return true;
}
function update() {
// ボールの位置を更新
ballX += ballSpeedX;
ballY += ballSpeedY;
// ボールの位置が下の境界を超えたらゲームオーバー
if (ballY + ballRadius > canvas.height) {
gameState = State.GAME_OVER;
}
// ボールの位置が上の境界に触れたら跳ね返って点数追加
if(ballY - ballRadius < 0) {
ballSpeedY = -ballSpeedY;
score += 1;
}
// ボールの位置が左右の境界を超えたときの処理
if (ballX - ballRadius < 0 || ballX + ballRadius > canvas.width) {
ballSpeedX = -ballSpeedX;
}
padX += padSpeed;
if (padX < 0) padX = 0;
if (padX + padWidth > canvas.width) padX = canvas.width - padWidth;
if (padSpeed > 0) padSpeed -= 1;
if (padSpeed < 0) padSpeed += 1;
if (isHitPaddle()) {
if (padSpeed != 0) {
dx = padSpeed > 0 ? 1 : -1;
if (Math.abs(ballSpeedX+dx) >= 1 && Math.abs(ballSpeedX+dx) <= 3)
ballSpeedX += dx;
}
if (ballY >= padY && ballY <= padY+padHeight) {
ballSpeedX = -ballSpeedX;
}
ballSpeedY = -ballSpeedY;
}
}
function draw_text_center(text, font='30px Consolas') {
context.font = font;
context.textAlign = 'left';
text_w = context.measureText(text).width;
context.fillText(text, canvas.width/2-text_w/2, canvas.height/2);
}
function draw_scores() {
context.font = '20px Consolas';
context.textAlign = 'left';
score_text = 'Score: ' + score;
context.fillText(score_text, 10, canvas.height-10);
hscore_text = 'High Score: ' + high_score;
context.fillText(hscore_text, canvas.width-200, canvas.height-10);
}
function init() {
// パドル位置の初期化
padX = canvas.width / 2 - padWidth/2;
// ボールの位置の初期化
ballX = padX + padWidth/2;
ballY = padY - ballRadius*2;
ballSpeedX = 2;
ballSpeedY = -2;
// スコア初期化
if (high_score < score) high_score = score;
score = 0;
}
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
draw_scores();
drawBall();
drawPaddle();
switch (gameState) {
case State.STANBY:
init();
draw_text_center("Press 'S' Key to Start");
break;
case State.GAME:
update();
break;
case State.GAME_OVER:
draw_text_center("Game Over!!");
break;
}
}
setInterval(draw, 10);
ゲームの仕様を考える
ゲームとしての仕様は以下のとおりです。
- 画面の上部の壁(境界)にボールを当てたら+1点
- 画面の下部(境界)にボールが触れたらゲームオーバー
という、とてもシンプルなルールです。あとはScoreとHigh Scoreが表示されます。
ボールの動きは、基本的に大きく変化しないので非常に単調で、やろうと思えば延々と壁打ち可能です。
ただし、前回の実装から、パドルの移動部分とボールとの当たり判定部分に多少改良を加えました。ボールがパドルに当たる瞬間、パドルが左右に動いている場合は、その方向に移動量が一定の範囲内で加味されます。これで多少は単調さが解消されてはいます。
ゲーム性をあげるための仕掛けは色々考えられます。この記事の最後に発展形のアイディアを出してみたいと思います。
ゲームの状態遷移
このゲームを構成する状態は3つあります。
- 開始待ち画面(STANDY)
- ゲーム中(GAME)
- ゲームオーバー(GAME_OVER)
以下がその状態遷移図です。
JavaScriptの中で状態遷移はどうやって実装しているのでしょうか? draw()関数の以下の部分です。
function draw() {
・・・
switch (gameState) {
case State.STANBY:
init();
draw_text_center("Press 'S' Key to Start");
break;
case State.GAME:
update();
break;
case State.GAME_OVER:
draw_text_center("Game Over!!");
break;
}
}
draw()関数は10ミリ秒に一回呼ばれます。その時に、今の状態を見て処理を振り分ける、というわけです。状態別にそれぞれに応じた処理を書けばよいだけです。とてもシンプルです。
状態の変更は、gameState 変数を変更することで行います。コードの中の該当部分を追っていきましょう。
初期状態は STANDBYです。
let gameState = State.STANBY;
特定の状態にいるときに特定のキーが入力されたら遷移します。
function keyDownHandler(event) {
・・・
if (event.key == 's') {
if (gameState == State.STANBY) gameState = State.GAME;
}
if (gameState == State.GAME_OVER) gameState = State.STANBY;
}
ボールが下の境界を超えたらゲームオーバの部分の処理です。
function update() {
・・・
// ボールの位置が下の境界を超えたらゲームオーバー
if (ballY + ballRadius > canvas.height) {
gameState = State.GAME_OVER;
}
・・・
}
さて、最後に State の定義を見てみます。オブジェクトのプロパティとして定義していて、中身は数字です。
const State = {
STANBY: 0,
GAME: 1,
GAME_OVER: 2
}
さて、この後は
さて、これでいったんゲームは完成とします。ミニマムなゲームとはいえ、必要な構成要素は一通り入っています。
- 画面上にオブジェクトを描画して動かす
- ユーザの入力を受けてゲームの中のオブジェクトを反応させる
- ゲームの状態遷移を管理し、状態によって振る舞いを変える
これらの骨組みは、ほぼすべてのアクションゲームに共通するものです。
今回作成した「壁打ちテニス」はこれ以上ないシンプルさでしたが、それでもいろんなバリエーションが考えられます。例えば、
- 壁の位置によってボールの跳ね方を変える
- 時間が経つにつれてボールの速度を上げる
- パドルが連続して動かせる時間を制限する(人間の体力パラメータのようなもの)
などです。
ゲームに登場するオブジェクトを増やすのなら、アイディア次第でバリエーションはほぼ無限です。パドルを増やして二人用ゲームにしたり、障害物を画面中に置いたりなど、好きなように作り変えてみてください。
コメント