Title: Rotating hit box rects in pygame
Tags: python;rotation;pygame;collision-detection;image-rotation
Question: I'm currently building a top down racer, however I'm trying to get accurate collision detection with static square objects.
What would be the best method for this?
Here is a portion of my code:
class VehicleSprite(Entity):
MAX_FORWARD_SPEED = 18
MAX_REVERSE_SPEED = 1
def __init__(self, images, position):
Entity.__init__(self)
self.src_images = images
self.images = images
self.rect = self.images.get_rect(center=position)
self.position = pygame.math.Vector2(position)
self.velocity = pygame.math.Vector2(0, 0)
self.speed = self.direction = 0
self.k_left = self.k_right = self.k_down = self.k_up = 0
self.width = 28
self.height = 64
self.numImages = 8
self.cImage = 0
def update(self, time):
self.speed += self.k_up + self.k_down
# To clamp the speed.
self.speed = max(-self.MAX_REVERSE_SPEED,
min(self.speed, self.MAX_FORWARD_SPEED))
# Degrees sprite is facing (direction)
self.direction += (self.k_right + self.k_left)
rad = math.radians(self.direction)
self.velocity.x = -self.speed*math.sin(rad)
self.velocity.y = -self.speed*math.cos(rad)
self.position += self.velocity
if (self.cImage &gt;= self.numImages - 1):
self.cImage = 0
else:
self.cImage += 1
self.images = pygame.transform.rotate(self.src_images, self.direction)
self.rect = self.images.get_rect(center=self.position)
def render(self, screen, camera):
screen.blit(self.images, (self.rect.topleft+camera), (self.cImage*self.width, 0, self.width, self.height))
And here is the collision detection bit:
for sprite in all_sprites:
for crate in crates:
crate.update(time)
for crate in crates:
screen.blit(power_img, crate.position+camera)
# Collision with crate
if ((purple_bike.position.x &gt;= crate.x) and purple_bike.position.x < (crate.x + crate.width) or (purple_bike.position.x + purple_bike.width) &gt; crate.x and purple_bike.position.x + purple_bike.width < crate.x + crate.width) and ((purple_bike.position.y &gt; crate.y) and purple_bike.position.y < (crate.y + crate.height) or (purple_bike.position.y + purple_bike.height) &gt; crate.y and (purple_bike.position.y + purple_bike.height) < (crate.y + crate.height)):
print("Hit")
purple_bike.speed = 0
Comment: Sorry! The image rotates fine but I believe the 'hit box' doesn't rotate with it, so when I collide with other objects the collision is off. I just want something that can accurately represent a collision between a rotated image and a static one.

Comment: Ah that makes sense, the collision detection seemed a little inconsistent, I think I'll check Pymunk out, thanks for your help!

Comment: Please describe the issues in detail. The question is rather unclear.

