-
Notifications
You must be signed in to change notification settings - Fork 0
06 Collision Detection
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.
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 thequip.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))
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
ofa
inside the width/height (w-h
) rectangle ofb
? -
rect-contains-pos?
is the:pos
ofb
inside thew-h
rectangle ofa
? -
w-h-rects-collide?
do thew-h
rectangles ofa
andb
overlap? -
pos-in-poly?
is the:pos
ofa
inside the bounding polygon ofb
? (see Custom Bounds Functions) -
poly-contains-pos?
is the:pos
ofb
inside the bounding polygon ofa
? -
polys-collide?
do the bounding polygons ofa
andb
overlap? -
pos-in-rotating-poly?
is the:pos
ofa
inside the bounding polygon ofb
allowing for the:rotation
angle ofb
? -
rotating-poly-contains-pos?
is the:pos
ofb
inside the bounding polygon ofa
allowing for the:rotation
angle ofa
? -
rotating-polys-collide?
do the bounding polygons ofa
andb
overlap allowing for the:rotation
angles ofa
andb
?
These functions are based on the geometric collision predicates functions found in the quip.utils
namespace.
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]]) ;; └-┘
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)))