5

我正在使用 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()”之前,它很好,之后,它真的很卡。如何解决?

4

2 回答 2

3

位图旋转是一项计算量相当大的操作。你的代码变慢了,因为它每次更新都会旋转图像,每次都为每个精灵执行大量的数学运算。

在 sprite 构造函数中预先旋转位图是可能的(也很方便),然后简单地将生成的图像放入缓存中。然后,代码无需执行旋转计算,只需确定将哪些缓存图像分配给sprite.image.

这种方法的问题之一是程序员必须决定要构建多少预先生成的旋转。在下面的示例中,我使用整数角度来设置旋转,因此这强制了 360 帧的理论上限。我可以想象在类似矢量的游戏中,程序员可能需要亚度旋转,但这是另一个答案。如果您查看历史旋转位图游戏,通常只使用几个角度,可能是 8 步(360 / 8 → 45°)。无论如何,我的示例使用 15° 角,提供 24 步,这似乎很多!如果您在嵌入式空间中工作,或使用大型位图,则使用的内存可能会成为考虑因素。显然,如果您有许多相同的精灵,理想情况下它们应该共享缓存的图像。这不是本示例的工作方式。

此示例代码还执行基于位图掩码的碰撞(与简单的矩形碰撞相反),因此位图掩码也需要旋转。

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

下降旋转精灵演示

注意:此动画 .GIF 的帧率远低于屏幕版本,因此不能反映示例的真实操作。

于 2019-04-17T00:44:12.353 回答
3

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

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

于 2019-04-16T19:14:26.847 回答