Играли ли вы когда-нибудь в игры, где проверяется скорость набора текста? Это очень полезное развлечение, поскольку оно позволяет не только отслеживать значение, но и улучшает навык со временем. Если заинтересовались, то ее вполне можно создать самостоятельно.

В этом проекте создадим приложение на Python, с помощью которого можно будет проверять и увеличивать собственную скорость набора текста. Для графического интерфейса используем библиотеку pygame, которая для этих целей и предназначена. Также нарисуем изображения, которые будут отображаться на экране.
Для работы над проектом требуются базовые знания программирования на Python и библиотеки pygame.
Для установки библиотеки используйте в терминале следующую команду.
pip install pygame
По этой ссылке можно загрузить весь исходный код проекта: typing-speed-game.rar
Для начала разберемся с файловой структурой проекта:
Создадим файл sentences.txt, в котором на каждой строке добавим по предложению.
При создании проекта будем использовать принципы объектно-ориентированного программирования.
Для этого проекта будем использовать библиотеку pygame. Ее нужно импортировать вместе с другими встроенными модулями Python, такими как time и random.
import pygame
from pygame.locals import *
import sys
import time
import random
Дальше создаем класс игры, который будет содержать функции для старта, сброса и несколько дополнительных, отвечающих за вычисления.
Создадим конструктор класса, в котором будет определены все переменные проекта.
class Game:
def __init__(self):
self.w=750
self.h=500
self.reset=True
self.active = False
self.input_text=''
self.word = ''
self.time_start = 0
self.total_time = 0
self.accuracy = '0%'
self.results = 'Time:0 Accuracy:0 % Wpm:0 '
self.wpm = 0
self.end = False
self.HEAD_C = (255,213,102)
self.TEXT_C = (240,240,240)
self.RESULT_C = (255,70,70)
pygame.init()
self.open_img = pygame.image.load('type-speed-open.png')
self.open_img = pygame.transform.scale(self.open_img, (self.w,self.h))
self.bg = pygame.image.load('background.jpg')
self.bg = pygame.transform.scale(self.bg, (500,750))
self.screen = pygame.display.set_mode((self.w,self.h))
pygame.display.set_caption('Type Speed test')
В этом конструкторе инициализируем ширину и высоту окна, а также переменные, которые нужны для вычислений. Наконец, инициализируем саму pygame и загрузим изображения. Самое важное — переменная экрана, на котором будет выводиться интерфейс.
Метод draw_text() — это вспомогательная функция для класса Game, которая выводит текст на экран. В качестве аргумента она принимает экран, выводимое сообщение, координату y экрана, где нужно нарисовать текст, а также размер и цвет шрифта. В этом случае все должно выводиться по центру. После прорисовки pygame нужно обновить экран.
def draw_text(self, screen, msg, y ,fsize, color):
font = pygame.font.Font(None, fsize)
text = font.render(msg, 1,color)
text_rect = text.get_rect(center=(self.w/2, y))
screen.blit(text, text_rect)
pygame.display.update()
В файле sentences.txt хранится список предложений. Метод get_sentence() будет открывать его и возвращать случайное предложение из списка. Целая строка будет разбиваться с помощью символа новой строки.
def get_sentence(self):
f = open('sentences.txt').read()
sentences = f.split('\n')
sentence = random.choice(sentences)
return sentence
В методе show_results() рассчитывается скорость набора. Таймер запускается в тот момент, когда пользователь нажимает на поле ввода, а останавливается в момент нажатия Enter. Затем рассчитывается разница и определяется время в секундах.
Для вычисления точности используется математика. Подсчитываются правильно введенные символы за счет сравнения введенного текста с тем, который нужно было ввести.
Формула следующая:
(правильные символы) х 100 / (всего символов в предложении)
WPM (words per minute) — это количество слов в минуту. Типичное слово состоит приблизительно из 5 символов, поэтому рассчитываем показатель, разделяя общее количество слов на 5. Затем результат делится на общее время, которое потребовалось для набора. Поскольку общее время указано в секундах, его сначала нужно конвертировать в минуты, разделив значение на 60.
Наконец, в нижней части есть иконка, выступающая кнопкой сброса. Когда пользователь нажимает на нее, игра сбрасывается. Речь о методе reset_game() пойдет дальше.
def show_results(self, screen):
if(not self.end):
# Расчет времени
self.total_time = time.time() - self.time_start
# Расчет точности
count = 0
for i,c in enumerate(self.word):
try:
if self.input_text[i] == c:
count += 1
except:
pass
self.accuracy = count/len(self.word)*100
# Расчет количества слов в минуту
self.wpm = len(self.input_text)*60/(5*self.total_time)
self.end = True
print(self.total_time)
self.results = 'Time:'+str(round(self.total_time)) +" secs Accuracy:"+ str(round(self.accuracy)) + "%" + ' Wpm: ' + str(round(self.wpm))
# Загрузка иконки
self.time_img = pygame.image.load('icon.png')
self.time_img = pygame.transform.scale(self.time_img, (150,150))
# screen.blit(self.time_img, (80,320))
screen.blit(self.time_img, (self.w/2-75,self.h-140))
self.draw_text(screen,"Reset", self.h - 70, 26, (100,100,100))
print(self.results)
pygame.display.update()
Это основной метод класса, отвечающий за обработку всех событий. Метод reset_game() вызывается в начале метода, который сбрасывает все переменные. Затем выполняется бесконечный цикл, который захватывает все события мыши и клавиатуры. После этого на экране рисуются заголовок и поле ввода.
Другой цикл ждет событий мыши и клавиатуры. Когда кнопка мыши нажимается, проверяется ее положение. Если она над полем ввода, то таймер запускается, а переменная active становится True. Если над кнопкой сброса — игра сбрасывается.
Когда значение active равняется True, а набор текста не завершен, ожидаются события с клавиатуры. Если пользователь нажимает любую клавишу, то нужно обновить сообщение поля ввода. Клавиша Enter завершает ввод, после чего происходят вычисления. Еще одно событие — для клавиши Backspace, которая удаляет последний символ введенного текста.
def run(self):
self.reset_game()
self.running=True
while(self.running):
clock = pygame.time.Clock()
self.screen.fill((0,0,0), (50,250,650,50))
pygame.draw.rect(self.screen,self.HEAD_C, (50,250,650,50), 2)
# Обновление текста пользовательского ввода
self.draw_text(self.screen, self.input_text, 274, 26,(250,250,250))
pygame.display.update()
for event in pygame.event.get():
if event.type == QUIT:
self.running = False
sys.exit()
elif event.type == pygame.MOUSEBUTTONUP:
x,y = pygame.mouse.get_pos()
# Расположение окна ввода
if(x>=50 and x<=650 and y>=250 and y<=300):
self.active = True
self.input_text = ''
self.time_start = time.time()
# Расположение кнопки сброса
if(x>=310 and x<=510 and y>=390 and self.end):
self.reset_game()
x,y = pygame.mouse.get_pos()
elif event.type == pygame.KEYDOWN:
if self.active and not self.end:
if event.key == pygame.K_RETURN:
print(self.input_text)
self.show_results(self.screen)
print(self.results)
self.draw_text(self.screen, self.results,350, 28, self.RESULT_C)
self.end = True
elif event.key == pygame.K_BACKSPACE:
self.input_text = self.input_text[:-1]
else:
try:
self.input_text += event.unicode
except:
pass
pygame.display.update()
clock.tick(60)
Метод reset_game() сбрасывает все переменные, так что проверить скорость набора можно снова. Еще раз выбирается случайное предложение с помощью метода get_sentence(). В конце определение класса закрывается. Создаем объект класса Game и запускаем программу.
def reset_game(self):
self.screen.blit(self.open_img, (0,0))
pygame.display.update()
time.sleep(1)
self.reset=False
self.end = False
self.input_text=''
self.word = ''
self.time_start = 0
self.total_time = 0
self.wpm = 0
# Получаем случайное предложение
self.word = self.get_sentence()
if (not self.word): self.reset_game()
# Загрузка окна
self.screen.fill((0,0,0))
self.screen.blit(self.bg,(0,0))
msg = "Typing Speed Test"
self.draw_text(self.screen, msg,80, 80,self.HEAD_C)
# Отрисовка поля ввода
pygame.draw.rect(self.screen,(255,192,25), (50,250,650,50), 2)
# Отрисовка строки предложения
self.draw_text(self.screen, self.word,200, 28,self.TEXT_C)
pygame.display.update()
Game().run()
Вывод:


