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’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’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’s docs, is used to update object’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’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’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’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’s what I did.<br>But before that, I had to redo slopes a bit.
Slopes in bump.lua
First, let’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’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...