I'm making a game using pygame, and I have an asteroid class. When I add rotation to the update method and I run the program, the asteroids move really slow and laggy, and even their images look worse then before.
I am not sure how to fix this and why this is happening. Here is the class:
class enemy(pygame.sprite.Sprite):
def __init__(self, x, y, width, height):
pygame.sprite.Sprite.__init__(self)
self.width = width
self.height = height
self.speedx = random.randrange(-3,3)
self.speedy = random.randrange(5,15)
self.image = random.choice(meteor_image)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.rotation = 0
self.rotation_speed = random.randrange(-8,8)
self.last_update = pygame.time.get_ticks()
def draw(self,win):
win.blit(self.image,(self.rect.x,self.rect.y))
def rotate(self):
time_now = pygame.time.get_ticks()
if time_now - self.last_update > 50:
self.last_update = time_now
self.rotation = (self.rotation + self.rotation_speed) % 360
new_meteor_image = pygame.transform.rotate(self.image, self.rotation)
old_center = self.rect.center
self.image = new_meteor_image
self.rect = self.image.get_rect()
self.rect.center = old_center
def update(self):
self.rotate()
self.rect.y += self.speedy
self.rect.x += self.speedx
Before I added the rotate function and added "self.roatate()" to the update function, it was good, after that, it is all really laggy. How to fix that?
You're taking the original, rotating it, then rotating the rotated image. Don't do that. The rotation process loses information, so you want to rotate from the original, unmodified version each time.
Rotation is also a heavy operation. I suggest creating a cache to store the rotated images, building that at start, then just pulling from that cache when you need to display.
Bitmap rotation is a reasonably computationally heavy operation. Your code is slowing down because it's rotating the image every update, performing this huge bunch of maths every time, for every sprite.
It's possible (and convenient) to pre-rotate the bitmap in your sprite constructor, and simply put the resultant images into a cache. Then instead of performing the rotation calculations, the code need only determine which of the cached images to assign to sprite.image
.
One of the issues with this approach, is that the programmer must decide how many pre-generated rotations to construct. In the example below I used integer angles to set rotation, so this forces a theoretical upper-limit of 360 frames. I can imagine in a vector-like game, a programmer may desire sub-degree rotation, but that's another answer. If you look at historical rotated-bitmap games, generally only a few angles were used, maybe 8 steps (360 / 8 → 45°). Anyway, my example uses 15° angles, giving 24 steps, this seems like a lot! If you are working in an embedded space, or using large bitmaps, the memory used may become a consideration. Obviously if you have many sprites that are the same, they should ideally share the cached images. This is not how this example works.
This example code also does bitmap-mask based collisions (as opposed to simple rectangle collisions), so the bitmap-masks needs to be rotated too.
import pygame
import random
# Window size
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
FPS = 60
# background colours
INKY_BLACK = ( 0, 0, 0)
class MovingSprite( pygame.sprite.Sprite ):
ANGLE_STEP = 15 # degrees, makes 360/ANGLE_STEP frames
def __init__( self, bitmap ):
pygame.sprite.Sprite.__init__( self )
self.rect = bitmap.get_rect()
self.rect.center = ( random.randrange( 0, WINDOW_WIDTH ), random.randrange( 0, WINDOW_HEIGHT ) )
self.crashing = False
# start with zero rotation
self.rotation = 0
self.rotations = [ bitmap ]
self.masks = [ pygame.mask.from_surface( bitmap ) ]
self.angle_slots = 360 // self.ANGLE_STEP
# pre-compute all the rotated images, and bitmap collision masks
for i in range( 1, self.angle_slots ):
rotated_image = pygame.transform.rotate( bitmap, self.ANGLE_STEP * i )
self.rotations.append( rotated_image )
self.masks.append( pygame.mask.from_surface( rotated_image ) )
self._setRotationImage( 0 ) # sets initial image, mask & rect
def rotateTo( self, angle ):
# If the given angle is not an exact point we have, round to nearest
if ( angle % self.ANGLE_STEP != 0 ):
angle = round( angle / self.ANGLE_STEP ) * self.ANGLE_STEP
rot_index = ( angle // self.ANGLE_STEP )
# set the pre-rotated image
self._setRotationImage( rot_index )
def rotateRight( self ):
if ( self.rotation == 0 ):
self._setRotationImage( self.angle_slots - 1 )
else:
self._setRotationImage( self.rotation - 1 )
def rotateLeft( self ):
if ( self.rotation == self.angle_slots - 1 ):
self._setRotationImage( 0 )
else:
self._setRotationImage( self.rotation + 1 )
def _setRotationImage( self, rot_index ):
rot_index %= self.angle_slots
self.rotation = rot_index
# Select the pre-rotated image & mash
self.image = self.rotations[ rot_index ]
self.mask = self.masks[ rot_index ]
# We need to preserve the centre-poisiton of the bitmap,
# as rotated bitmaps will (probably) not be the same size as the original
centerx = self.rect.centerx
centery = self.rect.centery
self.rect = self.image.get_rect()
self.rect.center = ( centerx, centery )
def newPosition( self ):
# Wander Around
if ( not self.crashing ):
self.rect.x += random.randrange( -2, 3 )
self.rect.y += random.randrange( -2, 3 )
else:
self.rect.y += 3
def crash( self ):
self.crashing = True
def update( self ):
self.newPosition()
if ( self.rect.y > WINDOW_HEIGHT ):
self.kill()
elif ( self.crashing == True ):
# rotate as we fall
self.rotateRight()
### MAIN
pygame.init()
pygame.font.init()
SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
WINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), SURFACE )
pygame.display.set_caption("Sprite Rotation Example")
# Load resource images
sprite_image = pygame.image.load( "tiny_alien_space.png" )#.convert_alpha()
# Make some sprites from game-mode
SPRITES = pygame.sprite.Group() # a group, for a single sprite
for i in range( 50 ):
SPRITES.add( MovingSprite( sprite_image ) )
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
elif ( event.type == pygame.KEYDOWN ):
if ( event.unicode == '+' or event.scancode == pygame.K_PLUS ):
# Pressing '+' adds a new sprite
SPRITES.add( MovingSprite( sprite_image ) )
# Handle continuous-keypresses, but only in playing mode
keys = pygame.key.get_pressed()
if ( keys[pygame.K_UP] ):
print("up")
elif ( keys[pygame.K_DOWN] ):
print("down")
elif ( keys[pygame.K_LEFT] ):
print("left")
elif ( keys[pygame.K_RIGHT] ):
print("right")
elif ( keys[pygame.K_ESCAPE] ):
# [Esc] exits too
done = True
# Repaint the screen
SPRITES.update() # re-position the game sprites
WINDOW.fill( INKY_BLACK )
SPRITES.draw( WINDOW ) # draw the game sprites
# Determine collisions - simple rect-based collision first
single_group = pygame.sprite.GroupSingle()
for s in SPRITES:
single_group.sprite = s
collisions = pygame.sprite.groupcollide( single_group, SPRITES, False, False )
# Now double-check collisions with the bitmap-mask to get per-pixel accuracy
for other in collisions[ s ]:
if ( other != s ): # we don't collide with ourselves
# Second step, do more complex collision detection
# using the sprites mask
if ( pygame.sprite.collide_mask( s, other ) ):
#print("Collision")
s.crash( )
other.crash( )
pygame.display.flip()
# Update the window, but not more than 60fps
clock.tick_busy_loop( FPS )
pygame.quit()
NOTE: The framerate of this animated .GIF is much lower than on-screen version, and thus does not reflect the true operation of the example.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With