I’ve designed a simple platformer engine with Pygame that incorporates some (basic) physics and collision detection.
How It Works
The algorithm works by applying x and y velocities to moving objects (like the player) in two discrete steps. This resolves instances in which the player approaches a platform from a diagonal and it becomes unclear which side of the platform should have a collision. The update method below is a member of the class for the Player entity. It handles input and tells the player entity to check for collisions.
def update(self, up, down, left, right, platforms): if up: # only jump if on the ground if self.onGround: self.yvel -= 7 if down: pass if left: self.xvel = -5 if right: self.xvel = 5 if not self.onGround: # only accelerate with gravity if in the air self.yvel += 0.3 # max falling speed if self.yvel > 30: self.yvel = 30 if not(left or right): self.xvel = 0 # increment in x direction self.rect.left += self.xvel # do x-axis collisions self.collide(self.xvel, 0, platforms) # increment in y direction self.rect.top += self.yvel # assuming we're in the air self.onGround = False; # do y-axis collisions self.collide(0, self.yvel, platforms)
The collide method checks for collisions against every other collidable element or platform. This means a downside in breaking collision detection down into separate x- and y-dimension checks is that we’ve essentially halved our performance — we’re checking everything twice.
def collide(self, xvel, yvel, platforms): for p in platforms: if sprite.collide_rect(self, p): if isinstance(p, ExitBlock): event.post(event.Event(QUIT)) if xvel > 0: self.rect.right = p.rect.left if xvel < 0: self.rect.left = p.rect.right if yvel > 0: self.rect.bottom = p.rect.top self.onGround = True self.yvel = 0 if yvel < 0: self.rect.top = p.rect.bottom
We can tell from line 2 in the above code block that I'm lazily checking for collisions against every possible platform. A potential optimization to this affront to Computer Science is to use "static buckets". Buckets work by dividing the game area into a grid. The simplest example would be to use a two by two grid. Upon initializing the level we would divide each of the collidable elements into one of the four grid square or "buckets". Then, when checking for collisions, we only check the bucket that the Player is currently in. Assuming a uniform distribution of platforms, we've reduced our necessary work by about three-quarters!
Such an approach seems promising but it really breaks down when testing collisions for elements that can move from bucket to bucket because we have to continually recalculate bucket membership. There are a few other approaches outlined in this excellent article that I intend on trying with my platforming engine and doing some benchmarks.