В этом проекте мы создали игру на Python с использованием pygame, которая отслеживает скорость набора текста пользователем.
]]>Четырнадцатая проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части. В этот раз закончим игру с помощью экрана «Игра закончена» и добавим возможность начать сначала.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
Сейчас, если у игрока заканчиваются жизни, программа просто резко обрывается. Выглядит не очень приятно, поэтому добавим экран «Игра закончена» и дадим возможность игрокам начать сначала, если они хотят.
Причина остановки программы в том, что игровой цикл управляется переменной running (значение которой может быть только True или False), и в момент смерти игрока он становится False. Вместо этого сделаем так, чтобы отслеживалось состояние игры (state). Оно может быть следующим: демонстрация экрана с законченной игрой или непосредственно игра. Создадим в начале переменную game_over:
# Цикл игры
game_over = True
running = True
while running:
if game_over:
show_go_screen()
Дальше нужно будет создать и show_go_screen, но сперва необходимо подумать еще кое о чем. Когда игра заканчивается и переходит к соответствующему экрану, а игрок выбирает начать сначала, нужно сбросить все: очки, астероиды, жизни игрока и так далее. Сейчас эти элементы настраиваются перед началом игрового цикла, но их нужно переместить в позицию после show_go_screen(), так, чтобы они происходили, когда игрок уже покидает этот экран.
# Цикл игры
game_over = True
running = True
while running:
if game_over:
show_go_screen()
game_over = False
all_sprites = pygame.sprite.Group()
mobs = pygame.sprite.Group()
bullets = pygame.sprite.Group()
powerups = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
for i in range(8):
newmob()
score = 0
Также необходимо переключить значение game_over на False при старте новой игры. Теперь можно менять и то, что происходит, когда у игрока заканчиваются жизни.
# Если игрок умер, игра окончена
if player.lives == 0 and not death_explosion.alive():
game_over = True
Осталось определить, что будет делать экран show_go_screen. Поскольку в этом примере достаточно будет лишь одного «экрана», ограничимся названием игры и инструкциями для игроков:
def show_go_screen():
screen.blit(background, background_rect)
draw_text(screen, "SHMUP!", 64, WIDTH / 2, HEIGHT / 4)
draw_text(screen, "Arrow keys move, Space to fire", 22,
WIDTH / 2, HEIGHT / 2)
draw_text(screen, "Press a key to begin", 18, WIDTH / 2, HEIGHT * 3 / 4)
pygame.display.flip()
waiting = True
while waiting:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYUP:
waiting = False
Игра будет просить «Нажать клавишу для старта». Это поведение нужно обрабатывать с помощью цикла while. Его можно воспринимать как миниатюрный игровой цикл, который проверяет всего два события. Первое — pygame.QUIT, которое происходит при нажатии на крестик окна программы. Второе — нажатие клавиши для старта. Важно отметить, что pygame.KEYDOWN здесь не используется.
Вместо этого указана KEYUP. Это нужно для того, чтобы игра не начиналась до тех пор, пока игрок не отпустит клавишу.
Это простейший способ сделать игровой экран. Есть масса других вариантов, но об этом — в следующих уроках.
Код урока — shmup-14.py
Вот и все, ваша первая игра на Pygame готова. Если вы следовали инструкциям и выполняли все задания, то это неплохой старт для начинающего программиста.
При этом есть еще много вещей, которые можно было бы добавить в игру:
И это только самые очевидные идеи.
Спасибо за то, что читали эти уроки и выполняли задания.
]]>Тринадцатая проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части. В этот раз добавим в игру улучшения, которые будут время от времени появляться на экране.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
В прошлой части был создан спрайт улучшения, который появляется на месте уничтоженного астероида с определенной долей вероятности. Улучшение «здоровье» уже готово, а вот улучшению «оружие» нужно уделить еще немного внимания.
Есть множество параметров, которые можно улучшить в оружии: скорость стрельбы, урон (хотя сейчас астероиды и так уничтожаются с одного попадания), тип оружия и так далее. В этой части поработаем над улучшением, увеличивающим темп стрельбы. Но можно оставить некую свободу действий, чтобы легко настраивать его в будущем. Для этого необходимо создать новый метод для Player, который будет вызываться, когда игрок подбирает улучшение «оружия»:
if hit.type == 'gun':
player.powerup()
Сперва нужно добавить новые свойства в спрайт Player: power будет отслеживать «уровень силы» (увеличивающийся с каждым выстрелом), а power_time — снижать его спустя некоторое время:
self.power = 1
self.power_time = pygame.time.get_ticks()
В методе powerup будут следующие свойства:
def powerup(self):
self.power += 1
self.power_time = pygame.time.get_ticks()
Теперь можно изменить метод shoot, чтобы игрок выстреливал двумя пулями, если значение power выше 1. Появляться они будут на кончиках крыльев:
def shoot(self):
now = pygame.time.get_ticks()
if now - self.last_shot > self.shoot_delay:
self.last_shot = now
if self.power == 1:
bullet = Bullet(self.rect.centerx, self.rect.top)
all_sprites.add(bullet)
bullets.add(bullet)
shoot_sound.play()
if self.power >= 2:
bullet1 = Bullet(self.rect.left, self.rect.centery)
bullet2 = Bullet(self.rect.right, self.rect.centery)
all_sprites.add(bullet1)
all_sprites.add(bullet2)
bullets.add(bullet1)
bullets.add(bullet2)
shoot_sound.play()
Теперь необходимо проверять время с помощью power_time. Его необходимо добавить в update игрока:
# тайм-аут для бонусов
if self.power >= 2 and pygame.time.get_ticks() - self.power_time > POWERUP_TIME:
self.power -= 1
self.power_time = pygame.time.get_ticks()
Важно не забыть установить значение 5000 (5 секунд) у POWERUP_ TIME сверху.
Чтобы закончить с функцей улучшений, нужно добавить приятные звуки в те моменты, когда игрок их подбирает. Это вы можете сделать самостоятельно. Используйте «bgxr», чтобы найти звуки, которые подойдут для игры. Назвать их можно как-то так: shield_sound и power_sound. Например:
shield_sound = pygame.mixer.Sound(path.join(snd_dir, 'pow4.wav'))
power_sound = pygame.mixer.Sound(path.join(snd_dir, 'pow5.wav'))
После этого нужно просто запускать их при столкновении игрока с соответствующим улучшением. Код для реализации этой функции есть ниже, но попробуйте реализовать его самостоятельно.
Код урока — shmup-13.py
Следующий урок будет последним. В нем создадим экран «Игра закончена».
]]>Двенадцатая проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части. В этот раз добавим в игру бонусы, которые будут появляться случайным образом.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
В нашем шутере уже много чего, но кое-чего важного не хватает — возможности для игрока делать свой корабль сильнее. Можно придумать массу улучшений, но начнем с двух:
Для начала нужно добавить еще один спрайт, который будет представлять собой объект улучшения. Чтобы упростить этот процесс, можно скопировать класс Bullet() и добавить пару изменений. Это сработает, потому что их поведение очень похоже: появиться в определенном месте (месте астероида, который был уничтожен) и двигаться вниз, а затем — убрать спрайт, если он уйдет за пределы экрана. При отображении улучшения будем случайным образом выбирать между «здоровьем» и «оружием».
class Pow(pygame.sprite.Sprite):
def __init__(self, center):
pygame.sprite.Sprite.__init__(self)
self.type = random.choice(['shield', 'gun'])
self.image = powerup_images[self.type]
self.image.set_colorkey(BLACK)
self.rect = self.image.get_rect()
self.rect.center = center
self.speedy = 2
def update(self):
self.rect.y += self.speedy
# убить, если он сдвинется с нижней части экрана
if self.rect.top > HEIGHT:
self.kill()
Нужно загрузить изображения в раздел с уже имеющимися. Это делается с помощью словаря, который содержит картинки улучшений в виде ключей.
powerup_images = {}
powerup_images['shield'] = pygame.image.load(path.join(img_dir, 'shield_gold.png')).convert()
powerup_images['gun'] = pygame.image.load(path.join(img_dir, 'bolt_gold.png')).convert()
Такие изображения будут использоваться в игре (можно нажать для скачивания):
![]()
![]()
Для создания спрайтов необходима единая группа (которая будет обрабатывать столкновения):
powerups = pygame.sprite.Group()
Затем, когда пуля уничтожает астероид, необходим случайный (маленький) шанс на выпадение улучшения:
# проверьте, не попала ли пуля в моб
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
score += 50 - hit.radius
random.choice(expl_sounds).play()
expl = Explosion(hit.rect.center, 'lg')
all_sprites.add(expl)
if random.random() > 0.9:
pow = Pow(hit.rect.center)
all_sprites.add(pow)
powerups.add(pow)
newmob()
random.random() выберет случайное десятичное число от 0 до 1, а улучшение будет появляться только в том случае, если значение окажется выше 0.9. То есть, шанс всего 10%.
Теперь нужна еще одна проверка столкновений. На этот раз — между игроком и группой улучшений. Ее стоит добавить после уже имеющихся:
# Проверка столкновений игрока и улучшения
hits = pygame.sprite.spritecollide(player, powerups, True)
for hit in hits:
if hit.type == 'shield':
player.shield += random.randrange(10, 30)
if player.shield >= 100:
player.shield = 100
if hit.type == 'gun':
pass
Сначала обрабатывается улучшение «здоровья», которое возвращает игроку случайное значение здоровья. С «оружием» все несколько сложнее, поэтому ему будет посвящен следующий материал.
Код урока — shmup-12.py
]]>Одиннадцатая проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части.
В этот раз у игрока появятся дополнительные жизни и анимация взрыва при смерти.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
В случае смерти игрока будет использоваться другая анимация из набора Kenny Game Art.
Скачать архив изображений можно по этой ссылке.
После этого их нужно загрузить в виде кадров, как это было с прошлыми взрывами. Добавим еще один тип взрыва 'player' и загрузим его в тот же цикл, поскольку у этой анимации столько же кадров, сколько было у предыдущей. Теперь код загрузки выглядит следующим образом:
explosion_anim = {}
explosion_anim['lg'] = []
explosion_anim['sm'] = []
explosion_anim['player'] = []
for i in range(9):
filename = 'regularExplosion0{}.png'.format(i)
img = pygame.image.load(path.join(img_dir, filename)).convert()
img.set_colorkey(BLACK)
img_lg = pygame.transform.scale(img, (75, 75))
explosion_anim['lg'].append(img_lg)
img_sm = pygame.transform.scale(img, (32, 32))
explosion_anim['sm'].append(img_sm)
filename = 'sonicExplosion0{}.png'.format(i)
img = pygame.image.load(path.join(img_dir, filename)).convert()
img.set_colorkey(BLACK)
explosion_anim['player'].append(img)
В спрайте класса Explosion не нужно ничего менять — достаточно просто сделать взрыв игрока, когда его здоровье падает до нуля. Этот пункт можно добавить в игру там же, где проверяются столкновения игрока с астероидами:
# Проверка, не ударил ли моб игрока
hits = pygame.sprite.spritecollide(player, mobs, True, pygame.sprite.collide_circle)
for hit in hits:
player.shield -= hit.radius * 2
expl = Explosion(hit.rect.center, 'sm')
all_sprites.add(expl)
newmob()
if player.shield <= 0:
death_explosion = Explosion(player.rect.center, 'player')
all_sprites.add(death_explosion)
running = False
Но если запустить программу сейчас, то будет одна проблема: после смерти игрока значение running становится False, игра заканчивается, и нет возможности увидеть анимацию.
Чтобы это исправить, нужно не заканчивать игру до тех пор, пока не завершится анимация взрыва. Так, игрока нужно удалять, но не менять значение running до завершения анимации взрыва:
if player.shield <= 0:
death_explosion = Explosion(player.rect.center, 'player')
all_sprites.add(death_explosion)
player.kill()
# Если игрок умер, игра окончена
if not player.alive() and not death_explosion.alive():
running = False
Функция alive() просто сообщает, является ли конкретный спрайт живым. Поскольку взрыв был убит (функцией kill()), после завершения анимации игра заканчивается.
Теперь нужно добавить несколько жизней игроку. Их можно отслеживать с помощью переменной, но также необходимо выводить их на экран. Вместо чисел также можно использовать миниатюрные изображения корабля игрока. Для начала нужно создать уменьшенное изображение:
player_img = pygame.image.load(path.join(img_dir, "playerShip1_orange.png")).convert()
player_mini_img = pygame.transform.scale(player_img, (25, 19))
player_mini_img.set_colorkey(BLACK)
Дальше — добавить несколько параметров в __init__() класса Player: счетчик жизней, флажок (переменная, которая может принимать значения True или False), чтобы показывать/скрывать игрока и таймер для определения того, сколько времени игрока нужно скрывать.
self.lives = 3
self.hidden = False
self.hide_timer = pygame.time.get_ticks()
Теперь, в случае смерти игрока, вместо использования kill() игрок будет скрываться, а от lives будет отниматься 1. Также на следующей жизни сбрасывается здоровье:
if player.shield <= 0:
death_explosion = Explosion(player.rect.center, 'player')
all_sprites.add(death_explosion)
player.hide()
player.lives -= 1
player.shield = 100
# Если игрок умер, игра окончена
if player.lives == 0 and not death_explosion.alive():
running = False
Дальше нужно проработать, как работает скрытие игрока. В классе Player() необходимо добавить соответствующий метод, который будет переключать флажок hidden на значение True и запускать таймер. Также нужно удостовериться в том, что пока игрок скрыт, он не может столкнуться с астероидом. Есть несколько вариантов, как этого можно добиться, но в самом простом не нужно добавлять/удалять его из групп или делать что-то подобное. Достаточно убрать игрока с нижней части экрана на короткое время:
def hide(self):
# временно скрыть игрока
self.hidden = True
self.hide_timer = pygame.time.get_ticks()
self.rect.center = (WIDTH / 2, HEIGHT + 200)
А в методе update() необходимо снова вернуть игрока, если прошло достаточно времени (начать можно с 1 секунды):
def update(self):
# показать, если скрыто
if self.hidden and pygame.time.get_ticks() - self.hide_timer > 1000:
self.hidden = False
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT - 10
Для отображения жизней нужно создать функцию, похожую на draw_shield_bar(), которая позволит размещать счетчик в конкретном месте:
def draw_lives(surf, x, y, lives, img):
for i in range(lives):
img_rect = img.get_rect()
img_rect.x = x + 30 * i
img_rect.y = y
surf.blit(img, img_rect)
Дальше необходим цикл с количеством, соответствующем количеству жизней и пространством в 30 пикселей для каждого изображения (ширина player_mini_img — 25 пикселей, так что между ними как раз останется достаточно места).
Далее нужно добавить вызов функции в раздел отрисовки игрового цикла:
draw_lives(screen, WIDTH - 100, 5, player.lives,
player_mini_img)
А вот и финальный результат:

