おさかなサバイバルは、自分より小さな魚を食べて大きくなるゲームです。このゲームを作ることで、Canvas描画の基本やマウス追従、円同士の当たり判定など、ゲーム開発で欠かせないテクニックが身につきます。
まずはCanvasを設定しましょう。Canvas要素を取得して描画コンテキスト(ctx)を準備します。高解像度ディスプレイにも対応するために、devicePixelRatioを使います。
var canvas = document.getElementById('game-canvas');
var ctx = canvas.getContext('2d');
function resizeCanvas() {
var wrapper = canvas.parentElement;
var rect = wrapper.getBoundingClientRect();
var dpr = window.devicePixelRatio || 1;
canvasW = rect.width;
canvasH = rect.height;
canvas.width = canvasW * dpr;
canvas.height = canvasH * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
getContext('2d')で2D描画用のコンテキストを取得します。devicePixelRatioを掛けることで、Retinaディスプレイなどでもくっきり表示できます。
プレイヤーの魚はオブジェクトで管理します。位置(x, y)と半径(radius)が基本の情報です。
var player = {
x: canvasW / 2,
y: canvasH / 2,
radius: 12,
color: '#3498DB',
vx: 0, vy: 0,
tailPhase: 0
};
画面の中央からスタートするように、canvasW / 2とcanvasH / 2を使っています。tailPhaseは尾びれのアニメーション用です。
プレイヤーの魚をマウスに追従させましょう。マウスの座標を取得して、少しずつプレイヤーを近づけるとなめらかな動きになります。
var targetX, targetY;
var PLAYER_LERP = 0.08;
canvas.addEventListener('mousemove', function (e) {
var rect = canvas.getBoundingClientRect();
targetX = e.clientX - rect.left;
targetY = e.clientY - rect.top;
});
// update関数内
var dx = targetX - player.x;
var dy = targetY - player.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d > 1) {
var speed = Math.min(3.5 + player.radius * 0.02, d * PLAYER_LERP + 1.75);
player.vx = (dx / d) * speed;
player.vy = (dy / d) * speed;
player.x += player.vx;
player.y += player.vy;
}
PLAYER_LERP(線形補間)を使うことで、マウスにぴったりくっつくのではなく、ゆるやかに近づく自然な動きを実現できます。
NPC魚は画面の端からランダムに出現させましょう。プレイヤーのサイズに応じて、小さい魚と大きい魚のバランスを調整します。
function createNPC() {
var radius = rand(5, 40);
var edge = Math.floor(Math.random() * 4);
var x, y;
if (edge === 0) { x = rand(radius, canvasW - radius); y = -radius * 3; }
else if (edge === 1) { x = rand(radius, canvasW - radius); y = canvasH + radius * 3; }
else if (edge === 2) { x = -radius * 3; y = rand(radius, canvasH - radius); }
else { x = canvasW + radius * 3; y = rand(radius, canvasH - radius); }
return {
x: x, y: y, radius: radius,
color: FISH_COLORS[Math.floor(Math.random() * FISH_COLORS.length)],
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed
};
}
4つの辺(上下左右)からランダムに出現位置を決め、画面の中心に向かって泳がせています。
円同士の当たり判定は「2つの円の中心間の距離」と「2つの半径の合計」を比べるだけです。距離が半径の合計より小さければ衝突しています。
function dist(x1, y1, x2, y2) {
var dx = x1 - x2;
var dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
}
// 衝突判定
var d = dist(player.x, player.y, npc.x, npc.y);
var touchDist = player.radius + npc.radius;
if (d < touchDist * 0.7) {
if (player.radius > npc.radius) {
// 自分より小さい魚を食べる
score += Math.ceil(npc.radius);
player.radius += npc.radius * 0.4 / player.radius * 5;
} else {
// 自分より大きい魚に食べられる → ゲームオーバー
gameOver();
}
}
touchDist * 0.7と少し余裕を持たせることで、見た目的に自然な衝突判定になります。自分より大きい魚に触れるとゲームオーバー、小さい魚に触れるとスコア獲得&成長します。
最後に、毎フレーム「更新→描画」を繰り返すゲームループを作りましょう。
function gameLoop() {
update();
draw();
if (gameRunning) {
animFrameId = requestAnimationFrame(gameLoop);
}
}
requestAnimationFrameは、ブラウザの描画タイミングに合わせて関数を呼び出してくれます。通常は1秒間に約60回呼ばれるので、なめらかなアニメーションになります。
おつかれさまでした!Canvas描画、マウス追従、円の当たり判定、ゲームループという4つの基本テクニックを使って、おさかなサバイバルの仕組みが分かりましたね。発展として、泡や水草などの背景エフェクトを追加したり、魚の種類を増やしたり、パワーアップアイテムを実装してみましょう。
A: はい、大丈夫です。このチュートリアルではステップごとにコードを書いていくので、初めての方でも順番に進めれば完成できます。わからないところがあれば、ひなテックの教室で質問もできますよ。
A: 基本部分は約30分〜1時間で作れます。見た目をこだわったり機能を追加すると、さらに楽しく発展させられます。