Почему моя программа стала сильно тормозить после добавления ротации, и как мне это исправить? - PullRequest
5 голосов
/ 16 апреля 2019

Я делаю игру, используя 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 ]

3 голосов
/ 17 апреля 2019

Поворот растрового изображения - это достаточно сложная вычислительная операция. Ваш код замедляется, потому что он поворачивает изображение при каждом обновлении, каждый раз выполняя эту огромную математику для каждого спрайта.

Можно (и удобно) предварительно повернуть растровое изображение в конструкторе спрайтов и просто поместить полученные изображения в кэш. Затем вместо выполнения вычислений поворота код должен только определить, какое из кэшированных изображений назначить 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()

falling rotating sprite demo

ПРИМЕЧАНИЕ. Частота кадров этого анимированного .GIF намного ниже, чем у экранной версии, и, следовательно, не отражает истинную работу примера.

3 голосов
/ 16 апреля 2019

Вы берете оригинал, поворачиваете его, а затем поворачиваете повернутое изображение.Не делай этого.Процесс поворота теряет информацию, поэтому вы хотите каждый раз поворачивать исходную неизмененную версию.

Поворот также является тяжелой операцией.Я предлагаю создать кэш для хранения повернутых изображений, создать его при запуске, а затем просто извлечь из этого кеша, когда вам нужно отобразить.

...