Код урока — shmup-11.py
В следующем уровне добавим в игру улучшения.
]]>Десятаячасть проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части. В этот раз добавим анимацию для взрывов астероидов.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
В первую очередь можно поменять сам принцип стрельбы. Сейчас для выстрелов нужно постоянно нажимать на пробел. Таким образом легко можно даже сломать клавишу. Но вместо этого можно сделать так, чтобы огонь велся до тех пор, пока кнопка нажата.
Для этого игроку нужно добавить несколько свойств:
self.shoot_delay = 250
self.last_shot = pygame.time.get_ticks()
shmup_delay будет измерять, сколько времени должно пройти между появлением пуль (в миллисекундах). last_shot — отслеживать, сколько прошло с момента последней пули так, чтобы в нужный момент выстрелить снова.
Теперь нужно добавить кнопку огня к проверке нажатий на клавиатуре. Она находится в функции обновления игрока:
def update(self):
self.speedx = 0
keystate = pygame.key.get_pressed()
if keystate[pygame.K_LEFT]:
self.speedx = -8
if keystate[pygame.K_RIGHT]:
self.speedx = 8
if keystate[pygame.K_SPACE]:
self.shoot()
Всю механику стрельбы также можно перенести в отдельный метод:
def shoot(self):
now = pygame.time.get_ticks()
if now - self.last_shot > self.shoot_delay:
self.last_shot = now
bullet = Bullet(self.rect.centerx, self.rect.top)
all_sprites.add(bullet)
bullets.add(bullet)
Теперь, если пробел нажат, игра будет проверять, сколько времени прошло после вылета последней пули. Если прошло shoot_delay миллисекунд, тогда вылетит следующая пуля, а параметр last_shot обновится. В конце концов, можно удалить следующие строки из игрового цикла.
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.shoot()
Следующий шаг — сделать взрывы более привлекательными в визуальном плане. Этого можно добиться, заставив астероиды по-настоящему взрываться, а не просто пропадать с экрана. Для этого нужен набор кадров анимации, которые будут воспроизводиться на месте уничтожения астероида. Вот эта последовательность:

Нажмите здесь, чтобы загрузить архив с этими изображениями.
Сначала нужно загрузить графику в игру и добавить ее в список. Как и в случае со спрайтом игрока, необходимо поменять размер этих изображений, сделав две версии. Крупная будет воспроизводиться в месте подрыва астероида игроком, а маленькая — там, где астероид врезается в корабль игрока. Для этого используется словарь explosion_anim c двумя списками lg и sm. Поскольку все файлы называются одинаково (с номерами от 0 до 8), можно использовать цикл для их загрузки, изменения размера и добавления в списки:
explosion_anim = {}
explosion_anim['lg'] = []
explosion_anim['sm'] = []
for i in range(9):
filename = 'regularExplosion0{}.png'.format(i)
img = pygame.image.load(path.join(img_dir, filename)).convert()
img.set_colorkey(BLACK)
img_lg = pygame.transform.scale(img, (75, 75))
explosion_anim['lg'].append(img_lg)
img_sm = pygame.transform.scale(img, (32, 32))
explosion_anim['sm'].append(img_sm)
Дальше требуется создать спрайт, представляющий собой взрыв на экране. Изображение спрайта будет быстро меняться, переходя от одного изображения к другому в наборе. Добравшись до последнего, спрайт исчезнет.
При его появлении спрайту нужно указать, где появляться (местоположение астероида) и какого быть размера. Как и в случае с функцией авто-огня, здесь можно использовать параметр frame_rate, который будет контролировать скорость воспроизведения анимации — если менять изображение с каждым обновлением игрового цикла (1/60 секунды), то взрыв произойдет очень быстро (1/10 секунды). Поэтому вот код спрайта Explosion:
class Explosion(pygame.sprite.Sprite):
def __init__(self, center, size):
pygame.sprite.Sprite.__init__(self)
self.size = size
self.image = explosion_anim[self.size][0]
self.rect = self.image.get_rect()
self.rect.center = center
self.frame = 0
self.last_update = pygame.time.get_ticks()
self.frame_rate = 50
def update(self):
now = pygame.time.get_ticks()
if now - self.last_update > self.frame_rate:
self.last_update = now
self.frame += 1
if self.frame == len(explosion_anim[self.size]):
self.kill()
else:
center = self.rect.center
self.image = explosion_anim[self.size][self.frame]
self.rect = self.image.get_rect()
self.rect.center = center
Теперь нужно сделать так, чтобы этот спрайт появлялся при уничтожении астероида:
# проверьте, не попала ли пуля в моб
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
score += 50 - hit.radius
random.choice(expl_sounds).play()
expl = Explosion(hit.rect.center, 'lg')
all_sprites.add(expl)
newmob()
и при попадании по игроку:
# Проверка, не ударил ли моб игрока
hits = pygame.sprite.spritecollide(player, mobs, True, pygame.sprite.collide_circle)
for hit in hits:
player.shield -= hit.radius * 2
expl = Explosion(hit.rect.center, 'sm')
all_sprites.add(expl)
newmob()
if player.shield <= 0:
running = False
А вот и финальный результат:

