Шестая часть проекта «Стрелялка с 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
остается одинаковым. Но важно каждый раз вычислять размеры прямоугольника при изменении изображения:
Можно увидеть, как меняется прямоугольник при повороте изображения. Для исправления «прыгающего» эффекта нужно убедиться, чтобы центр прямоугольника всегда находится в одном и том же месте, а не привязываться к верхнему левому углу:
Для этого необходимо указать положение центра прямоугольника, вычислить новый размер и сохранить координаты центра в обновленный прямоугольник:
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
(помня об их ограничениях).
В следующий раз разберем, как вести счет, и узнаем, как выводить текст на экран.