Создание врагов в играх на Python с библиотекой Arcade

Враги — ключевой элемент многих игр: они создают препятствия, вызывают реакцию у игрока и формируют ощущение достижения. Библиотека Arcade для Python позволяет быстро прототипировать и реализовать поведение врагов с минимальными затратами времени.
Быстрый обзор и цели статьи
Цель этой статьи — дать практическое руководство по созданию врагов разной сложности в Arcade. Вы научитесь:
- сделать простого игрока и статичного врага;
- реализовать врага, преследующего игрока;
- добавить вражеские пули и систему здоровья (HP);
- проверить и отладить поведение врага;
- применить лучшие практики и готовые чеклисты.
Код в примерах доступен для использования и адаптации; оформляйте его в отдельных файлах по мере экспериментов.
Создание простой игры
Перед началом убедитесь, что у вас установлен pip и Python. Установите Arcade одной командой:
pip install arcadeНиже минимальный рабочий пример окна и игрока, который может двигаться влево и вправо. Код — чистый Python с использованием примитивных кругов для наглядности.
import arcade
# Размер окна
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
# Атрибуты игрока
PLAYER_RADIUS = 25
PLAYER_SPEED = 5
class GameWindow(arcade.Window):
def __init__(self, width, height):
super().__init__(width, height)
arcade.set_background_color(arcade.color.WHITE)
self.player_x = width // 2
self.player_y = PLAYER_RADIUS + 10
def on_draw(self):
arcade.start_render()
arcade.draw_circle_filled(self.player_x, self.player_y, PLAYER_RADIUS, arcade.color.BLUE)
def on_key_press(self, key, modifiers):
if key == arcade.key.LEFT:
self.player_x -= PLAYER_SPEED
elif key == arcade.key.RIGHT:
self.player_x += PLAYER_SPEED
def update(self, delta_time):
# Ограничение выхода за пределы экрана
self.player_x = max(PLAYER_RADIUS, min(self.player_x, SCREEN_WIDTH - PLAYER_RADIUS))
def main():
window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT)
arcade.run()
if __name__ == '__main__':
main()Примечание: в реальном проекте лучше использовать arcade.Sprite и встроенную систему столкновений, но для понимания логики простые примитивы подходят лучше.
Создание простого врага
Самый простой враг — ещё один круг на экране. При столкновении с игроком можно выводить сообщение или инициировать окончание уровня.
# Добавляем в класс GameWindow
# Атрибуты врага
ENEMY_RADIUS = 20
class GameWindow(arcade.Window):
# ... предыдущие методы ...
def __init__(self, width, height):
super().__init__(width, height)
arcade.set_background_color(arcade.color.WHITE)
self.player_x = width // 2
self.player_y = PLAYER_RADIUS + 10
self.enemy_x = width // 2
self.enemy_y = height - ENEMY_RADIUS - 10
self.enemy_radius = ENEMY_RADIUS
def on_draw(self):
arcade.start_render()
arcade.draw_circle_filled(self.player_x, self.player_y, PLAYER_RADIUS, arcade.color.BLUE)
if self.enemy_radius > 0:
arcade.draw_circle_filled(self.enemy_x, self.enemy_y, self.enemy_radius, arcade.color.RED)
def update(self, delta_time):
if self.is_collision(self.player_x, self.player_y, self.enemy_x, self.enemy_y, PLAYER_RADIUS, self.enemy_radius):
print('Game Over!')
def is_collision(self, x1, y1, x2, y2, radius1, radius2):
distance_squared = (x1 - x2) 2 + (y1 - y2) 2
radius_sum_squared = (radius1 + radius2) ** 2
return distance_squared <= radius_sum_squaredЭтот подход прост и понятен, но для большинства проектов стоит перейти на спрайты и группы спрайтов — они дают аппаратные преимущества и встроенные методы для столкновений.
Враг, который преследует игрока
Чтобы враг двигался к игроку, обновляйте его позицию в методе update на основе положения игрока. Ниже — простая логика движения только по горизонтали.
# Добавляем преследование в метод update
class GameWindow(arcade.Window):
# ...
def update(self, delta_time):
if self.player_x < self.enemy_x:
self.enemy_x -= PLAYER_SPEED
elif self.player_x > self.enemy_x:
self.enemy_x += PLAYER_SPEED
# Проверка столкновения
if self.is_collision(self.player_x, self.player_y, self.enemy_x, self.enemy_y, PLAYER_RADIUS, self.enemy_radius):
print('Game Over!')Профиль поведения можно усложнять: плавное приближение, ограничение скорости врага, задержки реакции или использование векторного направления.
Добавление вражеских пуль
Чтобы враг стрелял, создайте класс Bullet и список активных снарядов. Время между выстрелами контролируется счётчиком.
class Bullet:
def __init__(self, x, y, radius, speed):
self.x = x
self.y = y
self.radius = radius
self.speed = speed
def update(self):
self.y -= self.speed
class GameWindow(arcade.Window):
def __init__(self, width, height):
super().__init__(width, height)
arcade.set_background_color(arcade.color.WHITE)
self.player_x = width // 2
self.player_y = PLAYER_RADIUS + 10
self.enemy_x = width // 2
self.enemy_y = height - ENEMY_RADIUS - 10
self.bullets = []
self.bullet_radius = 5
self.bullet_speed = 3
self.bullet_cooldown = 60 # кадров между выстрелами
self.bullet_timer = 0
def on_draw(self):
arcade.start_render()
arcade.draw_circle_filled(self.player_x, self.player_y, PLAYER_RADIUS, arcade.color.BLUE)
if self.enemy_radius > 0:
arcade.draw_circle_filled(self.enemy_x, self.enemy_y, self.enemy_radius, arcade.color.RED)
for bullet in self.bullets:
arcade.draw_circle_filled(bullet.x, bullet.y, bullet.radius, arcade.color.BLACK)
def update(self, delta_time):
self.bullet_timer += 1
if self.bullet_timer >= self.bullet_cooldown:
self.bullets.append(Bullet(self.enemy_x, self.enemy_y - self.enemy_radius, self.bullet_radius, self.bullet_speed))
self.bullet_timer = 0
# Обновление пуль и проверка столкновения с игроком
for bullet in list(self.bullets):
bullet.update()
if self.is_collision(self.player_x, self.player_y, bullet.x, bullet.y, PLAYER_RADIUS, bullet.radius):
print('Попадание по игроку!')
self.bullets.remove(bullet)
elif bullet.y < -10:
# Удаляем пули за пределами экрана
self.bullets.remove(bullet)Добавление очков здоровья у врагов
HP (health points) делает врага устойчивым к нескольким попаданиям и вводит дополнительные игровые решения.
# Константы
ENEMY_HEALTH = 100
class GameWindow(arcade.Window):
def __init__(self, width, height):
super().__init__(width, height)
arcade.set_background_color(arcade.color.WHITE)
self.player_x = width // 2
self.player_y = PLAYER_RADIUS + 10
self.enemy_x = width // 2
self.enemy_y = height - ENEMY_RADIUS - 10
self.enemy_health = ENEMY_HEALTH
def on_draw(self):
arcade.start_render()
arcade.draw_circle_filled(self.player_x, self.player_y, PLAYER_RADIUS, arcade.color.BLUE)
if self.enemy_health > 0:
arcade.draw_circle_filled(self.enemy_x, self.enemy_y, ENEMY_RADIUS, arcade.color.RED)
# Отрисовка простой полосы здоровья
health_width = 60
health_ratio = max(0, self.enemy_health / ENEMY_HEALTH)
arcade.draw_rectangle_filled(self.enemy_x, self.enemy_y + ENEMY_RADIUS + 8, health_width * health_ratio, 6, arcade.color.GREEN)
def update(self, delta_time):
# Если игрок сталкивается с врагом, наносим урон врагу
if self.is_collision(self.player_x, self.player_y, self.enemy_x, self.enemy_y, PLAYER_RADIUS, ENEMY_RADIUS):
self.enemy_health -= 10
print('Здоровье врага:', self.enemy_health)
if self.enemy_health <= 0:
print('Враг повержен')Эту логику можно расширять: визуальные эффекты при получении урона, иммунизированные фазы, регенерация и т.д.
Лучшие практики при создании врагов
Разнообразие атрибутов
Создавайте типы врагов с разными скоростями, размерами, здоровьем и силой атаки. Это позволяет формировать уровни разной сложности и вариативность игровой механики.
Уникальное поведение
У каждого типа должен быть свой паттерн поведения: некоторые враги движутся линейно, другие — по шаблону, третьи — адаптивно реагируют на действия игрока. Для сложного поведения используйте стейт-машины или поведенческие деревья.
Использование спрайтов
Перейдите на arcade.Sprite и SpriteList при масштабировании проекта: они оптимизированы по производительности и интегрируют методы столкновений.
Отслеживание состояния
Держите в объекте врага минимальный набор полей: позиция, скорость, HP, состояние (патруль/преследование/атака), таймеры для задержек.
Когда такие подходы не подходят (контрпример)
- Если у вас 1000+ активных врагов на экране, примитивные круги и Python-циклы могут не тянуть — нужна оптимизация через спрайты, групповую обработку и, возможно, C-расширения.
- Если требуется сложный ИИ (поиск пути, навигационные сетки), простая логика перемещения по направлению к игроку будет недостаточна.
Альтернативные подходы
- Использовать arcade.PhysicsEngineSimple/Platformer для управления столкновениями и гравитацией.
- Хранить поведение врагов в отдельном слое данных (компонентный подход) для облегчения тестирования.
- Прототипировать логику в визуальных инструментах (Tiled) и загружать уровни с уже заданными паттернами.
Проверки, тесты и критерии приёмки
Критерии приёмки для базового уровня с врагами:
- Игрок может двигаться и не выходит за границы экрана.
- Враг отображается и корректно реагирует на столкновение с игроком.
- Вражеские пули появляются с заданной периодичностью, двигаются и удаляются вне экрана.
- HP врага уменьшается при попаданиях и отображается корректно.
Тестовые случаи:
- Игрок при контакте с врагом вызывает вывод ‘Game Over!’.
- Враг преследует игрока по горизонтали при его перемещении.
- При длительном времени пули не накапливаются бесконечно (удаление при выходе за пределы).
- HP достигает нуля — враг исчезает.
Роль‑ориентированные чеклисты
Разработчик:
- Код разделён по файлам: main, enemy, bullets, ui.
- Используются спрайты и группы для большого числа врагов.
- Есть простой API для создания новых типов врагов.
Дизайнер уровней:
- Для каждого врага задана документация по параметрам (скорость, HP, поведение).
- Балансировка сложности проверена на 3 типичных уровнях.
Тестировщик:
- Есть набор автоматических тестов на столкновения и поведение пуль.
- Подготовлены ручные сценарии воспроизведения багов (lag, overlap, multiple hits).
Мини‑методология для внедрения врагов в проект
- Прототип: используйте круги для быстрой проверки механики. 2. Миграция: замените круги на arcade.Sprite и SpriteList. 3. Оптимизация: профилируйте обновления, сокращайте переборы списков. 4. Баланс: настраивайте HP, силу и частоту выстрелов в зависимости от уровня.
Простая модель принятия решений (Mermaid)
flowchart TD
A[Игрок обнаружил врага?] -->|Да| B{Дистанция < атака}
B -->|Да| C[Атаковать]
B -->|Нет| D{Дистанция < преследование}
D -->|Да| E[Преследовать]
D -->|Нет| F[Патрулировать]
A -->|Нет| FСоветы по совместимости и миграции
- Тестируйте проект на версии Python, которую вы используете в продакшне (рекомендуется Python 3.8 или новее).
- Проверяйте совместимость версии Arcade в среде разработки и на целевых платформах.
- При переходе на спрайты убедитесь, что все размеры и anchor точки корректно настроены.
Риски и простые митигаторы
- Производительность при большом числе объектов — используйте SpriteList и ограничивайте частоту обновлений.
- Сложный ИИ — вводите поведение постепенно и покрывайте модульными тестами.
- Неправильные столкновения из‑за некорректных радиусов — логируйте и визуализируйте bounding box для дебага.
Краткое резюме
Враги делают игру интереснее и глубже. Начните с простых кругов или спрайтов, добавьте поведение преследования, пули и систему HP. Переходите на спрайты и оптимизации по мере роста проекта. Используйте чеклисты для разработки, дизайна и тестирования, чтобы поддерживать качество и воспроизводимость.
Важно: адаптируйте скорость, здоровье и частоту атак под целевую аудиторию — для казуальной игры параметры и механики будут отличаться от хардкорного шутера.
Полезные заметки
Fact box с ключевыми константами, использованными в примерах:
- SCREEN_WIDTH = 800, SCREEN_HEIGHT = 600
- PLAYER_RADIUS = 25, PLAYER_SPEED = 5
- ENEMY_RADIUS = 20, ENEMY_HEALTH = 100
- Bullet: radius = 5, speed = 3, cooldown = 60 кадров
Небольшой чек для быстрого дебага: при проблемах с коллизией временно рисуйте линии между центрами и выводите расстояние в лог.
Спасибо за чтение — используйте примеры как отправную точку и расширяйте их под вашу игру.
Похожие материалы
Не удалось инициализировать Direct3D — решения
Исправить Data Retrieval в Diablo 4 на Steam
Open Graph в WordPress — настройка мета‑тегов
getconf: адаптивные скрипты для разных Linux
Проверка входов в Windows — успешные и неудачные попытки