Код урока — shmup-10.py
В следующей части игра станет дольше благодаря тому, что у игрока появятся жизни.
]]>Девятая часть проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части. В этот раз дадим игроку здоровье и добавим полоску, которая будет показывать его уровень.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
Пока что игрока уничтожает одно попадание астероида. Так играть не очень интересно, поэтому добавим свойство shield, которое будет всего лишь числом от 0 до 100.
class Player(pygame.sprite.Sprite):
def __init__(self):
self.speedx = 0
self.shield = 100
Теперь, каждый раз, когда астероид будет попадать в игрока, можно отнимать определенный уровень здоровья. Когда уровень упадет до 0, игра заканчивается. Чтобы было интереснее, можно сделать, чтобы крупные астероиды наносили больше урона. Для этого стоит использовать свойство radius астероида.
Пока что столкновение моб-против-игрока обрабатывается на простейшем уровне:
# Проверка, не ударил ли моб игрока
hits = pygame.sprite.spritecollide(player, mobs, False, pygame.sprite.collide_circle)
if hits:
running = False
Нужно добавить несколько изменений:
# Проверка, не ударил ли моб игрока
hits = pygame.sprite.spritecollide(player, mobs, True, pygame.sprite.collide_circle)
for hit in hits:
player.shield -= hit.radius * 2
if player.shield <= 0:
running = False
В spritecollide изменим значение с False на True, потому что нужно, чтобы астероид исчезал после попадания. Если этого не сделать, то по мере продвижения он будет снова сталкиваться с игроком в следующих кадрах. Возможно и то, что сразу несколько астероидов ударят по игроку, поэтому значений hit должно быть несколько. Необходимо перебирать hits и отнимать определенное количество уровня здоровья, основываясь на радиусе астероида. В конце концов, когда значение упадет до 0, игра закончится.
Вы могли заметить, что при удалении астероида, столкнувшегося с игроком, уменьшается их общее количество. Чтобы не изменять его, необходимо создавать новый при удалении. Мобы появлялись с помощью этого кода:
for i in range(8):
m = Mob()
all_sprites.add(m)
mobs.add(m)
Можно было бы добавить кое-какие изменения, но это вызовет повторение в коде, что не очень хорошо. Вместо этого лучше поместить логику создания мобов в функцию и использовать ее везде, где это потребуется:
def newmob():
m = Mob()
all_sprites.add(m)
mobs.add(m)
Теперь можно использовать эту же функцию в начале игры, когда астероиды только появляются и в том месте, где они пропадают после столкновения:
for i in range(8):
newmob()
# проверка, попала ли пуля в моб
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
score += 50 - hit.radius
random.choice(expl_sounds).play()
newmob()
# Проверка, не ударил ли моб игрока
hits = pygame.sprite.spritecollide(player, mobs, True, pygame.sprite.collide_circle)
for hit in hits:
player.shield -= hit.radius * 2
newmob()
if player.shield <= 0:
running = False
Имеется значение здоровья, но оно во многом бесполезно, если игроку неизвестно это значение. Нужно создать дисплей, но вместо показа просто цифр лучше вывести полоску, по которой будет видно, насколько она заполнена:
def draw_shield_bar(surf, x, y, pct):
if pct < 0:
pct = 0
BAR_LENGTH = 100
BAR_HEIGHT = 10
fill = (pct / 100) * BAR_LENGTH
outline_rect = pygame.Rect(x, y, BAR_LENGTH, BAR_HEIGHT)
fill_rect = pygame.Rect(x, y, fill, BAR_HEIGHT)
pygame.draw.rect(surf, GREEN, fill_rect)
pygame.draw.rect(surf, WHITE, outline_rect, 2)
Эта функция будет работать по аналогии с draw_text. Она создаст полоску с размерами BAR_LENGHT и BAR_HEIGHT, которая будет находиться в (x, y) и заполнена на следующее значение pct. Нарисуем два прямоугольника: первый — белый контур, второй — уровень здоровья. Добавим вызов этой функции в раздел отрисовки игрового цикла:
draw_text(screen, str(score), 18, WIDTH / 2, 10)
draw_shield_bar(screen, 5, 5, player.shield)
Теперь можно видеть, сколько здоровья у игрока, и как он меняется при попадании астероидов.
Код урока — shmup-9.py
]]>Восьмая часть проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части.
В этом уроке в игре появятся музыка и звуки.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
Хороший звук (музыка и звуковые эффекты) — это один из самых эффективных способов добавить в игру «энергии». Это неофициальный термин из геймдизайна, который применяется по отношению к элементам, делающим игры более веселыми и захватывающими. Его часто называют «чувством игры» (game feel).
Как и в случае с графикой, найти подходящие звуки может быть непросто. OpenGameArt — отличное место для поиска аудио-ассетов, но в этот раз попробуем другой подход — создать звуковые эффекты самостоятельно.
Для этого будет использоваться инструмент под названием Bfxr. Он выглядит следующим образом:

Но не стоит пугаться этого многообразия слайдеров и жаргонизмов. Кнопки слева случайным образом генерируют звук выбранного типа. Попробуйте нажать «Shoot» несколько раз. Сгенерированные звуки сохранятся в списке ниже.
Для Стрелялки нужны звуки «выстрела» и «взрыва». После получения подходящего звука, нужно нажать на кнопку «Export Wav» (НЕ «Save to Disk»).
Дальше необходимо создать папку «snd» (как и для изображений) и переместить WAV-файлы туда. Можете использовать, например, эти звуки:
Обратите внимание, что здесь два звука взрыва. Можно сделать так, чтобы они воспроизводились случайным образом, и таким образом добавить разнообразия в игру.
Теперь нужна музыка. Ее можно поискать на OpenGameArt или использовать следующую:
Обратите внимание, что автор указал «Attribution Instructions» (модель атрибуции). Это условия, которые выбрал музыкант, чтобы лицензировать свое произведение. Это значит, что его нужно упомянуть. Для этого достаточно скопировать и вставить текст со страницы в верхнюю часть программы.
Теперь можно добавлять звуки в игру. Во-первых, нужно указать, в какой папке они находятся:
# Frozen Jam by tgfcoder <https://twitter.com/tgfcoder> licensed under CC-BY-3
# Art from Kenney.nl
import pygame
import random
from os import path
img_dir = path.join(path.dirname(__file__), 'img')
snd_dir = path.join(path.dirname(__file__), 'snd')
Дальше файлы необходимо загрузить. Это стоит сделать в том же месте, где загружалась графика. Сначала звук выстрелов:
# Загрузка мелодий игры
shoot_sound = pygame.mixer.Sound(path.join(snd_dir, 'pew.wav'))
Теперь, когда звук загружен и присвоен переменной shoot_sound, на него можно ссылаться. Важно, чтобы он воспроизводился каждый раз, когда игрок стреляет, поэтому его нужно добавить в метод shoot():
def shoot(self):
bullet = Bullet(self.rect.centerx, self.rect.top)
all_sprites.add(bullet)
bullets.add(bullet)
shoot_sound.play()
Это все, что требуется. Стрелять теперь будет приятнее!
Дальше нужны звуки взрывов. Загрузим оба и добавим их в список:
# Загрузка мелодий игры
shoot_sound = pygame.mixer.Sound(path.join(snd_dir, 'pew.wav'))
expl_sounds = []
for snd in ['expl3.wav', 'expl6.wav']:
expl_sounds.append(pygame.mixer.Sound(path.join(snd_dir, snd)))
Для воспроизведения взрывов нужно будет случайным образом выбирать один и включать его при разрушении астероида.
# проверка, попала ли пуля в моб
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
score += 50 - hit.radius
random.choice(expl_sounds).play()
m = Mob()
all_sprites.add(m)
mobs.add(m)
Наконец, необходимо добавить фоновую музыку, которая сделает игру более эмоциональной. Здесь необходим другой подход, потому что музыка играет непрерывно.
Сначала загружаем файл:
expl_sounds = []
for snd in ['expl3.wav', 'expl6.wav']:
expl_sounds.append(pygame.mixer.Sound(path.join(snd_dir, snd)))
pygame.mixer.music.load(path.join(snd_dir, 'tgfcoder-FrozenJam-SeamlessLoop.ogg'))
pygame.mixer.music.set_volume(0.4)
Музыка довольно громкая, поэтому, чтобы она не перекрывала звуки, необходимо опустить ее максимальную громкость до 40%.
Чтобы музыка играла, нужно просто выбрать в каком месте кода она будет стартовать. В случае Стрелялки это будет позиция перед началом игрового цикла.
score = 0
pygame.mixer.music.play(loops=-1)
# Цикл игры
running = True
Параметр loops определяет, как часто трек будет повторяться. Если установить значение на -1, то он будет воспроизводиться бесконечно.
Игра теперь ощущается совсем по-другому. Геймплей не поменялся, но с музыкой и звуками все воспринимается живее. Осталось поэкспериментировать с разными звуками и понять, какие подходят лучше.
Код урока — shmup-8.py
В следующем уроке добавим игроку щиты, чтобы он не умирал так быстро.
]]>Седьмая часть проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части. В этот раз начнем вести счет и научимся выводить текст на экран.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
Ведение счет игрока — простая задача. Нужно переменная с начальным значением 0, к которой будет добавляться +1 при каждом уничтожении астероида. Поскольку астероиды разные, а в крупные попасть легче, чем в маленькие, есть смысл в том, чтобы давать больше очков за уничтожение тех, что поменьше.
Назовем переменную score и объявим ее до игрового цикла:
for i in range(8):
m = Mob()
all_sprites.add(m)
mobs.add(m)
score = 0
# Цикл игры
running = True
Чтобы назначать очки в зависимости от размера астероида, можно использовать свойство radius. У самых крупных астероидов изображение больше 100 пикселей, а радиус 100 * 0.85 / 2 = 43 пикселя. В то же время радиус самого маленького астероида — всего 8 пикселей. Можно вычитать радиус из большего числа, например 50, чтобы получать количество очков. Так, большой будет давать всего 7 очков, а маленький — 42 очка.
# проверка, попала ли пуля в моб
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
score += 50 - hit.radius
m = Mob()
all_sprites.add(m)
mobs.add(m)
Теперь, когда есть переменная со счетом, нужно отрисовывать ее на экране, и вот это уже посложнее. Делается все в несколько этапов. Если вы планируете выводить текст не один раз, тогда есть смысла создать функцию, которая будет называться draw_text. Ее параметры:
surf — поверхность, на которой текст будет написанtext — строка, которую нужно отображатьx, y — положениеТакже нужно выбрать шрифт. Проблема может возникнуть, если выбрать тот шрифт, который не установлен на компьютере. Решить это можно с помощью pygame.font.match_font(), которая ищет наиболее подходящий шрифт в системе.
Вот вся функция draw_text:
font_name = pygame.font.match_font('arial')
def draw_text(surf, text, size, x, y):
font = pygame.font.Font(font_name, size)
text_surface = font.render(text, True, WHITE)
text_rect = text_surface.get_rect()
text_rect.midtop = (x, y)
surf.blit(text_surface, text_rect)
Отрисовка текста на экране — это, фактически, вычисление необходимой структуры пикселей. В компьютерной графике это называется «рендерингом». За это будет отвечать функция font.render(). В функции есть неизвестный параметр True, который отвечает за включение и отключение сглаживания.
Если вы когда-нибудь пытались нарисовать что-нибудь пикселями (или, например, блоками в Minecraft), то знаете, насколько сложно изображать изогнутые линии на квадратной сетке. Максимум, что выходит, — зубчатая форма. Такая зубчатость называется «алиасингом». Вот как она выглядит:

