Пишем игру на Python: неоновая аркада с разбором кода и ИИ

Пишем на Python и Pygame неоновую аркаду Neon Void: полный разбор кода, частицы, тряска экрана, волны врагов и практические промпты для разработки игр с ИИ.

Pygame часто считают библиотекой для школьных проектов, и зря. На нём за вечер собирается динамичная аркада с неоновой графикой, частицами, тряской камеры и волнами врагов, которая выглядит как маленькая инди-игра. В этой статье мы напишем такую игру на Python с нуля, разберём каждый важный кусок кода и покажем, как ускорить разработку с помощью ИИ-ассистентов и грамотных промптов. В конце вас ждёт полный код, который можно скопировать и сразу запустить.

Что получится в итоге

Игра называется Neon Void. Это твин-стик шутер в духе Geometry Wars: вы управляете светящимся кораблём на WASD, целитесь мышью, стреляете левой кнопкой и уходите от врагов рывком на пробел. Противники лезут со всех сторон волнами, каждая следующая быстрее и плотнее, а с третьей волны появляются жёлтые «танки», которым нужно шесть попаданий. За серию убийств растёт множитель очков вплоть до x10, а пропущенный удар сбрасывает его. Весь проект умещается в один файл примерно на 340 строк без картинок и внешних ассетов: вся графика рисуется кодом.

Установка и запуск

Понадобится Python 3.10 или новее и библиотека Pygame. Установите её одной командой, сохраните код из конца статьи в файл neon_void.py и запустите.

pip install pygame
python neon_void.py

Если под вашу версию Python нет готовой сборки pygame, поставьте активно развиваемый форк pygame-ce командой pip install pygame-ce. Код совместим с обоими вариантами без изменений.

Игровой цикл и delta time

Любая игра крутится в бесконечном цикле из трёх шагов: обработать ввод, обновить состояние мира и нарисовать кадр. У нас за это отвечает метод run класса Game.

def run(self):
    running = True
    while running:
        # dt в секундах: игра работает одинаково на 60 и на 144 Гц
        dt = min(self.clock.tick(FPS) / 1000, 1 / 30)
        running = self.handle_events()
        self.update(dt)
        self.draw()
    pygame.quit()
    sys.exit()

Главная деталь здесь переменная dt, время кадра в секундах. Все скорости в игре заданы в пикселях в секунду и умножаются на dt, поэтому игра идёт одинаково на мониторе 60 Гц и 144 Гц. Ограничение min(…, 1 / 30) защищает от ситуации, когда окно перетащили, игра подвисла на секунду и враги за один кадр телепортировались сквозь игрока.

Неоновое свечение без шейдеров

Эффект свечения обычно делают шейдерами, но в Pygame можно обойтись хитростью. Функция glow_circle рисует на прозрачной поверхности несколько полупрозрачных кругов убывающего размера, а сверху яркое ядро.

