0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Lantern —レトロ3D表現のためのgame engine -- 壁打ち

0
Posted at

01. 壁打ちテニスゲーム

壁打ちテニスゲームを作成してください。
上壁グレー、SCORE表示、
四角黄色ボール、赤いPAD、
左右両端壁、背景黒

これまでに学んだ「四角形の描画」「AABB(軸に平行な境界ボックス)の当たり判定」「速度と反射の物理計算」をすべて組み合わせることで、壁打ちテニス(ブロック崩し風)ゲームが完成します。

スクリーンショット 2026-10-28 221339.png

条件(背景黒、グレーの壁、赤いPAD、黄色い四角ボール、スコア表示)をすべて満たしたコードを作成。

壁打ちテニスゲーム (main.wick)

// ==========================================
// 1. トップレベル: ゲームの初期設定
// ==========================================

// ボールの設定
let ball_size = 10.0
let ball_x = lt.W / 2.0
let ball_y = 50.0
let ball_vx = 150.0 // X方向の速度
let ball_vy = 150.0 // Y方向の速度

// パッド(赤いPAD)の設定
let pad_w = 60.0
let pad_h = 10.0
let pad_x = lt.W / 2.0 - pad_w / 2.0
let pad_y = lt.H - 30.0
let pad_speed = 300.0

// 壁の厚さ
let wall_t = 10.0

// ゲームの状態
let score = 0
let game_over = false

// ==========================================
// 2. ゲームループ(計算)
// ==========================================
fn update(dt: num) {
  // ゲームオーバー時はスペースキーでリセット
  if game_over == true {
    if lt.key("space") {
      ball_x = lt.W / 2.0
      ball_y = 50.0
      ball_vx = 150.0
      ball_vy = 150.0
      pad_x = lt.W / 2.0 - pad_w / 2.0
      score = 0
      game_over = false
    }
    return // これより下の計算を行わない
  }

  // ---------------------------------
  // PADの移動
  // ---------------------------------
  if lt.key("left") {
    pad_x = pad_x - pad_speed * dt
  }
  if lt.key("right") {
    pad_x = pad_x + pad_speed * dt
  }

  // PADが左右の壁にめり込まないようにする(ストッパー)
  if pad_x < wall_t {
    pad_x = wall_t
  }
  if pad_x + pad_w > lt.W - wall_t {
    pad_x = lt.W - wall_t - pad_w
  }

  // ---------------------------------
  // ボールの移動と壁の反射
  // ---------------------------------
  ball_x = ball_x + ball_vx * dt
  ball_y = ball_y + ball_vy * dt

  // 左壁との当たり判定
  if ball_x < wall_t {
    ball_x = wall_t
    ball_vx = ball_vx * -1.0
  }
  // 右壁との当たり判定
  if ball_x + ball_size > lt.W - wall_t {
    ball_x = lt.W - wall_t - ball_size
    ball_vx = ball_vx * -1.0
  }
  // 上壁との当たり判定
  if ball_y < wall_t {
    ball_y = wall_t
    ball_vy = ball_vy * -1.0
  }
  // 画面の下に落ちたらゲームオーバー
  if ball_y > lt.H {
    game_over = true
  }

  // ---------------------------------
  // ボールとPADの当たり判定 (AABB)
  // ---------------------------------
  let hit_x = (ball_x + ball_size > pad_x) and (ball_x < pad_x + pad_w)
  let hit_y = (ball_y + ball_size > pad_y) and (ball_y < pad_y + pad_h)
  
  if hit_x and hit_y {
    // ボールが下に向かって落ちている時だけ跳ね返す(連続ヒットのバグ防止)
    if ball_vy > 0.0 {
      ball_y = pad_y - ball_size
      
      // 反射させつつ、少しだけ速度を上げて難易度を上げる
      ball_vy = ball_vy * -1.05
      ball_vx = ball_vx * 1.05
      
      score = score + 10
    }
  }
}

// ==========================================
// 3. 描画ループ
// ==========================================
fn draw() {
  // 背景黒
  lt.clear(0.0, 0.0, 0.0)

  // ---------------------------------
  // 壁の描画 (グレー: 0.5, 0.5, 0.5)
  // ---------------------------------
  // 上壁
  lt.rect(0, 0, lt.W, wall_t, 0.5, 0.5, 0.5, 1.0)
  // 左壁
  lt.rect(0, 0, wall_t, lt.H, 0.5, 0.5, 0.5, 1.0)
  // 右壁
  lt.rect(lt.W - wall_t, 0, wall_t, lt.H, 0.5, 0.5, 0.5, 1.0)

  // ---------------------------------
  // ゲームオブジェクトの描画
  // ---------------------------------
  // PAD (赤: 1, 0, 0)
  lt.rect(pad_x, pad_y, pad_w, pad_h, 1.0, 0.0, 0.0, 1.0)

  // ボール (黄色: 1, 1, 0)
  lt.rect(ball_x, ball_y, ball_size, ball_size, 1.0, 1.0, 0.0, 1.0)

  // ---------------------------------
  // UIの描画
  // ---------------------------------
  lt.print("SCORE " + str(score), 20, 20, 1, 1, 1, 1)

  if game_over == true {
    lt.print("GAME OVER", lt.W / 2 - 40, lt.H / 2, 1, 0, 0, 1)
    lt.print("Press SPACE to Restart", lt.W / 2 - 80, lt.H / 2 + 20, 1, 1, 1, 1)
  }
}