Первая «a» выглядит чересчур угловатой из-за алиасинга. Сглаживание (анти-алиасинг) — это то, как современные компьютеры работают с текстом, чтобы он не был настолько зубчатым. Это происходит с помощью смешивания пикселей с фоном на границах объектов. В уменьшенном масштабе такой шрифт выглядит чисто и аккуратно.
Теперь можно показывать счет на экране. Нужно лишь добавить его в раздел отрисовки в верхней части экрана по центру.
# Рендеринг
screen.fill(BLACK)
screen.blit(background, background_rect)
all_sprites.draw(screen)
draw_text(screen, str(score), 18, WIDTH / 2, 10)

Код урока — shmup-7.py
Вот и все. В следующей раз будем работать с музыкой и звуками.
]]>Шестая часть проекта «Стрелялка с 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(помня об их ограничениях).
В следующий раз разберем, как вести счет, и узнаем, как выводить текст на экран.
]]>Пятая часть проекта «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начните с первой части. В этот раз будем менять то, как Pygame обрабатывает столкновения спрайтов.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
После прошлого урока в игре появилась графика, а спрайты превратились из простых прямоугольников в приятные PNG-изображения. Тем не менее появилась и новая проблема: игра начала засчитывать столкновения даже тогда, когда визуально их не видно. Чтобы увидеть это визуально, нужно рассмотреть схему:

Тип столкновения по умолчанию в Pygame — функция collide_rect(), которая высчитывает атрибуты rect спрайтов, чтобы понять, пересекаются ли они. Это столкновения AABB, которые работают быстро и надежно. Однако если изображения спрайтов не являются прямоугольниками, то происходит ситуация как на изображении выше. Прямоугольники пересекаются, функция collide_rect возвращает True, но игрок разочарован, потому что он-то на самом деле избежал столкновения.
Вот как можно избежать этой ситуации.

Используя collide_rect_ratio(), можно сделать прямоугольник меньшего размера, уменьшив количество «пустого» места, которое в противном случае воспринималось бы как пересечение. Но это не всегда сработает. На изображении выше крылья космического корабля все равно оказываются за пределами прямоугольника. Это значит, что иногда астероид будет проходить сквозь крыло, не нанося урона. Но это не так уж и плохо! На той скорости, на которой работает игра, игрок не заметит, но почувствует, что был очень близок к столкновению. Вместо разочарования он почувствует, что неплохо справляется.

Другой вариант — использовать ограничивающий круг. Он идеально вписывается в форму астероида. Не так хорошо подходит для крыльев корабля, но самой игре это не навредит.
Опираясь на возможности, описанные выше, для функции столкновения игрока с астероидом будут использоваться круги. В Pygame это сделать проще простого с помощью атрибута self.radius для каждого из спрайтов.
Сначала игрок. Какого размера должен быть круг? Придется поэкспериментировать, чтобы определить нужный радиус. Вот как сделать это в спрайте игрока __init()__:
self.rect = self.image.get_rect()
self.radius = 25
pygame.draw.circle(self.image, RED, self.rect.center, self.radius)
Нарисуем красный круг поверх изображения, чтобы видеть, как он работет. То же самое нужно сделать и для астероида:
self.rect = self.image.get_rect()
self.radius = int(self.rect.width / 2)
pygame.draw.circle(self.image, RED, self.rect.center, self.radius)
Здесь удалось пойти на небольшую хитрость. Если со временем будет решено использовать астероиды разных размеров, то указав радиус как 1/2 ширины изображения, можно будет не возвращаться к редактированию кода.
Вот как это выглядит:

Здесь видно, что для игрока радиус выбран чересчур большой —круг даже больше чем размер корабля по оси y. Лучше установить радиус self.radius = 20.
Для астероида хорошо было бы, чтобы его края выглядывали из-под круга, поэтому при расчете нужно уменьшить ширину астероида до 85% от оригинальной.
self.radius = int(self.rect.width * .85 / 2)
Чтобы игра начала использовать этот тип столкновений, нужно поменять команду spritecollide c AABB на ограничивающий круг:
# проверка, не ударил ли моб игрока
hits = pygame.sprite.spritecollide(player, mobs, False, pygame.sprite.collide_circle)
if hits:
running = False

Если все работает так, как хотелось, красные круги можно убрать. Лучше их просто закомментировать, если нужно будет поэкспериментировать с ними в будущем.
Код урока — shmup-5.py
Тип столкновений влияет на то, как будет восприниматься игра. Так, в этот раз изменился тип столкновений астероидов и игрока, но не поменялось взаимодействие астероидов с пулями. Последним лучше оставаться прямоугольниками.
В следующий раз речь пойдет о том, как добавить спрайтам анимацию.
]]>Четвертая часть проект «Стрелялка с Pygame». Если пропустили, обязательно вернитесь и начините с первой части. В этот раз речь пойдет об использовании заготовленной графики.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
В уроке, посвященном спрайтами, затрагивалась тема Opengameart.org — источника бесплатного арта для игр; и популярного местного автора, Kenney. Kenney создал набор арта, который прекрасно подойдет для этой стрелялки. Он называется «Space Shooter Pack». Найти его можно здесь:
https://opengameart.org/content/space-shooter-redux
Он включает массу качественных изображений с космическими кораблями, лазерами, астероидами и так далее.
Архив содержит несколько папок. Для этого проекта нужно папка PNG со всеми изображениями по отдельности. Для спрайтов пока что нужно только три, а также изображение звездного неба для фона.

![]()
![]()

Изображения необходимо скопировать туда, где игра сможет их найти. Лучше всего создать новую папку в том же месте, где хранится код игры. Назовем ее «img».
Чтобы быть уверенными в том, что код будет работать в любой операционной системе, нужно использовать функцию os.path, которая определяет правильный путь к файлам вне зависимости от ОС.
В верхней части программы необходимо определить местоположение папки img:
from os import path
img_dir = path.join(path.dirname(__file__), 'img')
Теперь можно приступать к загрузке фонового изображения. Загрузку ассетов необходимо производить до игрового цикла и кода запуска:
# Загрузка всей игровой графики
background = pygame.image.load(path.join(img_dir, 'starfield.png')).convert()
background_rect = background.get_rect()
Теперь можно прорисовать фон в разделе Draw игрового цикла до прорисовки любого из спрайтов:
# Рендеринг
screen.fill(BLACK)
screen.blit(background, background_rect)
all_sprites.draw(screen)
blit — это олдскульный термин из компьютерной графики, который обозначает прорисовку пикселей одного изображения на другом. В этом случае — прорисовку фона на экране. Теперь фон выглядит намного лучше:

Теперь можно загрузить изображения спрайтов:
# Загрузка всей игровой графики
background = pygame.image.load(path.join(img_dir, 'starfield.png')).convert()
background_rect = background.get_rect()
player_img = pygame.image.load(path.join(img_dir, "playerShip1_orange.png")).convert()
meteor_img = pygame.image.load(path.join(img_dir, "meteorBrown_med1.png")).convert()
bullet_img = pygame.image.load(path.join(img_dir, "laserRed16.png")).convert()
Начнем с игрока — необходимо заменить зеленый прямоугольник, то есть self.image и не забыть убрать параметр image.fill(GREEN), который больше не нужен:
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = player_img
self.rect = self.image.get_rect()

Тем не менее имеется пара проблем. Во-первых, картинка чуть больше, чем требуется. Есть два варианта: 1) открыть ее в графическом редакторе (Photoshop, GIMP) и отредактировать; 2) поменять размер прямо в коде. Выбираем второй вариант. Для этого понадобится функция Pygame под названием transform.scale(). Уменьшим изображение вдвое — до размера 50х30 пикселей.
Вторая проблема — черный прямоугольник вокруг корабля. Чтобы это исправить, нужно назначить прозрачный цвет с помощью set_colorkey:
self.image = pygame.transform.scale(player_img, (50, 38))
self.image.set_colorkey(BLACK)
Если повторить то же самое для классов Bullet и Mob (хотя их размер менять не нужно), игра будет выглядеть намного лучше:

Код урока — shmup-4.py
Теперь в игре есть графика, и это помогает заметить новую проблему: астероиды уничтожают корабль, даже когда не касаются его. В следующем уроке поработаем над тем, чтобы столкновения работали корректно.
]]>Третья часть проекта «Стрелялка с Pygame». В этот раз в игре появятся столкновения между игроком и врагами, а также пули, которыми игрок будет стрелять.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Она будет особенно интересна начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
Столкновения — важная часть разработки игр. Обнаружение столкновений подразумевает необходимость распознать, когда один объект в игре касается другого. Ответ на столкновение — это то, что случится в момент столкновения: когда Марио подберет монетку, когда меч Линка нанесет урон врагу и так далее.
В этой стрелялке есть спрайты врагов, которые летят сверху вниз по направлению к игроку, и хотелось бы понимать, когда они сталкиваются с игроком. На этом этапе предположим, что момент столкновения означает завершение игры.
У каждого спрайта в Pygame есть атрибут rect, определяющий его координаты и размер. Объект rect в Pygame представлен в формате [x, y, width, height], где x и y представляют собой верхний левый угол прямоугольника. Другое название для этого прямоугольника — ограничивающая рамка, потому что она является границей объекта.
Обнаружение столкновений называется AABB (axis-aligned bounding box или «параллельный осям ограничивающий параллелепипед»). Такое название объясняется тем, что прямоугольник выравнивается в соответствии с осями экрана, которые не наклоняются. Обнаружение столкновений AABB столь популярно, потому что работает быстро — компьютер молниеносно сравнивает координаты прямоугольников, что особенно удобно, когда на экране много объектов.
Чтобы обнаружить столкновение, необходимо сравнить прямоугольник игрока с прямоугольниками остальных мобов. Это можно сделать, пройдя циклом по всем мобам и сравнить каждый из них следующим образом:

