Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第18章 ゲームは反応のまとまり

これまで使った位置、速度、入力、条件分岐、状態を組み合わせ、操作すると結果が返る画面を作ります。その結果が次の操作を変えると、遊びの流れが生まれます。

18.1 まず目標のない操作を作る

矢印キーで円を動かします。第9章と同じ入力方法です。

def setup
  createCanvas(600, 360)
  @player_x = width / 2
  @player_y = height / 2
end

def draw
  @player_x -= 4 if keyIsDown(LEFT_ARROW)
  @player_x += 4 if keyIsDown(RIGHT_ARROW)
  @player_y -= 4 if keyIsDown(UP_ARROW)
  @player_y += 4 if keyIsDown(DOWN_ARROW)

  @player_x = constrain(@player_x, 20, width - 20)
  @player_y = constrain(@player_y, 20, height - 20)

  background(25, 30, 48)
  fill(90, 210, 180)
  ellipse(@player_x, @player_y, 40, 40)
end

constrain(コンストレイン)は、値を最小と最大の範囲へ収めます。第12章の keep_insideで自作した処理と同じ働きが、rbCanvas/p5 に用意されています。円の半径 20 を境界に使い、画面外へ出ないようにしています。

keyIsDown は、指定したキーが現在押されているかを調べます。LEFT_ARROW、RIGHT_ARROW、UP_ARROW、DOWN_ARROW は、それぞれ左・右・上・下の矢印キーを表す名前です。処理の後ろに if と条件を書く形を使い、押されている間は位置を 4 ずつ変えます。-= は左辺の値を減らし、+= は増やす記号です。

まだ勝敗はありませんが、操作への反応はあります。最初からスコアや敵を足さず、動かす感触を決めると調整点が見えます。

18.2 距離で触れたことを判定する

目標の位置を加えます。次の setup は変更部分です。18.1 の長いコードにある def setup から対応する end までと置き換えます。

def setup
  createCanvas(600, 360)
  @player_x = width / 2
  @player_y = height / 2
  @target_x = random(40, width - 40)
  @target_y = random(40, height - 40)
  @score = 0
end

続いて、18.1 の長いコードの draw で位置を更新したあと、background より前へ次の変更部分を足し、中心同士の距離を調べます。

distance = dist(@player_x, @player_y, @target_x, @target_y)

if distance < 35
  @score += 1
  @target_x = random(40, width - 40)
  @target_y = random(40, height - 40)
end

プレイヤーの半径が 20、目標の半径が 15 なら、中心間の距離が 35 より小さいときに円同士が重なります。四角い範囲で調べる方法もありますが、円には中心間距離が形と対応します。

dist(ディスト)は二点間の距離を求めます。dist(x1, y1, x2, y2) では、1 つ目の点 (x1, y1) から 2 つ目の点 (x2, y2) までの距離を返します。ここではプレイヤーと目標の中心座標を渡し、結果を distance へ入れています。@score += 1 は、今の得点へ 1 を足す書き方です。

18.3 結果をすぐ画面へ返す

目標とスコアを描きます。次の変更部分は、18.1 の drawの最後、プレイヤーを描く ellipse のあとへ足します。

fill(250, 190, 70)
ellipse(@target_x, @target_y, 30, 30)

fill(255)
textAlign(LEFT, TOP)
textSize(20)
text("score: #{@score}", 16, 16)

触れた瞬間に目標が移動し、スコアが増えます。操作、判定、反応が 1 フレームの中でつながりました。これがゲームの最小の循環です。

得点まで動く完成コード

ここまでの変更部分を一つへまとめます。最初の目標だけはプレイヤーの右 80 ピクセルへ置き、右矢印キーを押せば必ず最初の得点を試せるようにしています。2 個目からは乱数で場所を決めます。

def setup
  createCanvas(600, 360)
  @player_x = width / 2
  @player_y = height / 2
  @target_x = width / 2 + 80
  @target_y = height / 2
  @score = 0
end

def draw
  @player_x -= 4 if keyIsDown(LEFT_ARROW)
  @player_x += 4 if keyIsDown(RIGHT_ARROW)
  @player_y -= 4 if keyIsDown(UP_ARROW)
  @player_y += 4 if keyIsDown(DOWN_ARROW)
  @player_x = constrain(@player_x, 20, width - 20)
  @player_y = constrain(@player_y, 20, height - 20)

  distance = dist(@player_x, @player_y, @target_x, @target_y)
  if distance < 35
    @score += 1
    @target_x = random(40, width - 40)
    @target_y = random(40, height - 40)
  end

  background(25, 30, 48)
  fill(90, 210, 180)
  ellipse(@player_x, @player_y, 40, 40)

  fill(250, 190, 70)
  ellipse(@target_x, @target_y, 30, 30)

  fill(255)
  textAlign(LEFT, TOP)
  textSize(20)
  text("score: #{@score}", 16, 16)
