# Non-rectangular collision detection in Python and Pygame

I am programming a ray tracer in python, and I have gotten as far as making a character that can move across a 2d plane and change direction of movement when needed. The problem is this: I want to be able to use an image with transparency as the maze that the player travels through, and use transparent parts as parts that aren't walls, where as every thing else is a wall. The way I am using collision detection is by using the pygame.sprite.spritecollide method, which takes in a Sprite, and a Sprite Group. So I first add the map sprite object to the group, and then in the main while loop, it checks for collision. Here is a small snippet of what that might look like:

map = Map("map1.png") # "Map" class is regular sprite class but with "image" and "rect"
group = pygame.sprite.Group()
# inbetween stuff would be here
# while loop \/
running = True
while running:
# event handling stuff would be here
if pygame.sprite.spritecollide(player, group, False):
print True
# screen drawing routines would be here


As I mentioned, the image has transparency. But nevertheless, the image rect sticks to the non transparency position that it was, so I have to move the player off screen to get the console to stop saying "True".

So what I ask, is there a way that I can check for sprite collision but with transparency?

• Is there a particular reason you need to represent the maze as pixels instead of as a list of edges or some "vector" representation? Outside of Scratch, it's typical to use vector representations of colliders. Jul 13 '20 at 23:06
• Ah, I try to do something like that -- The original idea was actually to rewrite a ray tracer that I had written in scratch but I was to rewrite it in python instead Jul 15 '20 at 17:07
• What do you mean by "Ray Tracer" ? At first I thought ot: en.wikipedia.org/wiki/Ray_tracing_(graphics) Jul 30 '20 at 0:08
• By ray tracer, I dont mean the graphics card thing... I think it is actually meant to be "ray caster", but the source I was looking at originally said "ray tracer"; I realize how confusing that might be Aug 4 '20 at 18:20

This is an interesting question. It's possible to do this with a huge sprite representing the maze, using a sprite mask to ensure only the walls are part of collision. I'm not sure that's a good way to go, but it is relatively simple.

When the sprite is created, you must be sure to create the mask:

self.image = pygame.image.load( maze_image ).convert_alpha()


Then when the collision is checked, you must also ensure it's using the mask. This I must admit, left me a bit confused. I was under the impression that if a sprite has a .mask member it was always used for collisions. Perhaps it's just the function pygame.sprite.spritecollide() that doesn't use it automatically, since it does have the optional 'collided' parameter. /me shrugs.

Anyway, this leads to the collision call:

if ( pygame.sprite.spritecollide( maze, alien_group, False, collided=pygame.sprite.collide_mask ) ):


Where maze is the Maze sprite, and alien_group is a sprite GroupSingle containing only the single Player's alien sprite. The aforementioned extra parameter to force with-mask collisions is last. This collision function returns a list of what's collided with the maze sprite, but given there's only one alien, there's no need to check with what. The list can only be empty, or contain the alien.

The game checks for collision, and if found, changes the window background colour to red. Probably in your code, it needs to block the player, or whatever.

green_alien.png

hexmaze.png (has a transparent background)

Reference Code:

import pygame

# Window size
WINDOW_WIDTH  = 800
WINDOW_HEIGHT = 690
FPS           = 60

# background colours
INKY_BLACK = (   0,  0,  0 )
FIREY_RED  = ( 203, 49,  7 )

class MazeSprite( pygame.sprite.Sprite ):
""" A maze with a transparent background as a *huge* sprite """
def __init__( self, maze_image ):
pygame.sprite.Sprite.__init__( self )
self.rect  = self.image.get_rect( topleft=(0,0) )

class AlienSprite( pygame.sprite.Sprite ):
""" A tiny little alien hoardette """
def __init__( self, alien_image , x=50, y=50 ):
pygame.sprite.Sprite.__init__(self)
self.rect  = self.image.get_rect( center=( x, y ) )

def moveBy( self, dx, dy ):
self.rect.move_ip( dx, dy )
#print("Now at %s" % ( str( self.rect.center ) ) )

### MAIN
pygame.init()
pygame.font.init()
SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF
window  = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), SURFACE )
pygame.display.set_caption("Maze Sprite-Collision Example")

# Make some sprites to hold the Maze background and Player's Alien
maze  = MazeSprite( 'hexmaze.png' )
alien = AlienSprite( "green_alien.png", 283, 155 )

sprite_group = pygame.sprite.Group()        # All sprites for updating and drawing
alien_group = pygame.sprite.GroupSingle()   # Just for the alien collisions

clock = pygame.time.Clock()
done  = False
while not done:

# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True

# Handle continuous-keypresses
keys = pygame.key.get_pressed()
if ( keys[pygame.K_UP] ):
alien.moveBy( 0, -1 )
elif ( keys[pygame.K_DOWN] ):
alien.moveBy( 0, 1 )
elif ( keys[pygame.K_LEFT] ):
alien.moveBy( -1, 0 )
elif ( keys[pygame.K_RIGHT] ):
alien.moveBy( 1, 0 )

# has the alien hit the walls?
background = INKY_BLACK
# NOTE: Ensure we use "mask" collision
#       It's not used (here) automatically
if ( pygame.sprite.spritecollide( maze, alien_group, False, collided=pygame.sprite.collide_mask ) ):
# returned list is not empty
background = FIREY_RED

# Repaint the screen
sprite_group.update()          # re-position the game sprites
window.fill( background )
sprite_group.draw( window )    # draw the game sprites

pygame.display.flip()
clock.tick_busy_loop( FPS )

pygame.quit()