Skip to content

06 Collision Detection

Dave Kimber edited this page Jun 4, 2024 · 1 revision

For a quick demo, check out the collision-detection example game.

Being able to detect collisions between entities is a fundamental part of many games, quip provides a flexible framework for defining and managing collisions between different groups of sprites.

Colliders

A collider is an object that represents the collision handling relationship between to sprite groups. In its simplest form, the quip.collision/collider function takes two :sprite-group keywords, and a pair of collision handling functions.

You can specify a :colliders vector in a scene configuration map.

(defn colliders
  "Returns the list of colliders in the scene"
  []
  [(quip.collision/collider
    :player    ;; Sprite group `a`
    :enemies   ;; Sprite group `b`

    ;; Collision handler `a`, returns an updated `a`
    (fn [player enemy]
      (lose-health player))

    ;; Collision handler `b`, returns an updated `b`
    (fn [enemy player]
      nil))])

(defn scenes []
  {:level-01 {:update-fn update-level-01
              :draw-fn draw-level-01
              :colliders (colliders)}})

The first collision handler takes both an a and a b sprite and returns an updated a. It's often necessary to have information on both sprites in order to update the sprite (how much damage does that bullet do? what was the wall on the left or right?). Conversely the second handler takes a b and an a and returns an updated b. Either handler can return nil, which will remove the sprite from the scene.

⚠️ You must call the quip.collision/update-collisions function on the game state in your scene's update function:

(defn update-level-01
  [state]
  (-> state
      quip.sprite/update-scene-sprites
      ;; You must update collisions for them to work
      quip.collision/update-collisions))

Collision Detection Functions

By default a collider uses a simple rectangular collision detection function based on the position, width and height of each sprite quip.collision/w-h-rects-collide?.

You can override this for your collider by providing a value for :collision-detection-fn. This should be a function which takes an a and a b sprite, and returns whether they are colliding.

Several useful collision detection functions are provided by the quip.collision namespace. In ascending order of complexity and cost to calculate:

  • equal-pos? do the sprites have the same :pos?
  • pos-in-rect? is the :pos of a inside the width/height (w-h) rectangle of b?
  • rect-contains-pos? is the :pos of b inside the w-h rectangle of a?
  • w-h-rects-collide? do the w-h rectangles of a and b overlap?
  • pos-in-poly? is the :pos of a inside the bounding polygon of b? (see Custom Bounds Functions)
  • poly-contains-pos? is the :pos of b inside the bounding polygon of a?
  • polys-collide? do the bounding polygons of a and b overlap?
  • pos-in-rotating-poly? is the :pos of a inside the bounding polygon of b allowing for the :rotation angle of b?
  • rotating-poly-contains-pos? is the :pos of b inside the bounding polygon of a allowing for the :rotation angle of a?
  • rotating-polys-collide? do the bounding polygons of a and b overlap allowing for the :rotation angles of a and b?

These functions are based on the geometric collision predicates functions found in the quip.utils namespace.

Custom Bounds Functions

Sprites with complex shapes may want to specify a custom bounds function. When calling one of the built-in sprite creation functions you can set the :points optional argument to be a vector of [x y] positions relative to the sprite position (i.e. the top left of the sprite is [0 0]) that represent its bounding polygon. Alternatively you can set the :bounds-fn optional argument and provide a function that returns a bounding polygon points vector.

(quip.sprite/image-sprite
 :ship
 [100 100]
 24
 48
 "ship.png"
 :points [[0 -160]     ;; Makes kind of a ship shape:
          [79 -67]     ;;
          [79 90]      ;;    ^
          [52 160]     ;;   / \
          [-55 160]    ;;   | |
          [-80 90]     ;;   | |
          [-80 -67]])  ;;   └-┘

Non-Collide Functions

In some cases you may wish to specify what happens to sprites a and b when they are tested for collision and are found not to collide. You can use the optional arguments :non-collide-fn-a and :non-collide-fn-b. These work in the same way as the collision handling functions, they take both sprites as arguments and return an updated version of one of them.

(quip.ccollision/collider
 :player
 :food
 (fn [p f]
   (increase-health p))
 (fn [f p]
   nil)

 ;; NOTE: be careful, this is called once for each `:food` sprite
 :non-collide-fn-a (fn [p f]
                     (decrease-health p)))