Добавление случайно движущихся объектов в игру на Python Arcade

Введение
Случайно движущиеся объекты добавляют в игры элемент непредсказуемости и динамики. Игроки реагируют на появляющиеся угрозы и возможности, а правильная настройка поведения объектов определяет сложность и удовольствие от игры.
Этот материал шаг за шагом объясняет:
- как создать простое окно Arcade и отрисовать игрока;
- как добавлять множество объектов и заставлять их двигаться случайно;
- как направлять объекты к игроку и включать их только в зоне досягаемости;
- как обнаруживать столкновения и балансировать механику спавна и скорости.
Важно: примеры используют библиотеку arcade; установите её командой pip install arcade.
Что понадобится
- Python 3.7+;
- установленный пакет arcade (pip install arcade);
- базовые знания Python (классы, списки, циклы);
- опционально: GitHub-репозиторий с кодом (авторский код под MIT в исходном материале).
Основные понятия (в одну строку)
- Sprite — объект для отрисовки и коллизий (здесь мы используем простые круги);
- delta_time — время между кадрами; полезно для согласованного движения;
- нормализация вектора — представление направления с длиной 1 для стабильной скорости.
Создание простого окна и игрока
Ниже минимальный рабочий пример: создаём окно, рисуем синюю окружность — игрока, и управляем им стрелками.
import arcade
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
PLAYER_RADIUS = 15
class MyGame(arcade.Window):
def __init__(self, width, height):
super().__init__(width, height)
arcade.set_background_color(arcade.color.WHITE)
self.player_x = SCREEN_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 update(self, delta_time):
pass
def on_key_press(self, key, modifiers):
if key == arcade.key.LEFT:
self.player_x -= 5
elif key == arcade.key.RIGHT:
self.player_x += 5
if __name__ == "__main__":
game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT)
arcade.run()Совет: для плавного движения умножайте смещение на delta_time и используйте флаги клавиш (on_key_release / on_key_press) вместо мгновенных прыжков по 5 пикселей.
Добавление нескольких объектов
Создаём список объектов с случайными координатами и рисуем их как красные круги.
import arcade
import random
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
PLAYER_RADIUS = 15
OBJECT_RADIUS = 10
NUM_OBJECTS = 10
class MyGame(arcade.Window):
def __init__(self, width, height):
super().__init__(width, height)
arcade.set_background_color(arcade.color.WHITE)
self.player_x = SCREEN_WIDTH // 2
self.player_y = PLAYER_RADIUS + 10
self.objects = []
for _ in range(NUM_OBJECTS):
x = random.randint(0, SCREEN_WIDTH)
y = random.randint(0, SCREEN_HEIGHT)
self.objects.append((x, y))
def on_draw(self):
arcade.start_render()
arcade.draw_circle_filled(self.player_x, self.player_y, PLAYER_RADIUS, arcade.color.BLUE)
for obj in self.objects:
x, y = obj
arcade.draw_circle_filled(x, y, OBJECT_RADIUS, arcade.color.RED)
def update(self, delta_time):
pass
def on_key_press(self, key, modifiers):
if key == arcade.key.LEFT:
self.player_x -= 5
elif key == arcade.key.RIGHT:
self.player_x += 5
if __name__ == "__main__":
game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT)
arcade.run()Реализация случайного движения
Чтобы объекты перемещались случайно, в методе update генерируйте случайный dx и dy и прибавляйте к текущим координатам.
def update(self, delta_time):
for i in range(len(self.objects)):
x, y = self.objects[i]
dx = random.randint(-5, 5)
dy = random.randint(-5, 5)
x += dx
y += dy
self.objects[i] = (x, y)Такой подход прост, но может привести к резким скачкам и выходу объектов за границы экрана — обработайте границы (clamp) или отталкивание.
Движение объектов навстречу игроку
Чтобы объекты тянулись к игроку, вычислите вектор разницы, нормализуйте его и переместите объект по направлению с заданной скоростью.
import math
def update(self, delta_time):
for i in range(len(self.objects)):
x, y = self.objects[i]
dx = self.player_x - x
dy = self.player_y - y
distance = math.sqrt(dx 2 + dy 2)
if distance == 0:
continue
dx /= distance
dy /= distance
x += dx * 3
y += dy * 3
self.objects[i] = (x, y)Учтите деление на ноль — если distance == 0, пропустите обновление или обработайте столкновение.
Активация объектов в зоне вокруг игрока
Иногда нужно, чтобы объекты становились активными только в пределах радиуса. Это экономит вычисления и делает поведение более контролируемым.
def update(self, delta_time):
for i in range(len(self.objects)):
x, y = self.objects[i]
dx = self.player_x - x
dy = self.player_y - y
distance = math.sqrt(dx 2 + dy 2)
if distance < 100: # радиус активации
if distance == 0:
continue
dx /= distance
dy /= distance
x += dx * 3
y += dy * 3
self.objects[i] = (x, y)Датчики столкновений и взаимодействие
Добавим обработку столкновений: при столкновении удаляем объект и создаём новый в случайной позиции.
def update(self, delta_time):
for i in range(len(self.objects)-1, -1, -1): # идти в обратном порядке при удалении
x, y = self.objects[i]
dx = self.player_x - x
dy = self.player_y - y
distance = math.sqrt(dx 2 + dy 2)
if distance < PLAYER_RADIUS + OBJECT_RADIUS:
# столкновение: удаляем и сразу спавним новый объект
self.objects.pop(i)
self.objects.append((random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT)))
elif distance < 100:
dx /= distance
dy /= distance
x += dx * 3
y += dy * 3
self.objects[i] = (x, y)Важно: при удалении элементов из списка лучше итерироваться в обратном порядке или собирать индексы для удаления, чтобы избежать пропуска элементов.
Балансировка случайности
Правильная настройка параметров — ключ к хорошему геймплею. Ниже — несколько шаблонов для контроля скорости, ограничения и спавна.
Ограничение максимальной скорости
Чтобы объекты не прыгали слишком быстро, используйте MAX_SPEED и clamp для компонентов скорости.
MAX_SPEED = 5
# внутри update
speed = 3
vx = dx * speed
vy = dy * speed
vx = max(-MAX_SPEED, min(MAX_SPEED, vx))
vy = max(-MAX_SPEED, min(MAX_SPEED, vy))
x += vx
y += vyКонтроль частоты появления (spawn rate)
Добавьте таймер спавна, чтобы ограничить, как часто появляются новые объекты.
import time
SPAWN_DELAY = 2.0 # секунды
MAX_OBJECTS = 20
class MyGame(arcade.Window):
def __init__(self, width, height):
# ...
self.last_spawn_time = time.time()
def update(self, delta_time):
if time.time() - self.last_spawn_time > SPAWN_DELAY:
if len(self.objects) < MAX_OBJECTS:
self.objects.append((random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT)))
self.last_spawn_time = time.time()
# обновление объектов далееРегулируйте SPAWN_DELAY и MAX_OBJECTS, чтобы снизить или повысить сложность.
Полный пример: объединённый шаблон
Ниже — сокращённый, но рабочий пример, объединяющий ключевые механики: движение игрока, спавн, нормализация, ограничение скорости и корректное удаление при столкновении.
import arcade
import random
import math
import time
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
PLAYER_RADIUS = 15
OBJECT_RADIUS = 10
MAX_SPEED = 4
SPAWN_DELAY = 1.5
MAX_OBJECTS = 15
class MyGame(arcade.Window):
def __init__(self, width, height):
super().__init__(width, height)
arcade.set_background_color(arcade.color.WHITE)
self.player_x = SCREEN_WIDTH // 2
self.player_y = PLAYER_RADIUS + 10
self.keys = set()
self.objects = []
for _ in range(8):
self.objects.append((random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT)))
self.last_spawn_time = time.time()
def on_draw(self):
arcade.start_render()
arcade.draw_circle_filled(self.player_x, self.player_y, PLAYER_RADIUS, arcade.color.BLUE)
for x, y in self.objects:
arcade.draw_circle_filled(x, y, OBJECT_RADIUS, arcade.color.RED)
def on_key_press(self, key, modifiers):
self.keys.add(key)
def on_key_release(self, key, modifiers):
self.keys.discard(key)
def update(self, delta_time):
# движение игрока
if arcade.key.LEFT in self.keys:
self.player_x -= 200 * delta_time
if arcade.key.RIGHT in self.keys:
self.player_x += 200 * delta_time
# спавн
if time.time() - self.last_spawn_time > SPAWN_DELAY and len(self.objects) < MAX_OBJECTS:
self.objects.append((random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT)))
self.last_spawn_time = time.time()
# обновление и коллизии (обход в обратном порядке)
for i in range(len(self.objects) - 1, -1, -1):
x, y = self.objects[i]
dx = self.player_x - x
dy = self.player_y - y
dist = math.hypot(dx, dy)
if dist < PLAYER_RADIUS + OBJECT_RADIUS:
self.objects.pop(i)
continue
if dist < 120 and dist != 0:
nx = dx / dist
ny = dy / dist
vx = max(-MAX_SPEED, min(MAX_SPEED, nx * 3))
vy = max(-MAX_SPEED, min(MAX_SPEED, ny * 3))
x += vx
y += vy
self.objects[i] = (x, y)
if __name__ == '__main__':
game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT)
arcade.run()Советы по отладки и оптимизации
- Проверяйте границы экрана и используйте clamp, чтобы объекты не уходили за пределы видимой области.
- Для большого количества объектов переходите на arcade.Sprite и sprite lists — они оптимизированы по отрисовке и коллизиям.
- Итерируйтесь в обратном порядке при удалении элементов из списка.
- Используйте delta_time для детерминированного движения на разных FPS.
Альтернативные подходы
- Использовать физический движок (pymunk) для реалистичных столкновений и отскоков.
- Хранить состояние объектов в numpy-массиве для векторизованных обновлений (оптимизация при большом числе объектов).
- Для визуализации и коллизий использовать sprite-систему Arcade вместо ручных кругов.
Когда такой подход не подходит
- Когда требуется точная физика со столкновениями и импульсами — лучше интегрировать сторонний физический движок.
- Когда объектов очень много (> тысячи) — нужен другой подход к хранению и обновлению (векторизация, spatial partitioning).
Быстрая методика (mini-methodology)
- Начните с простого списка координат и визуализации.
- Добавьте нормализацию направления для контролируемой скорости.
- Введите ограничения скорости и радиусы активации.
- Перейдите на Sprite-листы при проблемах с производительностью.
- Тестируйте границы и экстремальные случаи (distance=0, пустые списки).
Чек-лист для разработчика
- Установлен arcade
- Игрок плавно двигается с delta_time
- Объекты корректно спавнятся и не выходят за экран
- Коллизии обрабатываются без ошибок при удалении
- Ограничение скорости не позволяет «телепортам»
- Пределы SPAWN_DELAY и MAX_OBJECTS настроены под желаемую сложность
Тест-кейсы и критерии приёмки
- При столкновении объект исчезает и создаётся новый (или счёт увеличивается).
- Объекты в зоне активации двигаются к игроку, вне зоны — стоят на месте.
- При увеличении NUM_OBJECTS и SPAWN_DELAY игра остаётся отзывчивой.
- Нет ошибок деления на ноль при совпадении координат.
Короткие советы по UX и игровому дизайну
- Смешивайте поведение: 60% объектов — пассивные, 40% — агрессивные.
- Используйте разные скорости и радиусы активации для разнообразия.
- Дайте игроку инструменты (ускорение, щит), чтобы взаимодействие было значимым.
Important: всегда профилируйте и проверяйте производительность на целевых устройствах.
Краткое резюме
Добавление случайно движущихся объектов в игру с помощью Arcade — простой путь сделать игру динамичнее. Начните с базовых списков координат, затем добавляйте нормализацию, контроль скорости, обработку столкновений и ограничение спавна. При увеличении масштаба переходите на оптимизированные структуры данных и системные подходы.
Ключевые идеи: нормализуйте векторы, избегайте деления на ноль, итерируйтесь в обратном порядке при удалении и используйте delta_time для согласованного движения на разных частотах кадров.
Похожие материалы
Как смотреть HBO из Европы — VPN и подарочные карты
Удаление аккаунта SoundCloud — инструкция
Лучшие приложения для отслеживания сериалов
Подключение Xbox One контроллера к Mac
Где смотреть «Дом дракона»