Данная статья является своеобразным продолжением темы об играх, которую мы начали с руководства по созданию игры Arkanoid.
С исходным кодом вы можете ознакомиться по ссылке.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Содержание статьи
- Скелет основного кода игры
- Механика выстрела из танка
- Выстрел в фоновом режиме
- Добавление зомби в игре
- Перемещение зомби
- Создание экрана «Игра окончена»
- Как можно улучшить игру?
На этот раз наша игра на Python будет немного (только немного!) сложнее:
Новые аспекты, с которыми мы познакомимся в данном руководстве:
- Танк будет менять свое направление при нажатии стрелок на клавиатуре;
- Танк будет стрелять. Внедрить эту возможность может оказаться намного сложнее, чем вы могли предположить;
- Каждый выстрел будет сопровождаться звуком;
- Зомби будут обладать некоторым «искусственным интеллектом», то есть они будут пытаться двигаться в сторону игрока;
- Счет будет отображаться на экране. Также в игре будет отдельный экран, который будет выводиться после завершения игры.
Приступим! Поскольку данная статья основана на предыдущей записи (игра Арканоид), я не буду объяснять код, который я объяснил в той записи. Например, как работают клавиши со стрелками.
Скелет основного кода игры
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import pgzrun import random TITLE = "Zombies vs Tanks" WIDTH = 800 HEIGHT = 640 def draw(): screen.blit("tank.png", (0,0)) pass def update(): pass pgzrun.go() |
Это базовый код, который просто откроет экран и отобразит фоновое изображение.
Давайте отобразим танк:
1 2 3 4 5 6 7 |
blue_tank = Actor("tank_blue") blue_tank.x = WIDTH/2 blue_tank.y = HEIGHT/2 def draw(): screen.blit("tank.png", (0,0)) blue_tank.draw() |
И добавим код для реагирования по нажатиям клавиш клавиатуры:
1 2 3 4 5 6 7 8 9 |
def update(): if keyboard.left: blue_tank.x = blue_tank.x - 5 if keyboard.right: blue_tank.x = blue_tank.x + 5 if keyboard.up: blue_tank.y = blue_tank.y - 5 if keyboard.down: blue_tank.y = blue_tank.y + 5 |
Проверим, что получилось:
Танк всегда смотрит передней частью вниз, независимо от того, в какую сторону мы движемся. Давайте это исправим.
В верхней части кода добавьте эти углы разворота:
1 2 3 4 |
UP = 180 DOWN = 0 LEFT = 270 RIGHT = 90 |
Поскольку танк уже смотрит вниз, угол для движения вниз равен 0.
Аналогично, чтобы переместить танк дулом вверх, нам нужно повернуть изображение на 180 градусов.
Эти значения будут зависеть от вашего исходного изображения, поэтому вам придется поиграть с ними.
Давайте обновим код, чтобы он применял эти углы:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def update(): if keyboard.left: blue_tank.x = blue_tank.x - 5 blue_tank.angle = LEFT if keyboard.right: blue_tank.x = blue_tank.x + 5 blue_tank.angle = RIGHT if keyboard.up: blue_tank.y = blue_tank.y - 5 blue_tank.angle = UP if keyboard.down: blue_tank.y = blue_tank.y + 5 blue_tank.angle = DOWN |
Таким образом, помимо перемещения танка, мы изменяем угол его наклона в зависимости от того, какую клавишу мы нажали. Посмотрим, что получается:
Отлично!
Механика выстрела из танка
Заставить танк стрелять оказалось не так просто. Главная причина в том, что нам нужно, чтобы пуля продолжала лететь в фоновом режиме, пока танки (а позже и зомби) двигаются.
Это означает, что мы должны обрабатывать состояние пули в фоновом режиме. К счастью, для этого есть отличный способ. Но прежде давайте определим графику для пули:
1 2 |
bullet = Actor("bulletblue") bullet_fired = True |
У нас есть переменная под названием bullet_fired
. Позже я расскажу, зачем она нам нужна.
Мы будем стрелять пулей при нажатии пробела. Код для этого следующий:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
if keyboard.space: if not bullet_fired: bullet_fired = True if blue_tank.angle == LEFT: bullet.x = blue_tank.x-30 bullet.y = blue_tank.y elif blue_tank.angle == RIGHT: bullet.x = blue_tank.x+30 bullet.y = blue_tank.y elif blue_tank.angle == DOWN: bullet.x = blue_tank.x bullet.y = blue_tank.y + 30 elif blue_tank.angle == UP: bullet.x = blue_tank.x bullet.y = blue_tank.y - 30 |
Это может показаться сложным, поэтому я расскажу обо всем по частям.
1 2 3 |
if blue_tank.angle == LEFT: bullet.x = blue_tank.x-30 bullet.y = blue_tank.y |
Если танк стоит передней частью влево, то пуля должна выпущена немного левее от него.
Если я не пропишу эту строку: bullet.x = blue_tank.x-30
, пуля появится в середине танка. В нашем случае создается впечатление, что танк выпустил пулю. Давайте снова посмотрим на код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
if keyboard.space: if not bullet_fired: bullet_fired = True if blue_tank.angle == LEFT: bullet.x = blue_tank.x-30 bullet.y = blue_tank.y elif blue_tank.angle == RIGHT: bullet.x = blue_tank.x+30 bullet.y = blue_tank.y elif blue_tank.angle == DOWN: bullet.x = blue_tank.x bullet.y = blue_tank.y + 30 elif blue_tank.angle == UP: bullet.x = blue_tank.x bullet.y = blue_tank.y - 30 |
Все, что мы делаем, это проверяем направление движения танка и рисуем пулю перед ним. Это не обязательно, просто выглядит красиво
Мы также будем воспроизводить звук выстрела, когда пуля выпущена.
1 2 3 4 |
if keyboard.space: if not bullet_fired: bullet_fired = True sounds.laserretro_004.play() ==> new code |
Атрибут laserretro_004
— это название нашего звукового файла в папке sounds
.
Протестируйте его:
Пуля появляется перед танком, но больше ничего не делает…
Выстрел в фоновом режиме
Как мы уже говорили, стрельба должна выполняться в фоновом режиме, поскольку мы хотим, чтобы игрок (а затем и зомби) продолжал двигаться.
В Pygame Zero есть функция, позволяющая планировать выполнение задач в фоновом режиме.
1 |
clock.schedule(shoot_bullet, 5) |
Метод clock.schedule()
планирует выполнение задачи в фоновом режиме. Первое значение — это функция (которую мы напишем). Второе — время в секундах когда она должна быть выполнена повторно.
Таким образом, каждые 5 секунд Pygame Zero будет вызывать нашу функцию shoot_bullet
. Давайте напишем эту функцию.
Сперва проверим переменную bullet_fired
. Мы хотим запускать эту функцию только в том случае, если пользователь нажал пробел, то есть выстрелил пулей. Поскольку функция работает в фоновом режиме, она ничего не будет делать, пока не будет нажата клавиша.
1 2 3 4 5 6 7 8 9 10 |
def shoot_bullet(): if bullet_fired: if blue_tank.angle == LEFT: bullet.x -= BULLET_SPEED elif blue_tank.angle == RIGHT: bullet.x += BULLET_SPEED elif blue_tank.angle == DOWN: bullet.y += BULLET_SPEED elif blue_tank.angle == UP: bullet.y -= BULLET_SPEED |
И после этого, в зависимости от того, в какую сторону направлен танк, мы будем менять местоположение пули. Например:
1 2 |
if blue_tank.angle == LEFT: bullet.x -= BULLET_SPEED |
Поэтому, если танк обращен влево, мы будем изменять ось x пули на -10
при каждом вызове функции. Это создаст впечатление, что пуля летит.
Также нам нужно проверить, не улетела ли пуля за пределы экрана:
1 2 3 |
if bullet.x >= WIDTH or bullet.x <=0 or \ bullet.y >=HEIGHT or bullet.y <=0: bullet_fired = False |
Если пуля выходит за пределы экрана, мы устанавливаем значение переменной bullet_fired
на False
. Это означает, что функция не будет запущена, пока пользователь снова не нажмет пробел.
Давайте проверим, как это работает.
Превосходно.
Добавление зомби в игре
Немного базовых настроек:
1 2 3 |
zombie_list = [] ZOMBIE_SPEED = 1 score = 0 |
Мы создаем список для хранения зомби и устанавливаем их скорость. Также создадим переменную для хранения счета игры.
Затем добавим это в функцию draw()
1 2 3 |
clock.schedule(create_zombies, 5) move_zombie() screen.draw.text(f"score: {score} ", (350, 150)) |
Мы запланировали функцию для создания зомби каждые 5 секунд, другую — для их перемещения, и, наконец — для отображения счета. Давайте рассмотрим все по очереди.
Создание зомби в игре
1 2 3 |
def create_zombies(): if len(zombie_list ) < 10 : loc_rand = random.randint(0,3) |
Сначала мы проверим длину списка zombie_list
, поскольку нам нужно только 10 зомби за раз. Вы можете изменить это число — от него будет зависеть сложность игры.
Затем нам нужно выбрать случайное число от 0 до 3.
- Если случайное число равно 0, зомби генерируется в верхней части экрана;
- Если 1, то зомби появляется в правой части экрана;
- Если 2 и 3 — внизу и слева.
Это обеспечит случайное появление зомби по всему экрану, а не только в одной точке. Весь код выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
def create_zombies(): if len(zombie_list ) < 10 : loc_rand = random.randint(0,3) if loc_rand == 0: y = random.randint(40, HEIGHT-40) z = Actor("zombie_stand.png") z.x = 1 z.y = y zombie_list.append(z) elif loc_rand == 1: y = random.randint(40, HEIGHT-40) z = Actor("zombie_stand.png") z.x = WIDTH-1 z.y = y zombie_list.append(z) elif loc_rand == 2: x = random.randint(40, WIDTH-40) z = Actor("zombie_stand.png") z.y = 1 z.x = x zombie_list.append(z) elif loc_rand == 3: x = random.randint(40, WIDTH-40) z = Actor("zombie_stand.png") z.y = HEIGHT - 1 z.x = x zombie_list.append(z) |
Я создаю случайных зомби в одном из четырех углов экрана. Давайте рассмотрим следующую часть кода:
1 2 3 4 5 6 |
if loc_rand == 0: y = random.randint(40, HEIGHT-40) z = Actor("zombie_stand.png") z.x = 1 z.y = y zombie_list.append(z) |
Мы говорим: Выберите положение y зомби случайным образом в точке от 40 пикселей до (HEIGHT-40) пикселей. Точка x
просто устанавливается равной 1
— она тоже может быть случайной.
Затем мы добавляем только что созданного зомби в наш список.
То же самое мы делаем для всех остальных направлений.
Перемещение зомби
Сначала разберем наш «искусственный интеллект». Он довольно глупый, но ведь и зомби не отличаются особым умом
Зомби будут пытаться двигаться в сторону танка. Поскольку мы знаем его местоположение, мы изменим положение зомби таким образом, чтобы они пытались следовать за танком.
1 2 3 4 5 6 7 8 9 10 |
def move_zombie(): for zomb in zombie_list: if zomb.x < blue_tank.x: zomb.x += ZOMBIE_SPEED elif zomb.x > blue_tank.x: zomb.x -= ZOMBIE_SPEED elif zomb.y < blue_tank.y: zomb.y += ZOMBIE_SPEED elif zomb.y > blue_tank.y: zomb.y -= ZOMBIE_SPEED |
Обратите внимание на эту часть кода:
1 2 |
if zomb.x < blue_tank.x: zomb.x += ZOMBIE_SPEED |
Так что если значение x
зомби находится слева от танка, мы переместим его вправо (увеличив его x
). И наоборот.
То же самое для движения вверх/вниз — мы проверяем y
танка и обновляем это значение для зомби.
Далее нам нужно проверить, получил ли зомби удар от танка:
1 2 |
for zomb in zombie_list: zomb.draw() |
Мы перебираем зомби с помощью цикла и рисуем их:
1 2 3 |
if zomb.colliderect(bullet): zombie_list.remove(zomb) score += 1 |
Если зомби сталкивается с пулей, мы удаляем зомби из списка и увеличиваем счет на +1
.
1 2 |
if zomb.colliderect(blue_tank): game_over = True |
Однако, если зомби столкнется с танком, игра закончится.
Давайте реализуем экран для завершения игры:
Создание экрана «Игра окончена»
Обновим функцию draw()
, чтобы она вызывала метод draw
только в том случае, если игра не закончилась:
1 2 3 4 5 6 7 8 9 |
def draw(): if not game_over: screen.blit("tank.png", (0,0)) blue_tank.draw() clock.schedule(shoot_bullet, 5) clock.schedule(create_zombies, 5) bullet.draw() move_zombie() screen.draw.text(f"score: {score} ", (350, 150)) |
Нам также нужно указать условие для завершения игры:
1 2 3 |
else: screen.fill("blue") screen.draw.text(f"GAME OVER, Your score: {score} ", (350, 150)) |
Если игра закончилась, экран окрашивается синим цветом и выводится надпись Game Over.
Давайте протестируем код:
Вот и все!
Как можно улучшить игру?
Если вы хотите бросить себе вызов, то можете попробовать улучшить игру. Помните, что код доступен по ссылке: https://github.com/shantnu/zombie_tanks.
- В игре есть баг — попробуйте выстрелить. Пуля движется вместе с танком! Попробуйте исправить это;
- Добавьте начальный экран, на котором будет отображаться уровень сложности — вы можете увеличить количество и скорость зомби;
- Чтобы сделать ИИ более забавным, добавьте несколько зомби, которые двигаются случайным образом, а не всегда следуют за игроком.
- Добавьте возможность перезапуска игры.
Статья о создании новой игры появится уже через несколько недель! Готовьтесь!
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»