end
濃紺の 600×360 のキャンバスに緑のプレイヤーと右隣の黄色い目標があり、左上に score: 0 と表示された実行結果
図 18-1 開始時は、右矢印キーで届く位置へ最初の目標を置く。
濃紺の 600×360 のキャンバスで緑のプレイヤーが右へ移動し、黄色い目標が左寄りの中央へ移り、左上に score: 1 と表示された実行結果
図 18-2 目標へ触れると得点が 1 になり、次の目標が別の位置へ移る。

18.4 制限時間より先に終わり方を選ぶ

スコアが 5 になったら終了する作品を作ります。終了を表す値を 1 行だけ足しても、draw がその値を読まなければ画面は変わりません。次の完成コードでは、@finished を用意し、得点が 5 になったら true へ変えます。draw の最初で @finished を調べ、終了中は終了画面を描いて return でそのフレームの残りを行いません。

def setup
  createCanvas(600, 360)
  @player_x = width / 2
  @player_y = height / 2
  @target_x = width / 2 + 80
  @target_y = height / 2
  @score = 0
  @finished = false
end

def draw
  if @finished
    background(45, 35, 70)
    fill(255)
    textAlign(CENTER, CENTER)
    textSize(32)
    text("5個みつけた!", width / 2, height / 2)
    return
  end

  @player_x -= 4 if keyIsDown(LEFT_ARROW)
  @player_x += 4 if keyIsDown(RIGHT_ARROW)
  @player_y -= 4 if keyIsDown(UP_ARROW)
  @player_y += 4 if keyIsDown(DOWN_ARROW)
  @player_x = constrain(@player_x, 20, width - 20)
  @player_y = constrain(@player_y, 20, height - 20)

  distance = dist(@player_x, @player_y, @target_x, @target_y)
  if distance < 35
    @score += 1
    if @score >= 5
      @finished = true
    else
      @target_x = random(40, width - 40)
      @target_y = random(40, height - 40)
    end
  end

  background(25, 30, 48)
  fill(90, 210, 180)
  ellipse(@player_x, @player_y, 40, 40)

  fill(250, 190, 70)
  ellipse(@target_x, @target_y, 30, 30)

  fill(255)
  textAlign(LEFT, TOP)
  textSize(20)
  text("score: #{@score}", 16, 16)
end

return は、ここでは「この draw の処理をここで終える」という命令です。終了画面を描いたあとにプレイヤーや目標を重ねず、キーによる位置更新も止められます。すぐもう一度遊ぶには、エディタの実行ボタンを押します。発展として再挑戦キーを作りたくなったら、第15章の場面管理を使い、キー入力で setup と同じ初期値へ戻します。

濃い紫色の 600×360 のキャンバス中央に、白い文字で「5個みつけた!」と表示された終了画面
図 18-3 5 点に達するとプレイヤーと目標の描画を止め、終了画面だけを表示する。

得点を競わない遊び方も作れます。たとえば、次のように終了条件や表示を変えられます。

  • 5 個集めると色が変わり、そのまま続く
  • スコアを表示せず、集めた位置だけ軌跡として残る
  • 目標から逃げるほど音が静かになる
  • 終了条件を置かず、好きなだけ触れる

まず一つの操作に一つの反応を対応させます。遊び直して必要だと感じたルールだけを足します。

同じ移動コードでも、作品の意味は変えられます。

  • 集める: 目標へ触れると増える。見つける喜びを作る
  • 避ける: 目標へ触れない時間を残す。距離と緊張を作る
  • 痕跡を描く: スコアを消し、通った場所へ線を残す。勝敗ではなく動きそのものを見る

選んだルールによって、速さ、探索、軌跡など、遊んでいるときに注目するものが変わります。操作を確かめるときは、「速い操作が得意な人だけ有利になっていないか」「キーを押し続けにくい人も遊べるか」「失敗したとき、責める表示になっていないか」を順に見ます。問題があれば、速度、操作、言葉のうち該当する箇所を直します。

18.5 一人で遊び直す

作った直後は、操作もルールも全部知っています。一度保存して閉じ、翌日に説明を読まず遊び直します。

  • 最初の 3 秒で何を押せばよいか分かるか
  • 目標へ触れたことが画面から分かるか
  • 速すぎる、遅すぎると感じる場所はどこか
  • もう一度遊びたい変化が一つあるか

気になった箇所を一つ直してもう一度遊び、前の版と比べます。

18.6 ゲームをもっと育てるなら

今のゲームを作り直さず、変える場所を一つ選びます。

  • 第15章の場面管理を使い、開始画面と再挑戦を加える
  • プレイヤーの速さを 3 種類作り、いちばん操作しやすいものを選ぶ
  • スコアを消し、色、音、軌跡で触れた結果を返す
  • 集める代わりに避ける、追いかける代わりに観察するルールへ変える

遊ぶ人に集めてほしいのか、避けてほしいのか、動いた跡を見てほしいのかを決めます。その操作と関係のないルールは外せます。

キーを押したときの速さや得点表示に、もう一度触りたくなる反応があるか遊び直します。次章では、これまでのスケッチから一つ選び、完成版へ近づけます。

参考資料