スクリーンショット 2026-10-28 221403.png

実装のポイント

  • 反射の物理法則: 壁やPADに当たったとき、速度に -1 を掛けるだけで「逆方向に跳ね返る」物理挙動が完成します。さらに、PADに当たるたびに -1.05 を掛けることで、ラリーが続くほど徐々にボールが速くなるスリリングな仕様を入れています
  • AABB判定の応用: PADとボールの衝突は、これまで迷路などで使ってきた「四角形同士の重なり(AABB)」判定と全く同じロジックです
  • 状態の管理: game_over という変数をフラグ(目印)として使い、true になったらボールやPADの移動計算(update)を止めるという、ゲーム進行の基本構造を作っています

高校生のための解説

壁打ちテニスゲームのコードがどのように動いているのか、高校の「数学(座標)」と「物理(等速直線運動)」の知識に結びつけて解説します。

ゲームプログラムは基本的に、1. データの準備 → 2. 計算(裏側の処理) → 3. 描画(画面に映す) という3つのステップで成り立っています。

1. データの準備(トップレベル)

ここでは、ゲームが始まる前の「初期状態」を定義しています。物理のテスト問題でいう「初速度$v_0$は〜、初期位置$x_0$は〜とする」という前提条件のセットです。

  • **ball_x, ball_y**: ボールの現在位置(座標)です
  • **ball_vx, ball_vy**: ボールの速度(Velocity)です。物理のベクトルと同じで、X方向のスピードとY方向のスピードを別々に持たせています
  • game_over: これは「フラグ」と呼ばれるスイッチです。最初は false(オフ)にしておき、ゲームオーバーになったら true(オン)に切り替えて使います

2. update関数(計算と物理法則)

この関数は、1秒間に約60回という猛スピードで何度も呼び出され、「次の瞬間の座標はどうなるか?」をひたすら計算し続けます。

① ボールの移動計算(等速直線運動)

ball_x = ball_x + ball_vx * dt
ball_y = ball_y + ball_vy * dt

dt は「前の計算から何秒経ったか(Delta Time)」を表す、ごくわずかな時間(約0.016秒など)です。
これは、物理で習う 「現在の位置 = 前の位置 +(速度 × 時間)」 という公式 $x = x_0 + vt$ をそのままプログラムにしたものです。

② 壁のバウンド(ベクトルの反転)

if ball_x < wall_t { // もし左壁にめり込んだら
  ball_x = wall_t    // めり込んだ分を押し戻す
  ball_vx = ball_vx * -1.0 // X方向の速度を逆にする
}

ボールが壁にぶつかったら、速度に -1 を掛けます。右へ進む速度(プラス)にマイナスを掛けると左向き(マイナス)になるため、跳ね返る動きが完成します。

③ パッドとボールの当たり判定(不等式)

let hit_x = (ball_x + ball_size > pad_x) and (ball_x < pad_x + pad_w)

「ボールとパッドがぶつかっているか?」を、数学の連立不等式で判定しています。
「ボールの右端がパッドの左端より大きく、かつ、ボールの左端がパッドの右端より小さい」という条件を満たしたときだけ、X軸が重なっている(ヒットしている)とみなします。Y軸も同様に計算し、両方が重なっていれば「当たった!」と判定してボールを跳ね返します。

3. draw関数(画面の描画)

update関数で計算した最新の座標データを使って、画面に色を塗る関数です。これも1秒間に約60回実行されます。

  1. lt.clear(0, 0, 0): まず、画面全体を黒(RGBすべて0)で塗りつぶして、前のコマの残像を消します
  2. lt.rect(...): 最新の ball_x や pad_x の座標に合わせて、四角形(壁、パッド、ボール)を描きます
  3. パラパラ漫画の完成: 「少し動かして計算(update)→ 黒板を消して描き直す(draw)」を人間には見えないほどのスピードで繰り返すことで、画面上でボールが滑らかに動いているように見えます

この「座標・速度・当たり判定」の3つが、スーパーマリオのような2Dアクションから最新のFPSゲームまで、すべてのゲームの土台となっているプログラミングの基本原理です。


参考資料

0
0
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?