01. 壁打ちテニスゲーム
壁打ちテニスゲームを作成してください。
上壁グレー、SCORE表示、
四角黄色ボール、赤いPAD、
左右両端壁、背景黒
これまでに学んだ「四角形の描画」「AABB(軸に平行な境界ボックス)の当たり判定」「速度と反射の物理計算」をすべて組み合わせることで、壁打ちテニス(ブロック崩し風)ゲームが完成します。
条件(背景黒、グレーの壁、赤い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)
}
}
実装のポイント
-
反射の物理法則: 壁や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回実行されます。
-
lt.clear(0, 0, 0): まず、画面全体を黒(RGBすべて0)で塗りつぶして、前のコマの残像を消します -
lt.rect(...): 最新のball_xやpad_xの座標に合わせて、四角形(壁、パッド、ボール)を描きます - パラパラ漫画の完成: 「少し動かして計算(update)→ 黒板を消して描き直す(draw)」を人間には見えないほどのスピードで繰り返すことで、画面上でボールが滑らかに動いているように見えます
この「座標・速度・当たり判定」の3つが、スーパーマリオのような2Dアクションから最新のFPSゲームまで、すべてのゲームの土台となっているプログラミングの基本原理です。
参考資料