Comment: That problem is not so easy to solve, since `pygame.Rect`s can't be rotated. So every time you rotate an image and then generate a new rect from it the size of the rect changes, because it has to contain the image completely. A very rudimentary solution would be to use a smaller rect, however for a racing game that wouldn't be a good solution. I think a good solution for you would be to use [pixel-perfect masks](http://renesd.blogspot.de/2017/03/pixel-perfect-collision-detection-in.html) to do the collision detection.

Comment: If you eventually want to add physics to your game, you could also use a physics library like [Pymunk](http://pymunk.org/), but that means you'll have to familiarize yourself with it first. I've posted an example that shows how you can use pretty arbitrary Pymunk shapes (combined shapes) [here](https://gamedev.stackexchange.com/a/141064/101019).

Comment: I've created a little racing game prototype with Pymunk in the recent past and can show you a simplified demo. The only real problem that I had was that very fast cars could move through the static segment bodies that I used as walls, so I had to switch to thicker polygon walls instead.

Here is the accepted answer: Here's an example that shows you how to use Pymunk in conjunction with pygame to get rotating hitboxes/collision rects. The green outlines are the edges of the Pymunk shapes and the blue rects are the pygame rects whose sole purpose is to store the blit position of the sprite. It takes some time to familiarize yourself with Pymunk, but it's one of the best ways to implement rotating, arbitrarily shaped hitboxes in pygame.
import sys
import math
import pygame as pg
import pymunk as pm
from pymunk import Vec2d
def flipy(p):
"""Convert chipmunk coordinates to pygame coordinates."""
return Vec2d(p[0], -p[1]+600)
class Player(pg.sprite.Sprite):
def __init__(self, pos, space, mass=0.3):
super().__init__()
self.image = pg.Surface((52, 72), pg.SRCALPHA)
pg.draw.polygon(self.image, pg.Color('steelblue2'),
[(1, 72), (26, 1), (51, 72)])
self.rect = self.image.get_rect(center=pos)
self.orig_image = self.image
# The verts for the Pymunk shape in relation
# to the sprite's center.
vertices = [(0, 36), (26, -36), (-26, -36)]
# Create the physics body and shape of this object.
moment = pm.moment_for_poly(mass, vertices)
self.body = pm.Body(mass, moment)
self.shape = pm.Poly(self.body, vertices, radius=3)
self.shape.friction = .8
self.shape.elasticity = .2
self.body.position = pos
# Add them to the Pymunk space.
self.space = space
self.space.add(self.body, self.shape)
self.accel_forw = False
self.accel_back = False
self.turn_left = False
self.turn_right = False
self.topspeed = 1790
self.angle = 0
def handle_event(self, event):
if event.type == pg.KEYDOWN:
if event.key == pg.K_w:
self.accel_forw = True
if event.key == pg.K_a:
self.turn_left = True
if event.key == pg.K_d:
self.turn_right = True
if event.key == pg.K_s:
self.accel_back = True
if event.type == pg.KEYUP:
if event.key == pg.K_w:
self.accel_forw = False
if event.key == pg.K_a:
self.turn_left = False
if event.key == pg.K_d:
self.turn_right = False
if event.key == pg.K_s:
self.accel_back = False
def update(self, dt):
# Accelerate the pymunk body of this sprite.
if self.accel_forw and self.body.velocity.length < self.topspeed:
self.body.apply_force_at_local_point(Vec2d(0, 624), Vec2d(0, 0))
if self.accel_back and self.body.velocity.length < self.topspeed:
self.body.apply_force_at_local_point(Vec2d(0, -514), Vec2d(0, 0))
if self.turn_left and self.body.velocity.length < self.topspeed:
self.body.angle += .1
self.body.angular_velocity = 0
if self.turn_right and self.body.velocity.length < self.topspeed:
self.body.angle -= .1
self.body.angular_velocity = 0
# Rotate the image of the sprite.
self.angle = self.body.angle
self.rect.center = flipy(self.body.position)
self.image = pg.transform.rotozoom(
self.orig_image, math.degrees(self.body.angle), 1)
self.rect = self.image.get_rect(center=self.rect.center)
class Wall(pg.sprite.Sprite):
def __init__(self, pos, verts, space, mass, *sprite_groups):
super().__init__(*sprite_groups)
# Determine the width and height of the surface.
width = max(v[0] for v in verts)
height = max(v[1] for v in verts)
self.image = pg.Surface((width, height), pg.SRCALPHA)
pg.draw.polygon(self.image, pg.Color('sienna1'), verts)
self.rect = self.image.get_rect(topleft=pos)
moment = pm.moment_for_poly(mass, verts)
self.body = pm.Body(mass, moment, pm.Body.STATIC)
# Need to transform the vertices for the pymunk poly shape,
# so that they fit to the image vertices.
verts2 = [(x, -y) for x, y in verts]
self.shape = pm.Poly(self.body, verts2, radius=2)
self.shape.friction = 1.0
self.shape.elasticity = .52
self.body.position = flipy(pos)
self.space = space
self.space.add(self.shape)
class Game:
def __init__(self):
self.done = False
self.screen = pg.display.set_mode((800, 600))
self.clock = pg.time.Clock()
self.bg_color = pg.Color(60, 60, 60)
self.space = pm.Space()
self.space.gravity = Vec2d(0.0, 0.0)
self.space.damping = .4
self.all_sprites = pg.sprite.Group()
self.player = Player((100, 300), self.space)
self.all_sprites.add(self.player)
# Position-vertices tuples for the walls.
vertices = [
([80, 120], ((0, 0), (100, 0), (70, 100), (0, 100))),
([400, 250], ((20, 40), (100, 0), (80, 80), (10, 100))),
([200, 450], ((20, 40), (300, 0), (300, 120), (10, 100))),
([760, 10], ((0, 0), (30, 0), (30, 420), (0, 400))),
]
for pos, verts in vertices:
Wall(pos, verts, self.space, 1, self.all_sprites)
def run(self):
while not self.done:
self.dt = self.clock.tick(30) / 1000
self.handle_events()
self.run_logic()
self.draw()
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
self.player.handle_event(event)
def run_logic(self):
self.space.step(1/60)
self.all_sprites.update(self.dt)
def draw(self):
self.screen.fill(self.bg_color)
self.all_sprites.draw(self.screen)
# Debug draw - Pymunk shapes are green, pygame rects are blue.
for obj in self.all_sprites:
shape = obj.shape
ps = [flipy(pos.rotated(shape.body.angle) + shape.body.position)
for pos in shape.get_vertices()]
ps.append(ps[0])
pg.draw.rect(self.screen, pg.Color('blue'), obj.rect, 2)
pg.draw.lines(self.screen, (90, 200, 50), False, ps, 2)
pg.display.flip()
if __name__ == '__main__':
pg.init()
Game().run()
pg.quit()
sys.exit()
Comment for this answer: Brilliant thanks skrx that's very concise and helpful, I'll give that a go!

Here is another answer: Since they are sprites, you can check for collisions using pygame.sprite.collide_rect(sprite1, sprite2). If the two sprites are colliding (more specifically, their rect's), then the statement is True. So:
if pygame.sprite.collide_rect(sprite1, sprite2):
print("Hit")
purple_bike.speed = 0
If they are in the same group, you can use pygame.sprite.spritecollide(). Within the docs, its parameters are:
spritecollide(sprite, group, dokill, collided = None)
If you want the sprite to be removed, set the third paarameter (doKill) to True. Otherwise, set it to False.
Comment for this answer: Thanks for your response, this works better than before, but the hit box seems to still be a bit off, I think it may have something to do with the rotation, maybe the hit box of the player doesn't rotate while the image does? I seem to be hitting 'imaginary boxes' above the crate sprite!
Comment for this answer: I have, I think my question has been answered, self.rect apparently doesn't rotate with the sprite image, so the collision is off. I've been advised of a library to check out to create some accurate physics for my scene. Thanks for the help!
Comment for this answer: @Bubblehead333 It could be the rotation but did you assign `self.rect` to your sprites?

{'source': 'StackExchangeDataset', 'subset': 'stackoverflow.com', 'metadata': {'AnswerCount': 2, 'CommentCount': 6, 'ContentLicense': 'CC BY-SA 3.0', 'CreationDate': '2017:00:08/04/17', 'FavoriteCount': None, 'PostTypeId': 1, 'Tags': '<python><rotation><pygame><collision-detection><image-rotation>', 'Title': 'Rotating hit box rects in pygame', 'ViewCount': 921, 'id': 45516072, 'user_AboutMe': '', 'user_AccountId': 8831224.0, 'user_DisplayName': 'Bubblehead333', 'user_Reputation': 55.0, 'user_Views': 4.0, 'user_id': 6597530.0}}