*P.S.
Я всё ещё школьник, поэтому ждать красивого кода не стоит, но если тебе хочется погрузиться в мир видеоигр с обратной стороны, эта статья должна показаться тебе хоть немного интересной.
Этот подробный гайд по созданию простецкой игры на Python. Всё это делать мы будем конечно в пучарме «PyCharm». Его настройки в этой статье не будет, всё это уже давно есть ру(ю)тубе.
Подготовка к разработке: Установка Pygame и организация проекта
Прежде чем мы начнем, необходимо настроить окружение для разработки. Мы будем делать игру с помощью pygame.
-
Установка Pygame.
Проверьте установлен ли у вас Python.
Открываем терминал и пишем:
pip install pygame
-
Структура проекта. Для удобства организации кода, создайте структуру папок и файлов:
проект/ ├── img/ # Папка для изображений (спрайтов) ├── sound/ # Папка для звуков ├── config.py # Файл с настройками игры ├── objects.py # Файл с классами игровых объектов └── main.py # Основной файл игры
-
Файл
config.py
– Сердце настроек. Создадим файлconfig.py
и поместим туда основные настройки игры. Это упростит изменение параметров в будущем.# config.py from os import path WIDTH = 480 # Ширина экрана HEIGHT = 600 # Высота экрана FPS = 60 # Частота кадров в секунду (FPS) # --- Цвета --- WHITE = (255, 255, 255) BLACK = (0, 0, 0) RED = (255, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) YELLOW = (255, 255, 0) # --- Пути к ресурсам --- img_dir = path.join(path.dirname(__file__), 'img') # Путь к папке img sound_dir = path.join(path.dirname(__file__), 'sound') # Путь к папке sound # --- Настройки Босса --- BOSS_LASER_SPEED = 5 BOSS_LASER_COLOR = RED
-
Перевожу со змеиного на человеческий
WIDTH
,HEIGHT
: Определяют размеры окна игры.from os import path
: Импортируем модуль path. Мы будем иметь доступ к переменным из этого файла во всём проекте.FPS
: Определяет скорость обновления экрана. Более высокое значение – более плавная анимация, но требует больше ресурсов.Цвета: Задаем цвета в формате RGB (Красный, Зеленый, Синий).
img_dir
,sound_dir
: Эти переменные определяют пути к папкам с изображениями и звуками, что упрощает доступ к ресурсам в коде.path.join
используется для корректного определения пути в зависимости от операционной системы.
-
Основные элементы игры: Спрайты и управление
Теперь перейдем к созданию игровых объектов и управлению ими. В Pygame все, что мы видим на экране (игрок, враги, пули), будет представлено спрайтами.
-
Класс
. Общие свойства задаются в классахEntityPlayer
иMob
. Иными словами, это общий “скелет” для всего.-
Что нужно знать:
Спрайт – это изображение, которое можно перемещать по экрану.
pygame.sprite.Sprite
– базовый класс для спрайтов в Pygame.image
: Атрибут, в котором хранится изображение спрайта.rect
: Атрибут, который представляет прямоугольник, окружающий изображение. Используется для определения положения спрайта и обнаружения коллизий.x
,y
: Координаты верхнего левого угла прямоугольникаrect
.speed
: Скорость перемещения объекта.
-
-
Класс
Player
(определение игрока). КлассPlayer
(вobjects.py
) наследует (или содержит) общие свойства и добавляет специфичную для игрока логику:# objects.py import pygame from config import * # Импортируем настройки из config.py class Player(pygame.sprite.Sprite): # Наследуемся от pygame.sprite.Sprite def __init__(self, all_sprites, bullets): pygame.sprite.Sprite.__init__(self) player_img = pygame.image.load(path.join(img_dir, "player.png")).convert_alpha() self.image = pygame.transform.scale(player_img, (50, 50)) # Масштабируем изображение self.image.set_colorkey(BLACK) # Убираем фон (делаем прозрачным) self.rect = self.image.get_rect() # Получаем прямоугольник спрайта self.rect.centerx = WIDTH / 2 # Начальная позиция по x (по центру) self.rect.bottom = HEIGHT - 10 # Начальная позиция по y (внизу) self.speedx = 0 # Горизонтальная скорость (изначально 0) self.all_sprites = all_sprites self.bullets = bullets 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 def shoot(self): # Метод для выстрела bullet = Bullet(self.rect.centerx, self.rect.top) # Создаем пулю self.all_sprites.add(bullet) # Добавляем пулю в общую группу self.bullets.add(bullet) # Добавляем пулю в группу пуль laser_sound.play() # Проигрываем звук выстрела
-
Что делает код?
init()
: Конструктор класса. Загружает изображение игрока, масштабирует его, убирает фон (делает прозрачным), устанавливает начальную позицию и скорость.-
update()
: Метод, вызываемый каждый кадр для обновления состояния игрока:Сбрасывает горизонтальную скорость (чтобы игрок останавливался, когда клавиши не нажаты).
Получает состояние клавиш.
Устанавливает скорость в зависимости от нажатых клавиш (влево/вправо).
Обновляет позицию игрока.
Ограничивает движение игрока по краям экрана.
shoot()
: Создает пулю и добавляет ее в группы спрайтов.
-
-
Обработка событий Pygame (игровой цикл). Теперь, в
main.py
, необходимо создать основной игровой цикл и обрабатывать события.# main.py import pygame from config import * from objects import * # --- Инициализация --- pygame.init() pygame.mixer.init() # Инициализация звука screen = pygame.display.set_mode((WIDTH, HEIGHT)) # Создаем окно игры pygame.display.set_caption("Space Shooter") # Устанавливаем заголовок окна clock = pygame.time.Clock() # Создаем объект для управления FPS # --- Загрузка изображений --- background = pygame.image.load(path.join(img_dir, "starfield.png")).convert_alpha() background_rect = background.get_rect() # Получаем прямоугольник фона # --- Создание групп спрайтов --- all_sprites = pygame.sprite.Group() # Группа для всех спрайтов mobs = pygame.sprite.Group() # Группа для врагов bullets = pygame.sprite.Group() # Группа для пуль boss_lasers = pygame.sprite.Group() # Группа лазеров босса # --- Создание объектов --- player = Player(all_sprites, bullets) # Создаем игрока all_sprites.add(player) # Добавляем игрока в общую группу # --- Загрузка музыки и звуков --- pygame.mixer.music.load(path.join(sound_dir, 'Star_Wars_-_Rogue_One_-_OST_Izgojj-Odin_Zvjozdnye_Vojjny_65069199.mp3')) # Загружаем музыку pygame.mixer.music.play(-1) # Проигрываем музыку бесконечно laser_sound = pygame.mixer.Sound(path.join(sound_dir, 'blaster.mp3')) # Звук выстрела # --- Состояние игры --- game_over = True # Изначально игра не запущена # --- Стартовый экран (пока не используем) --- # start_screen() # Перенесем эту функцию позже # --- Основной игровой цикл --- def start_screen(): # ... (реализация стартового экрана, как в коде) ... pass # заглушка для начала, чтобы игра запустилась def reset_game(self): # ... (сброс состояния игры, создание новых объектов) ... pass # заглушка для начала def run(): # Функция запуска игрового цикла nonlocal game_over while True: if game_over: if not start_screen(): # Отображаем стартовый экран, возвращаем False при выходе return continue # --- Ограничение FPS --- clock.tick(FPS) # --- Обработка событий (ввод, выход) --- for event in pygame.event.get(): if event.type == pygame.QUIT: # Если нажали на крестик pygame.quit() # Выход из Pygame return # Завершаем функцию run() elif event.type == pygame.KEYDOWN: # Нажата клавиша if event.key == pygame.K_SPACE: # Если пробел player.shoot() elif event.key == pygame.K_ESCAPE: pygame.quit() return # Завершаем игру # --- Обновление спрайтов --- all_sprites.update() # Вызываем update() для всех спрайтов # --- Отрисовка --- screen.fill(BLACK) # Заполняем экран черным цветом screen.blit(background, background_rect) # Рисуем фон all_sprites.draw(screen) # Рисуем все спрайты # --- Обновление экрана --- pygame.display.flip() # Переворачиваем буфер экрана, чтобы отобразить изменения def main(): run() if __name__ == "__main__": main()
-
Что делает код?
Инициализирует Pygame.
Создает окно игры и устанавливает заголовок.
Создает объект для управления FPS (
clock
).Загружает фоновое изображение.
-
Создает группы спрайтов:
all_sprites
: Для всех спрайтов (игрок, враги, пули).mobs
: Для врагов.bullets
: Для пуль.boss_lasers
: Для лазеров босса.
Создает объект игрока.
Загружает музыку и звук выстрела.
Входит в основной игровой цикл
run()
.-
В игровом цикле:
clock.tick(FPS)
: Ограничивает частоту кадров.pygame.event.get()
: Получает все события (действия пользователя, такие как нажатия клавиш, закрытие окна).-
for event in ...
: Обрабатывает события.pygame.QUIT
: Если пользователь закрывает окно, игра завершается.-
pygame.KEYDOWN
: Если нажата клавиша:K_SPACE
: Игрок стреляет.K_ESCAPE
: Игрок выходит из игры.
all_sprites.update()
: Вызывает методupdate()
для каждого спрайта в группе. Это обновляет положение и состояние спрайтов (например, движение игрока, движение пуль).-
Отрисовка:
screen.fill(BLACK)
: Заливает экран черным цветом.screen.blit(background, background_rect)
: Отображает фон.all_sprites.draw(screen)
: Отрисовывает все спрайты в группеall_sprites
. Pygame автоматически использует атрибутimage
спрайтов и ихrect
для отрисовки.pygame.display.flip()
: Обновляет (переворачивает) экран, чтобы отобразить все изменения.
-
Враги и коллизии
Теперь добавим врагов, чтобы сделать игру более интересной, и реализуем систему коллизий, чтобы определить, когда пули попадают во врагов, и когда игрок сталкивается с врагами.
-
Класс
Mob
(создание врагов). Создадим классMob
(вobjects.py
) для представления врагов. Враги будут появляться сверху и двигаться вниз.# objects.py import pygame import random from config import * class Mob(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) meteor_img = pygame.image.load(path.join(img_dir, "Ship.png")).convert_alpha() # Загружаем изображение врага self.image = meteor_img #.convert() # Альтернатива convert_alpha() self.image.set_colorkey(BLACK) self.rect = self.image.get_rect() # Получаем прямоугольник врага self.rect.x = random.randrange(WIDTH - self.rect.width) # Случайная начальная позиция по x self.rect.y = random.randrange(-100, -40) # Начальная позиция по y (за пределами экрана) self.speedy = random.randrange(1, 8) # Случайная скорость падения self.speedx = random.randrange(-3, 3) # Случайная скорость по x def update(self): # Метод для обновления позиции врага self.rect.x += self.speedx # Двигаем по x 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)
-
Что делает код?
init()
: Загружает изображение врага, устанавливает случайную начальную позицию и скорость падения.update()
: Перемещает врага вниз, а также, если враг выходит за пределы экрана, переносит его обратно наверх.
-
Класс
Bullet
(создание пуль). Этот класс уже был представлен выше.-
Обнаружение коллизий и логика уничтожения. Теперь добавим логику обнаружения коллизий в основной цикл игры (
main.py
):# main.py (внутри цикла run()) # ... (код игрового цикла) ... # Проверка столкновений пуль с врагами (пули уничтожают врагов) hits = pygame.sprite.groupcollide(self.mobs, self.bullets, True, True) # Проверяем столкновения. True, True = удаляем и врага и пулю for hit in hits: m = Mob() # Создаем нового врага self.all_sprites.add(m) # Добавляем его в общую группу self.mobs.add(m) # Добавляем его в группу врагов self.score_counter.add(1) # Увеличиваем счет # Проверка столкновения игрока с врагами (проигрыш) hits = pygame.sprite.spritecollide(self.player, self.mobs, False) # Проверяем столкновения игрока с врагами. False = враг не удаляется if hits: self.game_over = True # Если есть столкновение, устанавливаем флаг game_over # Дополнительно: можно уменьшать здоровье игрока, а не сразу завершать игру # ... (остальной код игрового цикла) ...
-
Что делает код?
pygame.sprite.groupcollide(self.mobs, self.bullets, True, True)
: Проверяет столкновения между группой врагов (self.mobs
) и группой пуль (self.bullets
). Если пуля попадает во врага, и пуля, и враг удаляются из своих групп (параметрыTrue, True
). После каждого столкновения создается новый враг и увеличивается счет.pygame.sprite.spritecollide(self.player, self.mobs, False)
: Проверяет столкновения между игроком и врагами. Если происходит столкновение, устанавливается флагself.game
_over = True
, что означает конец игры.
-
4. Босс: Создание финального противника
После набора определенного количества очков появляется босс.
-
Класс
Boss
(создание босса). Создадим классBoss
(вobjects.py
).# objects.py import pygame import random from config import * class Boss(pygame.sprite.Sprite): def __init__(self, x, y, all_sprites, boss_lasers): pygame.sprite.Sprite.__init__(self) boss_image = pygame.image.load(path.join(img_dir, "Boss.png")).convert_alpha() # Загрузка изображения босса self.image = pygame.transform.scale(boss_image, (90, 150)) # Масштабирование self.image.set_colorkey(BLACK) self.rect = self.image.get_rect() # Получаем прямоугольник босса self.rect.x = x # Позиция по X self.rect.y = y # Позиция по Y print("Boss created at:", self.rect.x, self.rect.y) self.health = 50 # Здоровье босса self.original_image = self.image # Сохраняем оригинальное изображение (для анимации) self.last_shot = pygame.time.get_ticks() # Время последнего выстрела self.shoot_delay = 2000 # Задержка между выстрелами (в миллисекундах) self.all_sprites = all_sprites # Группа всех спрайтов self.boss_lasers = boss_lasers # Группа лазеров босса def update(self): # Двигаем босса (просто для примера, можно сделать сложнее) self.rect.x += random.choice([-2, -1, 0, 1, 2]) # Случайное движение по горизонтали if self.rect.left < 0: self.rect.left = 0 # Ограничиваем движение слева if self.rect.right > WIDTH: self.rect.right = WIDTH # Ограничиваем движение справа # Стреляем now = pygame.time.get_ticks() # Получаем текущее время if now - self.last_shot > self.shoot_delay: # Если прошло достаточно времени с последнего выстрела self.last_shot = now # Обновляем время последнего выстрела laser = BossLaser(self.rect.centerx, self.rect.bottom) # Создаем лазер self.all_sprites.add(laser) # Добавляем лазер в общую группу спрайтов self.boss_lasers.add(laser) # Добавляем лазер в группу лазеров босса def draw(self, screen): screen.blit(self.image, self.rect) def damage(self, amount): self.health -= amount if self.health <= 0: self.kill() # Если здоровье <= 0, уничтожаем босса
-
Что делает код?
init()
: Загружает изображение босса, масштабирует, устанавливает начальную позицию, здоровье, и другие параметры.update()
: Отвечает за перемещение босса и стрельбу лазерами.draw()
: Рисует изображение босса на экране.damage()
: Уменьшает здоровье босса. При достижении 0, босс удаляется.
-
-
Класс
BossLaser
(лазеры босса).# objects.py import pygame from config import * class BossLaser(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.Surface((5, 20)) # Создаем поверхность для лазера (прямоугольник) self.image.fill(BOSS_LASER_COLOR) # Заливаем поверхность цветом лазера self.image = self.image.convert_alpha() # Прозрачность self.rect = self.image.get_rect() # Получаем прямоугольник лазера self.rect.centerx = x # Центрируем лазер по x self.rect.bottom = y # Располагаем лазер под боссом self.speedy = BOSS_LASER_SPEED # Устанавливаем скорость падения def update(self): self.rect.y += self.speedy # Двигаем лазер вниз if self.rect.bottom > HEIGHT: self.kill() # Удаляем лазер, если он вышел за пределы экрана
-
Добавление босса в игру (main.py). Логика появления босса и столкновения с ним:
# main.py (внутри run()) # ... (код игрового цикла) ... # Проверка на появление босса if self.score_counter.get_score() % 100 == 0 and self.score_counter.get_score()!=0 and self.boss is None: self.boss = Boss(WIDTH // 2 - 50, 50, self.all_sprites, self.boss_lasers) # Создаем босса self.all_sprites.add(self.boss) # Добавляем босса в группу спрайтов # Проверка столкновений пуль с боссом if self.boss: for bullet in self.bullets: if pygame.sprite.collide_rect(bullet, self.boss): # Проверяем столкновение self.boss.damage(3) # Уменьшаем здоровье босса self.bullets.remove(bullet) # Удаляем пулю self.all_sprites.remove(bullet) break # Важно: только одно попадание за раз if self.boss.health <= 0: self.boss.kill() # Если здоровье босса меньше или равно 0, уничтожаем его self.boss = None # Проверка столкновения игрока с лазерами босса (проигрыш) hits = pygame.sprite.spritecollide(self.player, self.boss_lasers, True) # True - лазеры удаляются if hits: self.game_over = True # Конец игры
-
Что делает код?
Проверяет, набрал ли игрок достаточно очков (
self.score_counter.get_score() % 100 == 0 and self.score_counter.get_score()!=0
) и отсутствует ли босс на экране (self.boss is None
).Если условия выполняются, создается новый экземпляр
Boss
.Проверяет, произошло ли столкновение пуль с боссом. Если да, то босс получает урон, а пуля удаляется.
Проверяет, уничтожен ли босс (здоровье <= 0). Если да, то босс удаляется.
Проверяет столкновения игрока с лазерами босса. Если игрок столкнулся с лазером, игра заканчивается.
-
Уровни и игровой процесс: Организация игры (Упрощено)
В данной реализации используется простая структура уровней - игра переходит в game_over
после коллизии игрока с мобов/пулей босса, а после отображается стартовый экран.
-
Класс
ScoreCount
(добавлено в objects.py). Класс, отображающий очки.# objects.py import pygame from config import * class ScoreCount: def __init__(self, initial_score=0): self.score = initial_score self._score = initial_score self.font = pygame.font.Font(None, 36) # Шрифт и размер self.update_score_surface() def add(self, points): self.score += points self.update_score_surface() def get_score(self): return self.score def update_score_surface(self): self.score_surface = self.font.render(f"Score: {int(self.score)}", True, (255, 255, 255)) def reset(self): self.score = 0 def draw(self, screen, x=10, y=10): if self.score_surface: screen.blit(self.score_surface, (x, y))
-
Отображение счета в игре (main.py). Необходимо создать объект
ScoreCount
и отрисовывать его.# main.py # ... (внутри класса Application.__init__) ... self.score_counter = ScoreCount() # ... (внутри игрового цикла run()) ... self.score_counter.draw(self.screen)
6. Завершение игры: Состояние игры и стартовый экран (main.py)
-
Состояние игры: Переменная
self.game
_over
отслеживает состояние игры.# main.py (внутри класса Application) def start_screen(self): start_image = pygame.image.load(path.join(img_dir, "start.png")).convert_alpha() start_rect = start_image.get_rect(center=(WIDTH // 2, HEIGHT // 2)) font_name = pygame.font.match_font('arial') font = pygame.font.Font(font_name, 30) line1 = "Нажмите на любую кнопку," line2 = "чтобы начать игру, или Esc, чтобы выйти" text_surface1 = font.render(line1, True, WHITE) text_surface2 = font.render(line2, True, WHITE) text_rect1 = text_surface1.get_rect(center=(WIDTH // 2, HEIGHT // 2 - 20)) text_rect2 = text_surface2.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 20)) running = True while running: self.clock.tick(FPS) for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() return False # Возврат False для выхода из игры if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: pygame.quit() return False # Выход из игры else: running = False # Нажата другая кнопка -> начинаем игру self.screen.blit(start_image, start_rect) self.screen.blit(text_surface1, text_rect1) self.screen.blit(text_surface2, text_rect2) pygame.display.flip() self.game_over = False # Игра началась self.reset_game() # Сброс параметров
-
Что делает код?
Отображает изображение стартового экрана.
Отображает текст.
Ожидает нажатия клавиши (любой, кроме
ESC
).Если нажата клавиша -
self.game
_over
=False
, игра начинается.Функция возвращает
False
, если был произведен выход из игры.
Итоги
Вот что получилось

