おさかなサバイバルの作り方 — JavaScriptでブラウザゲームを作ろう

このゲームで遊ぶ → ソースコード全文 →

このチュートリアルで学べること

おさかなサバイバルは、自分より小さな魚を食べて大きくなるゲームです。このゲームを作ることで、Canvas描画の基本やマウス追従、円同士の当たり判定など、ゲーム開発で欠かせないテクニックが身につきます。

ステップ1: 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 / 2canvasH / 2を使っています。tailPhaseは尾びれのアニメーション用です。

ステップ2: マウス追従とNPC魚を作ろう

プレイヤーの魚をマウスに追従させましょう。マウスの座標を取得して、少しずつプレイヤーを近づけるとなめらかな動きになります。

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魚の生成

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つの辺(上下左右)からランダムに出現位置を決め、画面の中心に向かって泳がせています。

ステップ3: 当たり判定とゲームループを実装しよう

円同士の当たり判定は「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つの基本テクニックを使って、おさかなサバイバルの仕組みが分かりましたね。発展として、泡や水草などの背景エフェクトを追加したり、魚の種類を増やしたり、パワーアップアイテムを実装してみましょう。

よくある質問

Q: プログラミング初心者でも作れますか?

A: はい、大丈夫です。このチュートリアルではステップごとにコードを書いていくので、初めての方でも順番に進めれば完成できます。わからないところがあれば、ひなテックの教室で質問もできますよ。

Q: どのくらい時間がかかりますか?

A: 基本部分は約30分〜1時間で作れます。見た目をこだわったり機能を追加すると、さらに楽しく発展させられます。