为什么添加轮播后我的程序真的变得很慢,我该如何解决?

时间:2019-04-16 19:03:13

标签: python python-2.7 pygame pygame-surface pygame-clock

我正在使用pygame制作游戏,并且有一个小行星类。当我在更新方法中添加旋转并运行程序时,小行星的移动速度非常缓慢且缓慢,甚至其图像看上去也比以前更差。

我不确定该如何解决以及为什么会这样。这是课程:

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

在我添加旋转功能并将“ self.roatate()”添加到更新功能之前,这很好,在那之后,这一切真的很滞后。该如何解决?

2 个答案:

答案 0 :(得分:3)

您要获取原件,旋转它,然后旋转旋转的图像。不要那样做轮换过程会丢失信息,因此您希望每次都从原始的未修改版本轮换。

旋转也是一项繁重的工作。我建议创建一个缓存来存储旋转的图像,在开始时进行构建,然后在需要显示时从该缓存中拉出。

答案 1 :(得分:3)

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.