На изображении видно, что только прямоугольник №3 сталкивается с большим черным прямоугольником. №1 пересекается с осью x, а №2 — с осью y. Но для пересечения двух прямоугольников, должны пересекаться обе их оси. Вот как это преподнести в коде:
if mob.rect.right > player.rect.left and \
mob.rect.left < player.rect.right and \
mob.rect.bottom > player.rect.top and \
mob.rect.top < player.rect.bottom:
collide = True
К счастью, в Pygame есть встроенная функция spritecollide() для выполнения того же самого.
В раздел «update» игрового цикла необходимо добавить следующую команду:
# Обновление
all_sprites.update()
# Проверка, не ударил ли моб игрока
hits = pygame.sprite.spritecollide(player, mobs, False)
if hits:
running = False
spritecollide() принимает 3 аргумента: название спрайта, который нужно проверять, название группы для сравнения и значения True или False для параметра dokill. Последний параметр позволяет указать, должен ли объект удаляться при столкновении. Если нужно было, например, проверить, подобрал ли игрок монетку, необходимо указать значение True так, чтобы монетка пропала.
Результат команды spritecollide() — это список спрайтов, которые столкнулись с игроком (он может быть не один). Присвоим его переменной hits.
Если список hits будет непустым, значение инструкции if окажется True. В результате значение running изменится на False, и игра закончится.
Пришло время добавить еще один спрайт — пули. Это будет спрайт, который появляется в момент выстрела над спрайтом игрока и двигается вверх с большой скоростью. Определение спрайта вам знакомо, поэтому вот сразу готовый класс Bullet:
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 20))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.speedy = -10
def update(self):
self.rect.y += self.speedy
# убить, если он заходит за верхнюю часть экрана
if self.rect.bottom < 0:
self.kill()
В метод __init__() спрайта пули нужно передать значения x и y, чтобы указать спрайту, где появляться. Поскольку спрайт игрока может двигаться, то место появления будет соответствовать местоположению игрока. Значение speedy будет отрицательным, чтобы он спрайт двигался наверх.
Наконец, нужно проверить оказался ли спрайт за пределами экрана. Если да — его можно удалять.
Чтобы было легко хотя бы вначале, нужно сделать так, чтобы каждый раз при нажатии пробела вылетала пуля. Это нужно добавить к проверке событий:
for event in pygame.event.get():
# проверка для закрытия окна
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.shoot()
Новый код проверяет событие KEYDOWN, и если таковое наблюдается, проверяет нажата ли кнопка K_SPACE. Если да — запускается метод игрока shoot().
В первую очередь необходимо добавить группу для пуль:
bullets = pygame.sprite.Group()
Теперь можно создавать следующий метод в классе Player:
def shoot(self):
bullet = Bullet(self.rect.centerx, self.rect.top)
all_sprites.add(bullet)
bullets.add(bullet)
Все что делает метод shoot() — создает пулю, используя в качестве места появления верхнюю центральную часть игрока. После этого нужно убедиться, что пуля добавлена в all_sptires (чтобы она отрисовалась и обновилась) и в bullets, которая будет использоваться для столкновений.
Теперь нужно проверить, задела ли пуля моба. Отличие в том, что есть несколько пуль (в группе bullets) и несколько мобов (в группе mobs), поэтому нельзя использовать spritecollide() как в прошлый раз, потому что в этой функции сравнивается только один спрайт с группой. Вместо этого нужно использовать groupcollide():
# Обновление
all_sprites.update()
# Проверка, не ударил ли моб игрока
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
m = Mob()
all_sprites.add(m)
mobs.add(m)
Функция groupcollide() похожа на spritecollide() за исключением того, что нужно указывать две группы для сравнения, а возвращать функция будет список задетых мобов. Также есть два значения dokill для каждой из групп.
Если просто удалять мобов, то появится проблема: они закончатся. Поэтому нужно просто проходить циклом по hits и для каждого уничтоженного моба создавать один новый.
Теперь это начинает напоминать реальную игру:

Код урока — shmup-3.py
В следующем уроке вы узнаете, как добавить графику в игре вместо того, чтобы использовать цветные прямоугольники.
]]>Вторая часть проекта «Стрелялка с Pygame». В этот раз в игре появятся враги, от которых должен будет уклоняться игрок.
В этой серии уроков будет создана полноценная игра на языке Python с помощью библиотеки Pygame. Это будет особенно интересно начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
Пока что можно не думать о том, какими именно будут спрайты врагов. Главное, чтобы они просто отображались на экране. Можно сделать проект, в котором космический корабль будет уклоняться от метеоритов, или единорог — от пицц. Это никак не затронет код, поэтому внешний вид не столь важен на данном этапе.
Учитывая это, нужно дать спрайту врага в коде какое-то общее название. Почти идеальным именем для такого рода объектов (которые двигаются в игре) является моб (вы могли слышать это выражение раньше).
Начнем с определения свойств спрайта:
class Mob(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((30, 40))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speedy = random.randrange(1, 8)
Важно определить хорошую стартовую точку для появления мобов. Не хочется, чтобы они возникали из ниоткуда, поэтому нужно просто выбрать значение, которое бы размещало объект сверху за пределами экрана (y < 0), а значение x должно быть в пределах двух сторон.
Дальше для функции обновления нужно задать движения спрайта с определенной скоростью, но что с ним будет, когда он доберется до нижней части экрана? Его можно удалить и создать новый или же можно перенести этот же спрайт в случайное место в верхней части экрана.
def update(self):
self.rect.y += self.speedy
if self.rect.top > HEIGHT + 10:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speedy = random.randrange(1, 8)
Врагов будет много, поэтому нужно создать группу mobs для них всех. Это также упростит работу с ними в дальнейшем. После этого нужно вызвать определенное количество мобов и добавить их в группы.
player = Player()
all_sprites.add(player)
for i in range(8):
m = Mob()
all_sprites.add(m)
mobs.add(m)
Должен получиться поток мобов, которые опускаются вниз по экрану:

Пока что все отлично, но мобы, двигающиеся ровно вниз, смотрятся скучно. Нужно добавить движение по координате x:
class Mob(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((30, 40))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speedy = random.randrange(1, 8)
self.speedx = random.randrange(-3, 3)
def update(self):
self.rect.x += self.speedx
self.rect.y += self.speedy
if self.rect.top > HEIGHT + 10 or self.rect.left < -25 or self.rect.right > WIDTH + 20:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speedy = random.randrange(1, 8)
Также нужно изменить инструкцию if, которая создает новых мобов в тот момент, когда те пропадают с экрана. Моб, который двигается по диагонали, пропадет с экрана задолго до того как доберется до нижней части, поэтому нужно перезагрузить его быстрее.
Теперь игра должна выглядеть следующим образом:

Код урока — shmup-2.py
В следующем уроке научимся определять, когда два моба сталкиваются и дадим игроку возможность отстреливаться.
]]>В этой серии уроков будет создана полноценная игру с помощью Python и Pygame. Это будет особенно интересно начинающим программистам, которые уже знакомы с основами языка и хотят углубить знания, а также узнать, что лежит в основе создания игр.
Если вы еще не знакомы с pygame, вернитесь и закончите первый урок в водной части «Библиотека Pygame / Часть 1. Введение». Дальше будет использоваться программа pygame template.py, которая была создана в том уроке, как основа для этого.
В этой серии мы будем работать над игрой в жанре «Shmup» или «Shoot ’em up» (или, если еще проще, «Стрелялка»). Игрок будет пилотом маленького космического корабля, который пытается выжить среди метеоритов и прочих предметов, летящих на него.
Для начала нужно сохранить файл pygame template.py с новым именем. Таким образом вы сможете использовать этот шаблон в будущем для других игр. Можно назвать файл просто как shmup.py.
В первую очередь, необходимо отредактировать настройки, добавив некоторые значения в игру:
WIDTH = 480
HEIGHT = 600
FPS = 60
Игра будет выполнена в «портретном режиме» — это значит, что высота окна больше, чем его ширина. Игра будет экшеном, поэтому важно задать высокое значение кадров в секунду. В таком случае спрайты будут двигаться максимально плавно. 60 — идеальное значение.
Первое, что необходимо добавить — спрайт, который олицетворяет собой игрока. В итоге это будет космический корабль, но на начальных этапах можно просто игнорировать графику и использовать прямоугольники вместо спрайтов. Важно добиться того, чтобы они отображались на экране и двигались так, как требуется. Заменить их потом артами не составит труда.
Это начало спрайта игрока:
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 40))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT - 10
self.speedx = 0
Для игрока выбран размер 50х40 пикселей. Он будет находится по центру в нижней части экрана. Также есть свойство speedx, которое будет отслеживать, с какой скоростью двигается игрок по оси x (со стороны в сторону). Если вы не до конца понимаете, как это все работает, вернитесь к уроку «Работа со спрайтами».
Метод update() спрайта запускается в каждом кадре. Он будет перемещать спрайт с конкретной скоростью:
def update(self):
self.rect.x += self.speedx
Теперь нужно показать спрайт, чтобы убедиться, что он отображается на экране:
all_sprites = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
Не забывайте, что каждый созданный спрайт должен быть добавлен в группу all_sprites, так чтобы он обновлялся и прорисовывался на экране.
Управлять в этой игре нужно будет с помощью клавиатуры, поэтому игрок должен будет двигаться при нажатии кнопок Влево или Вправо (это могут быть a или d).
Когда речь заходит об использовании кнопок в игре, есть выбор, а это значит, что речь идет о Событиях:
1 вариант: в этой очереди событий можно определить два события (по одному для каждой кнопки), каждое из которых будет менять скорость игрока, соответственно:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
player.speedx = -8
if event.key == pygame.K_RIGHT:
player.speedx = 8
Проблема с этим методом в том, что после нажатия кнопки Игрок двигается, но не останавливается. Нужно также добавить два события KEYUP, которые будут возвращать скорость к значению 0.
2 вариант: установить скорость спрайта по умолчанию на значение 0, за исключением случаев, когда нажимаются кнопки Влево или Вправо. В результате движение будет более плавным, а код проще.
def update(self):
self.speedx = 0
keystate = pygame.key.get_pressed()
if keystate[pygame.K_LEFT]:
self.speedx = -8
if keystate[pygame.K_RIGHT]:
self.speedx = 8
self.rect.x += self.speedx
Этот код устанавливает скорость speedx на значении 0 для каждого кадра, а затем проверяет, не нажата ли кнопка. pygame.key.get_pressed() возвращает словарь со всеми клавишами клавиатуры и значениями True или False, которые указывают на то, нажата ли какая-то из них. Если одна из кнопок нажимается, скорость меняется соответственно.
Наконец, нужно сделать так, чтобы спрайт не пропадал с экрана. Для этого нужно добавить этот код к update() игрока:
if self.rect.right > WIDTH:
self.rect.right = WIDTH
if self.rect.left < 0:
self.rect.left = 0
Теперь если rect попробует двигаться за пределы экрана, он остановится. Второй вариант — телепортировать спрайт с одного края экрана ко второму, когда он туда доберется. Но в этой игре предпочтительнее тормозить игрока.
Вот весь код этого шага:
# Игра Shmup - 1 часть
# Cпрайт игрока и управление
import pygame
import random
WIDTH = 480
HEIGHT = 600
FPS = 60
# Задаем цвета
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
# Создаем игру и окно
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Shmup!")
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 40))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT - 10
self.speedx = 0
def update(self):
self.speedx = 0
keystate = pygame.key.get_pressed()
if keystate[pygame.K_LEFT]:
self.speedx = -8
if keystate[pygame.K_RIGHT]:
self.speedx = 8
self.rect.x += self.speedx
if self.rect.right > WIDTH:
self.rect.right = WIDTH
if self.rect.left < 0:
self.rect.left = 0
all_sprites = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
# Цикл игры
running = True
while running:
# Держим цикл на правильной скорости
clock.tick(FPS)
# Ввод процесса (события)
for event in pygame.event.get():
# проверка для закрытия окна
if event.type == pygame.QUIT:
running = False
# Обновление
all_sprites.update()
# Рендеринг
screen.fill(BLACK)
all_sprites.draw(screen)
# После отрисовки всего, переворачиваем экран
pygame.display.flip()
pygame.quit()
В следующем уроке в игру будут добавлены спрайты врагов, от которых игрок будет уклоняться.
]]>Третья часть серии руководств «Разработка игр с помощью Pygame». Она предназначена для программистов начального и среднего уровней, которые заинтересованы в создании игр и улучшении собственных навыков кодирования на Python. Начать стоит с урока: «Библиотека Pygame / Часть 1. Введение».
Разноцветные прямоугольники вполне можно использовать на старте разработки, чтобы убедиться, что игра работает, но рано или поздно захочется задействовать изображение космического корабля или персонажа для спрайта. Это подводит к первому вопросу: где брать графику для игры?
Когда вам нужен арт для игры, у вас есть 3 варианта:
Первый и второй варианты подходят для творческих людей или тех, у кого есть талантливые друзья, но у большинства программистов рисование не входит в набор навыков. Поэтому остается интернет. Но здесь важно помнить, что нельзя использовать изображения, на которые у вас нет прав. Вы без проблем найдете изображение с Марио или любимым Покемоном, но их нельзя использовать, особенно если планируется выкладывать игру в интернет для других пользователей.
К счастью, есть хорошее решение — OpenGameArt.org. На этом сайте полно изображений, звуков, музыки и другого контента. Весь он лицензирован так, что его можно свободно использовать в играх. Один из лучших создателей контента на этом сайте — Kenney. Его можно найти, просто введя это имя в строку поиска или зайти на его сайт.
Особенность арта Kenney (помимо отличного качества) — он выпускает контент в коллекциях. Это значит, что разработчик получает различные изображения, выполненные в едином стиле, и нет необходимости брать картинки в разных источниках.
В этом уроке будет использоваться набор Platformer Art Complete Pack от Kenney, в котором полно графики для создания игры в жанре платформера. Нужно всего лишь скачать его и распаковать. Начнем с изображения p1_jump.png.