def glow_circle(radius, color, layers=4):
    """Рисует круг с мягким свечением на отдельной поверхности."""
    size = radius * 4
    surf = pygame.Surface((size, size), pygame.SRCALPHA)
    center = (size // 2, size // 2)
    for i in range(layers, 0, -1):
        alpha = int(60 / i)
        pygame.draw.circle(surf, (*color, alpha), center, radius + i * radius // 2)
    pygame.draw.circle(surf, color, center, radius)
    return surf

При отрисовке спрайт выводится с флагом pygame.BLEND_ADD: цвета не перекрывают друг друга, а складываются, поэтому пересекающиеся объекты светятся ярче, как настоящий неон. Спрайт создаётся один раз в конструкторе объекта, а не на каждом кадре, и это заметно экономит ресурсы.

Управление, которое приятно чувствовать

Если просто присваивать скорость по нажатию клавиши, корабль двигается как на шарнирах. Мы плавно тянем текущую скорость к целевой, и управление сразу становится «дорогим» и отзывчивым.

def update(self, dt, keys):
    direction = Vector2(
        keys[pygame.K_d] - keys[pygame.K_a],
        keys[pygame.K_s] - keys[pygame.K_w],
    )
    if direction.length_squared() > 0:
        direction = direction.normalize()

    self.dash_cooldown = max(0.0, self.dash_cooldown - dt)
    self.invulnerable = max(0.0, self.invulnerable - dt)
    self.fire_timer = max(0.0, self.fire_timer - dt)

    if self.dash_timer > 0:
        self.dash_timer -= dt
    else:
        # Плавное ускорение вместо мгновенной скорости: так управление ощущается "дорогим"
        target = direction * self.SPEED
        self.vel += (target - self.vel) * min(1.0, 12 * dt)

    self.pos += self.vel * dt
    self.pos.x = max(self.radius, min(WIDTH - self.radius, self.pos.x))
    self.pos.y = max(self.radius, min(HEIGHT - self.radius, self.pos.y))

Нормализация вектора направления нужна, чтобы по диагонали корабль не летал быстрее, чем по прямой. Рывок устроен ещё проще: на 0.12 секунды скорость резко вырастает до 1400 пикселей в секунду, а игрок получает короткую неуязвимость. Этот приём знаком по Hades и Enter the Gungeon, он превращает уклонение в навык, а не в удачу.

def dash(self):
    if self.dash_cooldown > 0 or self.vel.length_squared() < 1:
        return False
    self.vel = self.vel.normalize() * self.DASH_SPEED
    self.dash_timer = self.DASH_TIME
    self.dash_cooldown = self.DASH_COOLDOWN
    self.invulnerable = max(self.invulnerable, self.DASH_TIME + 0.05)
    return True

В стрельбе есть маленький, но важный штрих: случайный разброс в несколько сотых радиана через rotate_rad. Очередь перестаёт выглядеть как лазерная линия и становится живой.

Враги и система волн

Функция spawn_enemy выбирает случайную сторону экрана и создаёт противника чуть за её краем, чтобы он не появлялся прямо перед носом игрока. Скорость обычных врагов растёт с номером волны, а с третьей волны с шансом 20% приходит медленный, но толстый танк.

def spawn_enemy(wave):
    """Враг появляется за краем экрана. Чем выше волна, тем злее противники."""
    side = random.choice(("top", "bottom", "left", "right"))
    margin = 40
    if side == "top":
        pos = (random.uniform(0, WIDTH), -margin)
    elif side == "bottom":
        pos = (random.uniform(0, WIDTH), HEIGHT + margin)
    elif side == "left":
        pos = (-margin, random.uniform(0, HEIGHT))
    else:
        pos = (WIDTH + margin, random.uniform(0, HEIGHT))

    if wave >= 3 and random.random() < 0.2:
        return Enemy(pos, speed=70 + wave * 4, hp=6, radius=26, color=YELLOW)
    return Enemy(pos, speed=120 + wave * 10, hp=1, radius=12, color=MAGENTA)

Метод update_waves выпускает врагов по одному с интервалом, который тоже сокращается с каждой волной. Когда все противники волны уничтожены, начинается следующая, и в ней врагов становится на три больше. Такая простая формула даёт плавный рост сложности без ручной настройки уровней.

def update_waves(self, dt):
    if self.to_spawn > 0:
        self.spawn_timer -= dt
        if self.spawn_timer <= 0:
            self.enemies.append(spawn_enemy(self.wave))
            self.to_spawn -= 1
            self.spawn_timer = max(0.15, 0.8 - self.wave * 0.05)
    elif not self.enemies:
        self.wave += 1
        self.to_spawn = 6 + self.wave * 3

Частицы, тряска экрана и комбо

Именно эти мелочи отличают сочную аркаду от учебного примера. Каждая частица хранит позицию, скорость и время жизни, замедляется на каждом кадре и тускнеет по мере угасания.

@dataclass
class Particle:
    pos: Vector2
    vel: Vector2
    color: tuple
    life: float
    max_life: float = field(init=False)

    def __post_init__(self):
        self.max_life = self.life

    def update(self, dt):
        self.pos += self.vel * dt
        self.vel *= 0.92
        self.life -= dt
        return self.life > 0

    def draw(self, screen, offset):
        k = self.life / self.max_life
        radius = max(1, int(4 * k))
        color = tuple(int(c * k) for c in self.color)
        pygame.draw.circle(screen, color, self.pos + offset, radius)

Метод burst разбрасывает пачку частиц во все стороны при взрыве врага, получении урона и рывке. Для тряски мы не двигаем объекты, а считаем общее смещение offset, которое добавляется ко всем координатам при отрисовке. Сила тряски зависит от события: мелкий враг почти не трясёт экран, танк встряхивает заметнее, а удар по игроку ощущается по-настоящему. Белая вспышка hit_flash на 50 миллисекунд при попадании даёт мгновенную обратную связь, что пуля долетела.

def draw(self):
    offset = Vector2()
    if self.shake > 0:
        power = self.shake * 40
        offset = Vector2(random.uniform(-power, power), random.uniform(-power, power))

    self.screen.fill(BG_COLOR)
    self.draw_grid(offset)
    for p in self.particles:
        p.draw(self.screen, offset)
    for b in self.bullets:
        pygame.draw.circle(self.screen, YELLOW, b.pos + offset, b.radius)
    for e in self.enemies:
        e.draw(self.screen, offset)
    if not self.game_over:
        self.player.draw(self.screen, offset)
    self.draw_hud()
    pygame.display.flip()

Коллизии

Все объекты в игре круглые, поэтому проверка столкновений сводится к одному сравнению: расстояние между центрами меньше суммы радиусов. Обратите внимание, что мы перебираем копии списков через срез [:], ведь удалять элементы из списка во время его обхода нельзя.

def handle_collisions(self):
    for enemy in self.enemies[:]:
        for bullet in self.bullets[:]:
            if enemy.pos.distance_to(bullet.pos) < enemy.radius + bullet.radius:
                self.bullets.remove(bullet)
                enemy.hp -= 1
                enemy.hit_flash = 0.05
                if enemy.hp <= 0:
                    self.enemies.remove(enemy)
                    self.burst(enemy.pos, enemy.color, count=int(enemy.radius * 1.5))
                    self.score += 10 * self.combo
                    self.combo = min(self.combo + 1, 10)
                    self.combo_timer = 2.0
                    self.shake = max(self.shake, 0.15 if enemy.radius > 20 else 0.06)
                break

Для сотни объектов такой перебор «каждый с каждым» работает мгновенно. Если захотите тысячи врагов, разбейте поле на сетку ячеек и проверяйте только соседей, это классическая оптимизация spatial hashing.

Как использовать ИИ и промпты в разработке игр

ИИ-ассистенты вроде ChatGPT, Claude, Gemini или GitHub Copilot отлично подходят для такой разработки, но только если правильно с ними разговаривать. Просьба «напиши мне игру» обычно заканчивается кашей из устаревших API и кода, который невозможно поддерживать. Работает другой подход: вы остаётесь архитектором, а ИИ пишет отдельные блоки по чёткому техническому заданию.

Первое правило: давайте контекст и ограничения. Укажите версию библиотеки, размер окна, стиль кода и то, чего делать не нужно. Хороший стартовый промпт для этого проекта выглядит так.

Ты опытный разработчик игр на Python. Напиши на Pygame 2 каркас аркады:
класс Game с игровым циклом, delta time, окно 1280x720, 60 FPS.
Код в одном файле, без внешних ассетов, с комментариями на русском.
Пока без врагов и стрельбы, только движение игрока на WASD с инерцией.

Второе правило: одна система за раз. Не просите сразу врагов, частицы и меню. Добавляйте механики по одной и передавайте ассистенту актуальный код, чтобы он не выдумывал структуру, которой у вас нет.

Вот мой класс Player: [вставить код].
Добавь рывок на пробел: скорость 1400 px/s, длительность 0.12 с,
кулдаун 0.9 с, неуязвимость на время рывка.
Не меняй остальную логику и покажи только изменённые методы.

Третье правило: используйте ИИ как ревьюера. Модели хорошо находят баги и узкие места, если прямо попросить их об этом и задать сценарий нагрузки.

Проверь этот код на баги и проблемы производительности
при 500 врагах на экране. Выдай список проблем по приоритету
и исправление для каждой.

Четвёртое правило: просите идеи, а не только код. Ассистент отлично генерирует варианты геймдизайна, из которых вы выберете лучшее.

Предложи 5 идей, как сделать эту аркаду сочнее (game feel).
Каждая должна реализовываться меньше чем в 30 строк
и не требовать внешних файлов.

И главное правило: не доверяйте слепо. Запускайте каждый сгенерированный фрагмент, читайте его и спрашивайте «почему так», если что-то непонятно. ИИ ускоряет разработку в разы, но ответственность за архитектуру и качество остаётся на вас. Кстати, именно так появились многие детали этой игры: плавная инерция, разброс пуль и вспышка при попадании пришли из запроса про game feel.

Куда развивать проект

Базовая версия готова, и её легко расширять. Добавьте звуки выстрелов и взрывов через pygame.mixer, сохраняйте рекорд в JSON-файл между запусками, сделайте бонусы вроде тройного выстрела или щита, а каждую пятую волну выпускайте босса со своим паттерном атак. Когда игра будет готова, соберите её в исполняемый файл через PyInstaller и отправьте друзьям. Каждую из этих задач удобно отдавать ИИ-ассистенту по правилам из раздела выше.

Полный код игры на Python

Скопируйте код целиком в файл neon_void.py и запустите командой python neon_void.py.

"""Neon Void: неоновая аркада на Python и Pygame.

Управление: WASD двигаться, мышь целиться, ЛКМ стрелять,
Пробел рывок, R рестарт после поражения, Esc выход.
"""
import math
import random
import sys
from dataclasses import dataclass, field

import pygame
from pygame.math import Vector2

WIDTH, HEIGHT = 1280, 720
FPS = 60
BG_COLOR = (8, 8, 20)
CYAN = (0, 240, 255)
MAGENTA = (255, 40, 200)
YELLOW = (255, 220, 60)
WHITE = (240, 240, 255)


def glow_circle(radius, color, layers=4):
    """Рисует круг с мягким свечением на отдельной поверхности."""
    size = radius * 4
    surf = pygame.Surface((size, size), pygame.SRCALPHA)
    center = (size // 2, size // 2)
    for i in range(layers, 0, -1):
        alpha = int(60 / i)
        pygame.draw.circle(surf, (*color, alpha), center, radius + i * radius // 2)
    pygame.draw.circle(surf, color, center, radius)
    return surf


@dataclass
class Particle:
    pos: Vector2
    vel: Vector2
    color: tuple
    life: float
    max_life: float = field(init=False)

    def __post_init__(self):
        self.max_life = self.life

    def update(self, dt):
        self.pos += self.vel * dt
        self.vel *= 0.92
        self.life -= dt
        return self.life > 0

    def draw(self, screen, offset):
        k = self.life / self.max_life
        radius = max(1, int(4 * k))
        color = tuple(int(c * k) for c in self.color)
        pygame.draw.circle(screen, color, self.pos + offset, radius)


@dataclass
class Bullet:
    pos: Vector2
    vel: Vector2
    radius: int = 4

    def update(self, dt):
        self.pos += self.vel * dt
        return 0 <= self.pos.x <= WIDTH and 0 <= self.pos.y <= HEIGHT


class Player:
    SPEED = 380
    DASH_SPEED = 1400
    DASH_TIME = 0.12
    DASH_COOLDOWN = 0.9
    FIRE_RATE = 0.11

    def __init__(self):
        self.pos = Vector2(WIDTH / 2, HEIGHT / 2)
        self.vel = Vector2()
        self.radius = 14
        self.hp = 3
        self.fire_timer = 0.0
        self.dash_timer = 0.0
        self.dash_cooldown = 0.0
        self.invulnerable = 0.0
        self.sprite = glow_circle(self.radius, CYAN)

    def update(self, dt, keys):
        direction = Vector2(
            keys[pygame.K_d] - keys[pygame.K_a],
            keys[pygame.K_s] - keys[pygame.K_w],
        )
        if direction.length_squared() > 0:
            direction = direction.normalize()

        self.dash_cooldown = max(0.0, self.dash_cooldown - dt)
        self.invulnerable = max(0.0, self.invulnerable - dt)
        self.fire_timer = max(0.0, self.fire_timer - dt)

        if self.dash_timer > 0:
            self.dash_timer -= dt
        else:
            # Плавное ускорение вместо мгновенной скорости: так управление ощущается "дорогим"
            target = direction * self.SPEED
            self.vel += (target - self.vel) * min(1.0, 12 * dt)

        self.pos += self.vel * dt
        self.pos.x = max(self.radius, min(WIDTH - self.radius, self.pos.x))
        self.pos.y = max(self.radius, min(HEIGHT - self.radius, self.pos.y))

    def dash(self):
        if self.dash_cooldown > 0 or self.vel.length_squared() < 1:
            return False
        self.vel = self.vel.normalize() * self.DASH_SPEED
        self.dash_timer = self.DASH_TIME
        self.dash_cooldown = self.DASH_COOLDOWN
        self.invulnerable = max(self.invulnerable, self.DASH_TIME + 0.05)
        return True

    def try_fire(self, target):
        if self.fire_timer > 0:
            return None
        self.fire_timer = self.FIRE_RATE
        aim = target - self.pos
        if aim.length_squared() == 0:
            aim = Vector2(0, -1)
        aim = aim.normalize()
        spread = random.uniform(-0.04, 0.04)
        return Bullet(self.pos + aim * 20, aim.rotate_rad(spread) * 900)

    def draw(self, screen, offset):
        if self.invulnerable > 0 and int(self.invulnerable * 20) % 2 == 0:
            return
        rect = self.sprite.get_rect(center=self.pos + offset)
        screen.blit(self.sprite, rect, special_flags=pygame.BLEND_ADD)


class Enemy:
    def __init__(self, pos, speed, hp, radius, color):
        self.pos = Vector2(pos)
        self.speed = speed
        self.hp = hp
        self.radius = radius
        self.color = color
        self.sprite = glow_circle(radius, color)
        self.hit_flash = 0.0

    def update(self, dt, player_pos):
        to_player = player_pos - self.pos
        if to_player.length_squared() > 0:
            self.pos += to_player.normalize() * self.speed * dt
        self.hit_flash = max(0.0, self.hit_flash - dt)

    def draw(self, screen, offset):
        rect = self.sprite.get_rect(center=self.pos + offset)
        screen.blit(self.sprite, rect, special_flags=pygame.BLEND_ADD)
        if self.hit_flash > 0:
            pygame.draw.circle(screen, WHITE, self.pos + offset, self.radius)


def spawn_enemy(wave):
    """Враг появляется за краем экрана. Чем выше волна, тем злее противники."""
    side = random.choice(("top", "bottom", "left", "right"))
    margin = 40
    if side == "top":
        pos = (random.uniform(0, WIDTH), -margin)
    elif side == "bottom":
        pos = (random.uniform(0, WIDTH), HEIGHT + margin)
    elif side == "left":
        pos = (-margin, random.uniform(0, HEIGHT))
    else:
        pos = (WIDTH + margin, random.uniform(0, HEIGHT))

    if wave >= 3 and random.random() < 0.2:
        return Enemy(pos, speed=70 + wave * 4, hp=6, radius=26, color=YELLOW)
    return Enemy(pos, speed=120 + wave * 10, hp=1, radius=12, color=MAGENTA)


class Game:
    def __init__(self):
        pygame.init()
        pygame.display.set_caption("Neon Void")
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
        self.clock = pygame.time.Clock()
        self.font = pygame.font.Font(None, 36)
        self.big_font = pygame.font.Font(None, 96)
        self.best_score = 0
        self.reset()

    def reset(self):
        self.player = Player()
        self.bullets = []
        self.enemies = []
        self.particles = []
        self.score = 0
        self.combo = 1
        self.combo_timer = 0.0
        self.wave = 1
        self.to_spawn = 6
        self.spawn_timer = 0.0
        self.shake = 0.0
        self.game_over = False

    def burst(self, pos, color, count=18, speed=320):
        for _ in range(count):
            angle = random.uniform(0, math.tau)
            vel = Vector2(math.cos(angle), math.sin(angle)) * random.uniform(0.3, 1) * speed
            self.particles.append(Particle(Vector2(pos), vel, color, random.uniform(0.3, 0.7)))

    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    return False
                if event.key == pygame.K_SPACE and not self.game_over:
                    if self.player.dash():
                        self.burst(self.player.pos, CYAN, count=10, speed=200)
                if event.key == pygame.K_r and self.game_over:
                    self.reset()
        return True

    def update_waves(self, dt):
        if self.to_spawn > 0:
            self.spawn_timer -= dt
            if self.spawn_timer <= 0:
                self.enemies.append(spawn_enemy(self.wave))
                self.to_spawn -= 1
                self.spawn_timer = max(0.15, 0.8 - self.wave * 0.05)
        elif not self.enemies:
            self.wave += 1
            self.to_spawn = 6 + self.wave * 3

    def handle_collisions(self):
        for enemy in self.enemies[:]:
            for bullet in self.bullets[:]:
                if enemy.pos.distance_to(bullet.pos) < enemy.radius + bullet.radius:
                    self.bullets.remove(bullet)
                    enemy.hp -= 1
                    enemy.hit_flash = 0.05
                    if enemy.hp <= 0:
                        self.enemies.remove(enemy)
                        self.burst(enemy.pos, enemy.color, count=int(enemy.radius * 1.5))
                        self.score += 10 * self.combo
                        self.combo = min(self.combo + 1, 10)
                        self.combo_timer = 2.0
                        self.shake = max(self.shake, 0.15 if enemy.radius > 20 else 0.06)
                    break

        for enemy in self.enemies[:]:
            touching = self.player.pos.distance_to(enemy.pos) < self.player.radius + enemy.radius
            if touching and self.player.invulnerable <= 0:
                self.enemies.remove(enemy)
                self.player.hp -= 1
                self.player.invulnerable = 1.5
                self.combo = 1
                self.shake = 0.35
                self.burst(self.player.pos, CYAN, count=40, speed=450)
                if self.player.hp <= 0:
                    self.game_over = True
                    self.best_score = max(self.best_score, self.score)

    def update(self, dt):
        self.particles = [p for p in self.particles if p.update(dt)]
        self.shake = max(0.0, self.shake - dt)
        if self.game_over:
            return

        keys = pygame.key.get_pressed()
        self.player.update(dt, keys)
        if pygame.mouse.get_pressed()[0]:
            bullet = self.player.try_fire(Vector2(pygame.mouse.get_pos()))
            if bullet:
                self.bullets.append(bullet)

        self.bullets = [b for b in self.bullets if b.update(dt)]
        for enemy in self.enemies:
            enemy.update(dt, self.player.pos)

        self.combo_timer -= dt
        if self.combo_timer <= 0:
            self.combo = 1

        self.update_waves(dt)
        self.handle_collisions()

    def draw_grid(self, offset):
        for x in range(0, WIDTH, 64):
            pygame.draw.line(self.screen, (20, 20, 45), (x + offset.x, 0), (x + offset.x, HEIGHT))
        for y in range(0, HEIGHT, 64):
            pygame.draw.line(self.screen, (20, 20, 45), (0, y + offset.y), (WIDTH, y + offset.y))

    def draw_hud(self):
        hud = f"SCORE {self.score}   x{self.combo}   WAVE {self.wave}   HP {self.player.hp}"
        self.screen.blit(self.font.render(hud, True, WHITE), (20, 16))
        if self.player.dash_cooldown > 0:
            width = int(120 * (1 - self.player.dash_cooldown / Player.DASH_COOLDOWN))
            pygame.draw.rect(self.screen, CYAN, (20, 52, width, 6))

        if self.game_over:
            title = self.big_font.render("GAME OVER", True, MAGENTA)
            info = self.font.render(
                f"Счёт: {self.score}   Рекорд: {self.best_score}   R: заново", True, WHITE
            )
            self.screen.blit(title, title.get_rect(center=(WIDTH / 2, HEIGHT / 2 - 40)))
            self.screen.blit(info, info.get_rect(center=(WIDTH / 2, HEIGHT / 2 + 30)))

    def draw(self):
        offset = Vector2()
        if self.shake > 0:
            power = self.shake * 40
            offset = Vector2(random.uniform(-power, power), random.uniform(-power, power))

        self.screen.fill(BG_COLOR)
        self.draw_grid(offset)
        for p in self.particles:
            p.draw(self.screen, offset)
        for b in self.bullets:
            pygame.draw.circle(self.screen, YELLOW, b.pos + offset, b.radius)
        for e in self.enemies:
            e.draw(self.screen, offset)
        if not self.game_over:
            self.player.draw(self.screen, offset)
        self.draw_hud()
        pygame.display.flip()

    def run(self):
        running = True
        while running:
            # dt в секундах: игра работает одинаково на 60 и на 144 Гц
            dt = min(self.clock.tick(FPS) / 1000, 1 / 30)
            running = self.handle_events()
            self.update(dt)
            self.draw()
        pygame.quit()
        sys.exit()


if __name__ == "__main__":
    Game().run()

Попробуйте побить рекорд в 1000 очков и поделитесь в комментариях, какие механики добавили сами.

Поделиться:

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *