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