Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my program became really laggy after I added rotation, and how do I fix this?

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?

like image 339
Omer Avatar asked Apr 16 '19 19:04

Omer


2 Answers

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.

like image 119
user3757614 Avatar answered Nov 15 '22 02:11

user3757614


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()

falling rotating sprite demo

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.

like image 37
Kingsley Avatar answered Nov 15 '22 03:11

Kingsley