ClojureのQuilでflappy bird。第6回

2023-05-19

「Nature of Code -Processingではじめる自然現象のシミュレーション」という本を入手。1章完了まで進めたので、おさらいに、フラッピーバードを作成中。
Clojureを勉強中なので、こうした方がいいよとかあれば、ツッコミお願いします。

前回は、障害物の表示を作成しました。

今回は、衝突判定を作成します。

当たり判定を作る

1つの点が四角形とぶつかっている(四角の中にある)かどうかは、
点のx座標が四角の幅の中にある、かつ、点のY座標が四角の高さの中にある
(and (<= x1 x x2) (<= y1 y y2))
で判断できます。

それの応用で、四角形と四角形がぶつかっているかの判断は、下の式で判断できるようです。

;;mover_test.clj
(t/testing "collision?"
    (let [m1 (mv/mover 20. 30. 40. 50.)
          m2 (mv/mover 100. 110. 20. 30.)
          m3 (mv/mover 10. 10. 30. 30.)
          m4 (mv/mover 10. 10. 100. 100.)
          m5 (mv/mover 25. 35. 35. 45.)
          ]
      (t/testing "範囲外はfalse"
        (t/is (not (mv/collision? m1 m2))))
      (t/testing "範囲内はtrue"
        (t/is (mv/collision? m1 m3))    ;i一部が重なる
        (t/is (mv/collision? m1 m4))    ;m4 ⊃ m1
        (t/is (mv/collision? m1 m5))    ;m1 ⊃ m5
        )))
;;mover.clj
(defn collision? [a b]
  (let [[ax1 ay1] (:location a)
        [ax2 ay2] (map + [ax1 ay1] (:size a))     ;;四角形左上の座標に幅高さを足して右下の座標を求める
        [bx1 by1] (:location b)
        [bx2 by2] (map + [bx1 by1] (:size b))]    
    (and (<= (max ax1 bx1) (min ax2 bx2))         ;;当たり判定
         (<= (max ay1 by1) (min ay2 by2)))))

テストを通ったので、組み込んでいきます。

当たりを判定する

まずは、update-stateにぶつかり判定を入れてみます。

(defn update-state [status]
  (let [new-status (-> status
                       (update :bird #(-> %
                                          (mv/apply-force gravity)
                                          (mv/update)))
                       (update :pipes #(map mv/update %)))]
    (when (->> new-status                                   ;;更新した位置で
               :pipes                                       ;;土管と
               (map #(mv/collision? (:bird new-status) %))  ;;自機が↓ぶつかっていたら
               (some true?))                                ;;ひとつでも
      (println "hit!"))                                     ;;"hit"の文字を出力
    new-status))

動作確認すると、hitの文字が出力されました。

ゲームオーバーをつくる

自機が土管にぶつかったら、ゲームオーバーにして描画や操作の受付を停止します。

ゲームオーバーを判断するために、関数間で渡しているマップに:game-over? false を追加します。

update-statusでは、game-over?が、trueなら受け取ったマップをそのまま返す、falseなら通常の処理

draw-stateでは、game-over?が、trueならgame overの文字を表示、falseなら通常の処理するように変更します。

(defn setup []
  (q/frame-rate 30)
  {:bird (mv/mover (/ (q/width) 4.) (/ (q/height) 2.) 30. 30.)
   ;;略
   :game-over? false})                                      ;追加

(defn update-state [{:keys [game-over?] :as status}]
  (if game-over?
    status                                                  ;;game overならマップをそのまま帰す
    (let [new-status (-> status

                         ;;略
                         )
          hit? (->> new-status                               ;;当たり判定
                          :pipes
                          (map #(mv/collision? (:bird new-status) %))
                          (some true?))]
      (assoc new-status :game-over? hit?))))                 ;;判定結果でマップを更新

(defn draw-state [{:keys [bird pipes game-over?]}]
  (if game-over?
    (do                                                                     ;;game overなら
      (q/text-size 32)                       
      (q/fill 0)
      (q/text "game-over!" (-> (q/width) (/ 2) (- 80)) (/ (q/height) 2)))   ;;gameover表示
    (do 

      ;;略                                                                   ;;通常の処理
      ))))

これで、自機が障害物に当たるとゲームオーバーするようになりました。

まとめ

今回は、当たり判定とゲームオーバーの処理を追加しました。

次は、以下を作成します。

  • 障害物(土管)を上下ペアで現れるように
  • 得点の表示
  • 障害物を通過したら点数が入る
  • 落下、上昇、スクロールなどのスピード調整