Стрелялка с Pygame №6: анимация спрайтов

Шестая часть проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части. В этом уроке астероиды будут выглядеть интереснее благодаря анимациям.

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

Анимированные астероиды

Все астероиды выглядят одинаково, что не очень-то впечатляет:

Все астероиды выглядят одинаково

Добавить разнообразия и привлекательности можно, заставив их вращаться. Благодаря этому будет создаваться впечатление, что они действительно летят в космосе. Это довольно легко сделать. Так же, как и с функцией pygame.transform.scale(), используемой для изменения размера спрайта Игрока, для вращения нужно задействовать pygame.transform.rotate(). Но чтобы сделать это правильно, нужно ознакомиться с парой нюансов.

В первую очередь необходимо добавить свойства спрайту Mob:

class Mob(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = meteor_img
		self.image.set_colorkey(BLACK)
        self.rect = self.image.get_rect()
        self.radius = int(self.rect.width * .85 / 2)
        self.rect.x = random.randrange(WIDTH - self.rect.width)
        self.rect.y = random.randrange(-150, -100)
        self.speedy = random.randrange(1, 8)
        self.speedx = random.randrange(-3, 3)
        self.rot = 0
        self.rot_speed = random.randrange(-8, 8)
        self.last_update = pygame.time.get_ticks()

Первое свойство rot (сокращенно от «rotation» (вращение)) будет измерять, на сколько градусов должен вращаться астероид. Начальное значение — 0, но оно будет меняться со временем. rot_speed измеряет, на сколько градусов астероид будет поворачиваться каждый раз — чем больше число, тем быстрее будет происходить вращение. Подойдет любое значение: положительное задаст вращение по часовой стрелке, а отрицательное — против.

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

В библиотеке есть объект pygame.time.Clock(), который называется clock (часы). Он позволяет контролировать FPS (количество кадров в секунду). Вызывая pygame.time.get_ticks(), можно узнать, сколько миллисекунд прошло с тех пор, как часы были запущены. Так можно будет сказать, прошло ли достаточно времени, что в очередной раз менять изображение спрайта.

Вращение изображения

Для этой операции потребуется еще несколько строк кода. Используем новый метод self.rotate(), который можно добавить в метод update():

	def update(self):
		self.rotate()

Благодаря этому можно будет добиться того, что в методе не будет лишней информации, а вращение можно убрать, закомментировав всего одну строку. Вот начало метода вращения:

	def rotate(self):
		now = pygame.time.get_ticks()
		if now - self.last_update > 50:
			self.last_update = now
			# вращение спрайтов

Сначала проверяется текущее время, затем вычитается время последнего обновления. Если прошло более 50 миллисекунд, нужно обновлять изображение. Добавляем значение now в last_update и можно делать вращение. Кажется, что осталось лишь применить его к спрайту — как-то так:

self.image = pygame.transform.rotate(self.image, self.rot_speed)

Но в таком случае возникнет проблема:

Вращение изображений астероидов

Вращение разрушительно!

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

Решение состоит в том, чтобы использовать переменную rot для отслеживания общей степени вращения (добавляя rot_speed с каждым обновлением) и вращать оригинальное изображение с таким шагом. Таким образом спрайт каждый раз будет представлять собой чистое изображение, которое повернется всего один раз.

Сначала нужно скопировать оригинальную картинку:

class Mob(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image_orig = random.choice(meteor_images)
        self.image_orig.set_colorkey(BLACK)
        self.image = self.image_orig.copy()
        self.rect = self.image.get_rect()
        self.radius = int(self.rect.width * .85 / 2)
        self.rect.x = random.randrange(WIDTH - self.rect.width)
        self.rect.y = random.randrange(-150, -100)
        self.speedy = random.randrange(1, 8)
        self.speedx = random.randrange(-3, 3)
        self.rot = 0
        self.rot_speed = random.randrange(-8, 8)
        self.last_update = pygame.time.get_ticks()

Затем в методе rotate нужно обновить значение rot и применить вращение к исходному изображению:

    def rotate(self):
        now = pygame.time.get_ticks()
        if now - self.last_update > 50:
            self.last_update = now
            self.rot = (self.rot + self.rot_speed) % 360
            self.image = pygame.transform.rotate(self.image_orig, self.rot)

Стоит отметить, что был использован оператор остатка, %, чтобы значение rot не было больше 360.

Изображения уже выглядят хорошо, но все еще есть одна проблема:

Вращение изображений астероидов

Кажется, что астероиды прыгают, а не плавно вращаются.

Обновление прямоугольника (rect)

После поворота изображения, размер rect может оказаться неправильным. В качестве примера стоит рассмотреть процесс вращения корабля:

Процесс вращения корабля

Здесь видно, что при вращении rect остается одинаковым. Но важно каждый раз вычислять размеры прямоугольника при изменении изображения:

При вращении rect остается одинаковым

Можно увидеть, как меняется прямоугольник при повороте изображения. Для исправления «прыгающего» эффекта нужно убедиться, чтобы центр прямоугольника всегда находится в одном и том же месте, а не привязываться к верхнему левому углу:

Прямоугольник меняется при повороте изображения

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

	def rotate(self):
        now = pygame.time.get_ticks()
        if now - self.last_update > 50:
            self.last_update = now
            self.rot = (self.rot + self.rot_speed) % 360
            new_image = pygame.transform.rotate(self.image_orig, self.rot)
            old_center = self.rect.center
            self.image = new_image
            self.rect = self.image.get_rect()
            self.rect.center = old_center

Случайные размеры астероида

Последнее, что можно сделать, чтобы астероиды выглядели интереснее, — задавать их размеры случайным образом.


Сперва необходимо загрузить их и добавить в список:

meteor_images = []
meteor_list =['meteorBrown_big1.png','meteorBrown_med1.png',
              'meteorBrown_med1.png','meteorBrown_med3.png',
              'meteorBrown_small1.png','meteorBrown_small2.png',
              'meteorBrown_tiny1.png']
for img in meteor_list:
    meteor_images.append(pygame.image.load(path.join(img_dir, img)).convert())

Далее нужно просто каждый раз выбирать случайный астероид для появления в кадре:

class Mob(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image_orig = random.choice(meteor_images)
        self.image_orig.set_colorkey(BLACK)
        self.image = self.image_orig.copy()

Так гораздо лучше!

Случайные астероиды в кадре

Код урока — shmup-6.py

Итого

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

В следующий раз разберем, как вести счет, и узнаем, как выводить текст на экран.