Better slopes in AABB collision systems

ibobev1 pts0 comments

Better slopes in AABB collision systems · Andrey Listopadov

Better slopes in AABB collision systems

Wed, Jun 17, 2026

@gamedev @programming<br>tic80<br>fennel<br>~10 minutes read

Earlier this year I published a guide on how to implement slopes in AABB collision resolution using bump.lua.<br>The resulting system worked, but was a bit hard to use in-game and had a few issues, so I wouldn&rsquo;t consider it a viable solution.

The main issue with the old approach was the use of the cross collision response.<br>It allowed the object to move through the slope as if it wasn&rsquo;t there at all, and then the update loop corrected the y position after the fact.<br>This alone makes writing code the game a lot more complicated than it needs to be:

(fn player-collision-filter [_ obj]<br>(if (not= nil obj.slope)<br>:cross<br>:slide))

(fn grounded? [obj]<br>(let [(_ y collisions l)<br>(world:check obj obj.x (+ obj.y 1) player-collision-filter)]<br>(and (or (faccumulate [res nil i 1 l :until res]<br>(let [col (. collisions i)]<br>(when col.other.slope ;;<br>(= obj.y (col.other.slope obj)))))<br>(= obj.y y))<br>collisions)))

(fn update [player main]<br>;; ---8<br>(let [(x y collisions l)<br>(world:move<br>player<br>(+ player.x player.x-vel)<br>(+ player.y player.y-vel)<br>player-collision-filter)]<br>(doto player<br>(tset :x x)<br>(tset :y y))<br>(for [i 1 l]<br>(let [collision (. collisions i)<br>other collision.other]<br>(when other.slope<br>(case (other.slope player)<br>y* (when (>= player.y y*)<br>(world:update player player.x y* player.w player.h) ;;<br>(doto player<br>(tset :y y*)))))))<br>;; ---8<br>))

I had to resort to calling the world:update method, which, as described in the bump&rsquo;s docs, is used to update object&rsquo;s position in the world without checking collisions.<br>This made slopes really awkward to use and work with.<br>And while I suspected that this was a wrong approach, I decided to go with it anyway, because at the time I didn&rsquo;t knew better.

Then, while working on the game for the Spring Lisp Game Jam I again needed a custom collision handling that is not supported by bump.lua directly, but a bit simpler this time around.<br>It&rsquo;s a common thing in games, where you can pass through the block from one side, but not from the other.<br>Like clouds usually behave in platformers - you can cross them from below, but still land on them:

Your browser does not support the video tag.

A cloud platform in the game Rayman (1995)

However, bump.lua by default only supports the following type of collisions: slide, cross, touch, and bounce:

Figure 1: Response types from bump.lua documentation

Solving this using the cross handler seemed wrong, and after a bit of searching I learned that in bump.lua you can define your own collision response functions.<br>So I defined my own collision response called slide-cross (because the main intention was to be able to go through from the sides with the slide response but cross over it from below):

(fn slide-cross [world col x y w h goal-x goal-y filter]<br>(let [goal-x (or goal-x x)]<br>(var goal-y (or goal-y y))<br>(if ((+ y h) col.otherRect.y)<br>(let [tch col.touch<br>move col.move]<br>(when (or (not= move.x 0) (not= move.y 0))<br>(set goal-y tch.y))<br>(set col.slide {:x goal-x :y goal-y})<br>(let [(cols len) (world:project col.item tch.x tch.y w h goal-x goal-y filter)]<br>(values goal-x goal-y cols len)))<br>(let [(cols len) (world:project col.item x y w h goal-x goal-y filter)]<br>(values goal-x goal-y cols len)))))

(world:addResponse :slide-cross slide-cross)

With such colliders in place, I could replicate cloud platform behavior:

Your browser does not support the video tag.

I didn&rsquo;t use clouds themselves in the actual game - instead, this collider was assigned to specific blocks I wanted to be able to run past, acting more as background structures you can still jump on, i.e. pedestals, but the idea of making a custom collider got me thinking.<br>If I can make a custom collision response function, why not try to do it for slopes too?

So that&rsquo;s what I did.<br>But before that, I had to redo slopes a bit.

Slopes in bump.lua

First, let&rsquo;s start with how slopes are defined:

(fn make-slope [x y x0 y0 x1 y1 slope-type]<br>(let [(nx ny) (slope-normal x0 y0 x1 y1 slope-type)]<br>{:slope slope-type<br>:surface #(+ y0 (/ (* (- y1 y0) (- (clamp x0 $ x1) x0)) (- x1 x0)))<br>: x : y : nx : ny<br>:h (m/max y0 y1) :w (m/max x0 x1)}))

For example, to define a slope at coordinates x,y that is 16px long and 8px tall (), we can call make-slope like this:

(make-slope x y 0 8 16 0 :floor)

A 45° 8x8 slope () can be defined as follows:

(make-slope x y 0 8 8 0 :floor)

These slopes get taller from left to right, if we want a slope that goes lower from left to right (), we swap the coordinates:

(make-slope x y 0 0 16 8 :floor)

This function returns an object representing the slope.<br>Of course, we&rsquo;re not limited to defining 16x8 and 8x8 slopes - these are just the most logical choices for a platformer in TIC-80.<br>We can define steeper slopes like 8x16, or use an arbitrary size altogether.<br>As long as it can...

slope player goal collision slopes rsquo

Related Articles