第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
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 と同じ初期値へ戻します。
得点を競わない遊び方も作れます。たとえば、次のように終了条件や表示を変えられます。
- 5 個集めると色が変わり、そのまま続く
- スコアを表示せず、集めた位置だけ軌跡として残る
- 目標から逃げるほど音が静かになる
- 終了条件を置かず、好きなだけ触れる
まず一つの操作に一つの反応を対応させます。遊び直して必要だと感じたルールだけを足します。
同じ移動コードでも、作品の意味は変えられます。
- 集める: 目標へ触れると増える。見つける喜びを作る
- 避ける: 目標へ触れない時間を残す。距離と緊張を作る
- 痕跡を描く: スコアを消し、通った場所へ線を残す。勝敗ではなく動きそのものを見る
選んだルールによって、速さ、探索、軌跡など、遊んでいるときに注目するものが変わります。操作を確かめるときは、「速い操作が得意な人だけ有利になっていないか」「キーを押し続けにくい人も遊べるか」「失敗したとき、責める表示になっていないか」を順に見ます。問題があれば、速度、操作、言葉のうち該当する箇所を直します。
18.5 一人で遊び直す
作った直後は、操作もルールも全部知っています。一度保存して閉じ、翌日に説明を読まず遊び直します。
- 最初の 3 秒で何を押せばよいか分かるか
- 目標へ触れたことが画面から分かるか
- 速すぎる、遅すぎると感じる場所はどこか
- もう一度遊びたい変化が一つあるか
気になった箇所を一つ直してもう一度遊び、前の版と比べます。
18.6 ゲームをもっと育てるなら
今のゲームを作り直さず、変える場所を一つ選びます。
- 第15章の場面管理を使い、開始画面と再挑戦を加える
- プレイヤーの速さを 3 種類作り、いちばん操作しやすいものを選ぶ
- スコアを消し、色、音、軌跡で触れた結果を返す
- 集める代わりに避ける、追いかける代わりに観察するルールへ変える
遊ぶ人に集めてほしいのか、避けてほしいのか、動いた跡を見てほしいのかを決めます。その操作と関係のないルールは外せます。
キーを押したときの速さや得点表示に、もう一度触りたくなる反応があるか遊び直します。次章では、これまでのスケッチから一つ選び、完成版へ近づけます。
参考資料
- rbCanvas/p5 0.5.1 API リファレンス — キー入力、
dist、constrainなどの仕様 - Ruby リファレンスマニュアル「制御構造」 — 条件分岐の構文