Или же можете просто скачать картинку отсюда.
В первую очередь нужна папка для хранения ассетов. В играх так называют, например, арты или звук. Назовем папку “img” и перенесем туда изображение игрока.
Чтобы использовать изображение в игре, нужно сообщить библиотеке Pygame, чтобы она загружала файл. Для этого необходимо указать его местоположение. В зависимости от используемого компьютера этот процесс может отличаться, но поскольку нужно сделать так, чтобы игра работала на любом устройстве, необходимо загрузить библиотеку Python под название os и указать, где находится игра:
import pygame
import random
import os
# настройка папки ассетов
game_folder = os.path.dirname(__file__)
Специальная переменная __file__ относится к папке, в которой сохранен код игры, а команда os.path.dirname указывает путь к папке. Например, путь к коду на компьютере с macOS может быть такой:
/Users/gvido/Documents/gamedev/tutorials/1_3_sprite_example.py
Если используется Windows, тогда он будет выглядеть приблизительно вот так:
C:\Users\gvido\Documents\python\game.py
Разные операционные системы по-разному подходят к поиску местоположения файлов. С помощью команды os.path можно позволить ПК самостоятельно определять правильный путь (вне зависимости от того, используется “/” или “”).
Теперь можно точно указать местоположение папки “img”:
import pygame
import random
import os
# настройка папки ассетов
game_folder = os.path.dirname(__file__)
img_folder = os.path.join(game_folder, 'img')
player_img = pygame.image.load(os.path.join(img_folder, 'p1_jump.png')).convert()
Изображение загружается с помощью pygame.image.load(), а convert() ускорит прорисовку в Pygame, конвертируя изображение в тот формат, который будет быстрее появляться на экране. Теперь можно заменить зеленый квадрат на изображение персонажа:
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = player_img
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
Обратите внимание, что команды self.image.fill(GREEN) больше нет, потому что заливка одним цветом больше не нужна. get_rect() работает так же — теперь она будет окружать прямоугольником любое изображение self.image.
Если сейчас запустить программу, вы увидите маленького инопланетянина, который двигается по экрану. Но осталась одна проблема, которой не видно из-за черного фона. С помощью команды screen.fill() нужно поменять цвет фона, например, на синий. Теперь понятно, в чем проблема.

Файл изображения на компьютере — это всегда сетка пикселей. Вне зависимости от нарисованной формы, имеются дополнительные пиксели, заполняющие «фон». Нужно сообщить Pygame, чтобы она игнорировала пиксели, которые не нужны. В случае этого изображения речь идет о черном цвете, поэтому в код необходимо добавить следующее:
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = player_img
self.image.set_colorkey(BLACK)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
set_colorkey() говорит Pygame игнорировать любые пиксели конкретного цвета. Так выглядит намного лучше:

Вы освоили основы работы с Python! Время переходить к созданию настоящей игры. У нас есть руководство с процессом создания игры Shoot ’em up от начала и до конца.
]]>Вторая часть серии руководств «Разработка игр с помощью Pygame». Она предназначена для программистов начального и среднего уровней, которые заинтересованы в создании игр и улучшении собственных навыков кодирования на Python. Начать стоит с урока: «Библиотека Pygame / Часть 1. Введение».
Спрайт — это элемент компьютерной графики, представляющий объект на экране, который может двигаться. В двухмерной игре все, что вы видите на экране, является спрайтами. Спрайты можно анимировать, заставлять их взаимодействовать между собой или передавать управление ими игроку.
Для загрузки и отрисовки спрайтов в случай этой игры их нужно добавить в разделы “Обновление” и “Визуализация” игрового цикла. Несложно представить, что если в игре много спрайтов, то цикл довольно быстро станет большим и запутанным. В Pygame для этого есть решение: группировка спрайтов.
Набор спрайтов — это коллекция спрайтов, которые могут отображаться одновременно. Вот как нужно создавать группу спрайтов в игре:
clock = pygame.time.Clock()
all_sprites = pygame.sprite.Group()
Теперь этой возможностью можно воспользоваться, добавив группу целиком в цикл:
# Обновление
all_sprites.update()
# Отрисовка
screen.fill(BLACK)
all_sprites.draw(screen)
Теперь при создании каждого спрайта, главное убедиться, что он добавлен в группу all_sprites. Такой спрайт будет автоматически отрисован на экране и обновляться в цикле.
Можно переходить к созданию первого спрайта. В Pygame все спрайты выступают объектами. Если вы не работали с этим типом данных в Python, то для начала достаточно знать, что это удобный способ группировки данных и кода в единую сущность. Поначалу это может путать, но спрайты Pygame — отличная возможность попрактиковаться в работе с объектами и понять, как они работают.
Начнем с определения нового спрайта:
class Player(pygame.sprite.Sprite):
class сообщает Python, что определяется новый объект, который будет спрайтом игрока. Его тип pygame.sprite.Sprite. Это значит, что он будет основан на заранее определенном в Pygame классе Sprite.
Первое, что нужно в определении class — специальная функция __init__(), включающая код, который будет запущен при создании нового объекта этого типа. Также у каждого спрайта в Pygame должно быть два свойства: image и rect.
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 50))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
Первая строка, Pygame.sprite.Sprite.__init__(self) требуется в Pygame — она запускает инициализатор встроенных классов Sprite. Далее необходимо определить свойство image. Сейчас просто создадим квадрат размером 50х50 и заполним его зеленым (GREEN) цветом. Чуть позже вы узнаете, как сделать image спрайта красивее, используя, например, персонажа или космический корабль, но сейчас достаточно сплошного квадрата.
Дальше необходимо определить rect спрайта. Это сокращенное от rectangle (прямоугольник). Прямоугольники повсеместно используются в Pygame для отслеживания координат объектов. Команда get_rect() оценивает изображение image и высчитывает прямоугольник, способный окружить его.
rect можно использовать для размещения спрайта в любом месте. Начнем с создания спрайта по центру:
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 50))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
Теперь, после определения спрайта игрока Player, нужно отрисовать (создать) его, инициализировав экземпляр (instance) класса Player. Также нужно обязательно добавить спрайт в группу all_sprites.
all_sprites = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
Сейчас, если запустить программу, по центру окна будет находиться зеленый квадрат. Увеличьте значения WIDTH и HEIGHT в настройках программы, чтобы создать достаточно пространства для движения спрайта в следующем шаге.

В игровом цикле есть функция all_sprites.update(). Это значит, что для каждого спрайта в группе Pygame ищет функцию update() и запускает ее. Чтобы спрайт двигался, нужно определить его правила обновления:
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 50))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
def update(self):
self.rect.x += 5
Это значит, что при каждом игровом цикле x-координата спрайта будет увеличиваться на 5 пикселей. Запустите программу, чтобы посмотреть, как он скрывается за пределами экрана, достигая правой стороны.

Исправить это можно, заставив спрайт двигаться по кругу — когда он добирается до правой стороны экрана, просто переносить его влево. Это легко сделать, используя элемент управления rect спрайта:

Так, если левая сторона rect пропадает с экрана, просто задаем значение правого края равное 0:
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 50))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
def update(self):
self.rect.x += 5
if self.rect.left > WIDTH:
self.rect.right = 0
Теперь можно видеть, как спрайт будто бы двигается по кругу.

На этом все. Отправляйтесь изучать и экспериментировать, но не забывайте, что все, что вы помещаете в метод update(), будет происходить в каждом кадре. Попробуйте научить спрайт двигаться сверху вниз (изменив координату y) или заставить его отталкиваться от стен (изменяя направлении по достижении края).
# Pygame шаблон - скелет для нового проекта Pygame
import pygame
import random
WIDTH = 800
HEIGHT = 650
FPS = 30
# Задаем цвета
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((50, 50))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
def update(self):
self.rect.x += 5
if self.rect.left > WIDTH:
self.rect.right = 0
# Создаем игру и окно
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
all_sprites = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
# Цикл игры
running = True
while running:
# Держим цикл на правильной скорости
clock.tick(FPS)
# Ввод процесса (события)
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
# Обновление
all_sprites.update()
# Рендеринг
screen.fill(BLACK)
all_sprites.draw(screen)
# После отрисовки всего, переворачиваем экран
pygame.display.flip()
pygame.quit()
В следующем уроке речь пойдет о том, как использовать арт в спрайтах — перейти от обычного квадрата к анимированному персонажу.
]]>
Это первая часть серии руководств «Разработка игр с помощью Pygame». Она предназначена для программистов начального и среднего уровней, которые заинтересованы в создании игр и улучшении собственных навыков кодирования на Python.
Код в уроках был написан на Python 3.7 и Pygame 1.9.6
Pygame — это «игровая библиотека», набор инструментов, помогающих программистам создавать игры. К ним относятся:
В сердце каждой игры лежит цикл, который принято называть «игровым циклом». Он запускается снова и снова, делая все, чтобы работала игра. Каждый цикл в игре называется кадром.
В каждом кадре происходит масса вещей, но их можно разбить на три категории:
Речь идет обо всем, что происходит вне игры — тех событиях, на которые она должна реагировать. Это могут быть нажатия клавиш на клавиатуре, клики мышью и так далее.
Изменение всего, что должно измениться в течение одного кадра. Если персонаж в воздухе, гравитация должна потянуть его вниз. Если два объекта встречаются на большой скорости, они должны взорваться.
В этом шаге все выводится на экран: фоны, персонажи, меню. Все, что игрок должен видеть, появляется на экране в нужном месте.
Время
Еще один важный аспект игрового цикла — скорость его работы. Многие наверняка знакомы с термином FPS, который расшифровывается как Frames Per Second (или кадры в секунду). Он указывает на то, сколько раз цикл должен повториться за одну секунду. Это важно, чтобы игра не была слишком медленной или быстрой. Важно и то, чтобы игра не работала с разной скоростью на разных ПК. Если персонажу необходимо 10 секунд на то, чтобы пересечь экран, эти 10 секунд должны быть неизменными для всех компьютеров.
Теперь, зная из каких элементов состоит игра, можно переходить к процессу написания кода. Начать стоит с создания простейшей программы pygame, которая всего лишь открывает окно и запускает игровой цикл. Это отправная точка для любого проекта pygame.
В начале программы нужно импортировать необходимые библиотеки и задать базовые переменные настроек игры:
# Pygame шаблон - скелет для нового проекта Pygame
import pygame
import random
WIDTH = 360 # ширина игрового окна
HEIGHT = 480 # высота игрового окна
FPS = 30 # частота кадров в секунду
Дальше необходимо открыть окно игры:
# создаем игру и окно
pygame.init()
pygame.mixer.init() # для звука
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
pygame.init() — это команда, которая запускает pygame. screen — окно программы, которое создается, когда мы задаем его размер в настройках. Дальше необходимо создать clock, чтобы убедиться, что игра работает с заданной частотой кадров.
Теперь необходимо создать игровой цикл:
# Цикл игры
running = True
while running:
# Ввод процесса (события)
# Обновление
# Визуализация (сборка)
Игровой цикл — это цикл while, контролируемый переменной running. Если нужно завершить игру, необходимо всего лишь поменять значение running на False. В результате цикл завершится. Теперь можно заполнить каждый раздел базовым кодом.
Начнем с раздела отрисовки. Персонажей пока нет, поэтому экран можно заполнить сплошным цветом. Чтобы сделать это, нужно разобраться, как компьютер обрабатывает цвета.
Экраны компьютеров сделаны из пикселей, каждый из которых содержит 3 элемента: красный, зеленый и синий. Цвет пикселя определяется тем, как горит каждый из элементов:

Каждый из трех основных цветов может иметь значение от 0 (выключен) до 255 (включен на 100%), так что для каждого элемента есть 256 вариантов.
Узнать общее количество отображаемых компьютером цветов можно, умножив:
>>> 256 * 256 * 256
16,777,216
Теперь, зная, как работают цвета, можно задать их в начале программ:
# Цвета (R, G, B)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
А после этого — заполнить весь экран.
# Рендеринг
screen.fill(BLACK)
Но этого недостаточно. Дисплей компьютера работает не так. Изменить пиксель — значит передать команду видеокарте, чтобы она передала соответствующую команду экрану. По компьютерным меркам это очень медленный процесс. Если нужно нарисовать на экране много всего, это займет много времени. Исправить это можно оригинальным способом, который называется — двойная буферизация. Звучит необычно, но вот что это такое.
Представьте, что у вас есть двусторонняя доска, которую можно поворачивать, показывая то одну, то вторую сторону. Одна будет дисплеем (то, что видит игрок), а вторая — оставаться скрытой, ее сможет «видеть» только компьютер. С каждым кадром рендеринг будет происходить на задней части доски. Когда отрисовка завершается, доска поворачивается и ее содержимое демонстрируется игроку.

А это значит, что процесс отрисовки происходит один раз за кадр, а не при добавлении каждого элемента.
В pygame это происходит автоматически. Нужно всего лишь сказать доске, чтобы она перевернулась, когда отрисовка завершена. Эта команда называется flip():
# Рендеринг
screen.fill(BLACK)
# после отрисовки всего, переворачиваем экран
pygame.display.flip()
Главное — сделать так, чтобы функция flip() была в конце. Если попытаться отрисовать что-то после поворота, это содержимое не отобразится на экране.
Игры еще нет, поэтому пока сложно сказать, какие кнопки или другие элементы управления понадобятся. Но нужно настроить одно важное событие. Если попытаться запустить программу сейчас, то станет понятно, что нет возможности закрыть окно. Нажать на крестик в верхнем углу недостаточно. Это тоже событие, и необходимо сообщить программе, чтобы она считала его и, соответственно, закрыла игру.
События происходят постоянно. Что, если игрок нажимает кнопку прыжка во время отрисовки? Это нельзя игнорировать, иначе игрок будет разочарован. Для этого pygame сохраняет все события, произошедшие с момента последнего кадра. Даже если игрок будет лупить по кнопкам, вы не пропустите ни одну из них. Создается список, и с помощью цикла for можно пройтись по всем из них.
for event in pygame.event.get():
# проверить закрытие окна
if event.type == pygame.QUIT:
running = False
В pygame много событий, на которые он способен реагировать. pygame.QUIT — событие, которое стартует после нажатия крестика и передает значение False переменной running, в результате чего игровой цикл заканчивается.
Пока что нечего поместить в раздел Update (обновление), но нужно убедиться, что настройка FPS контролирует скорость игры. Это можно сделать следующим образом:
while running:
# держим цикл на правильной скорости
clock.tick(FPS)
Команда tick() просит pygame определить, сколько занимает цикл, а затем сделать паузу, чтобы цикл (целый кадр) длился нужно время. Если задать значение FPS 30, это значит, что длина одного кадра — 1/30, то есть 0,03 секунды. Если цикл кода (обновление, рендеринг и прочее) занимает 0,01 секунды, тогда pygame сделает паузу на 0,02 секунды.
Наконец, нужно убедиться, что когда игровой цикл завершается, окно игры закрывается. Для этого нужно поместить функцию pygame.quit() в конце кода. Финальный шаблон pygame будет выглядеть вот так:
# Pygame шаблон - скелет для нового проекта Pygame
import pygame
import random
WIDTH = 360
HEIGHT = 480
FPS = 30
# Задаем цвета
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# Создаем игру и окно
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
# Цикл игры
running = True
while running:
# Держим цикл на правильной скорости
clock.tick(FPS)
# Ввод процесса (события)
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
# Обновление
# Рендеринг
screen.fill(BLACK)
# После отрисовки всего, переворачиваем экран
pygame.display.flip()
pygame.quit()
Ура! У вас есть рабочий шаблон Pygame. Сохраните его в файле с понятным названием, например, pygame_template.py, чтобы можно было использовать его каждый раз при создании нового проекта pygame.
В следующем руководстве этот шаблон будет использован как отправная точка для изучения процесса отрисовки объектов на экране и их движения.
]]>