Окно открывается, цикл крутится, по экрану едет круг. Но круг — это ещё не игра, а только проверка, что цикл работает. Сегодня мы заменим его настоящей сценой: ночным небом со звёздами и кораблём, который будет героем всей нашей аркады «Комета».
Координаты: где верх, где низ
Представьте клетчатый лист из тетради, но с одним отличием: начало отсчёта не в левом нижнем углу, как на уроках математики, а в левом верхнем. Вправо идёт ось x, а вот ось y смотрит вниз — как строки текста на странице: чем дальше читаете, тем больше номер строки.
Положение любой точки окна задают два числа — координаты (x, y), измеренные в пикселях. Верхний левый угол окна — (0, 0), правый нижний — примерно (960, 640). Значит, чтобы поставить объект ниже, нужно увеличивать y, а не уменьшать.
WIDTH = 960
HEIGHT = 640
center_x = WIDTH // 2
center_y = HEIGHT // 2
bottom_y = HEIGHT - 80
print(center_x, center_y, bottom_y)
Разбор по строкам
WIDTH = 960,HEIGHT = 640— размеры окна, те самые константы изmain.py.WIDTH // 2— деление нацело: половина ширины, то есть середина по горизонтали. Для 960 получится 480.HEIGHT // 2— середина по вертикали, 320. Вместе с предыдущей строкой это центр окна.HEIGHT - 80— точка в 80 пикселях от нижнего края. Здесь будет стоять корабль.print(...)выведет три числа:480 320 560.
Почему ось y вниз
Так устроена почти вся компьютерная графика: экран рисуется строка за строкой сверху вниз, поэтому и счёт идёт сверху. Привыкнуть можно за десять минут, главное — не рассуждать «как в математике».
Цвета: три числа от 0 до 255
Экран смешивает цвет из света трёх маленьких фонариков: красного, зелёного и синего. У каждого яркость от 0 (погашен) до 255 (на полную). Выключены все три — чёрный, включены все на максимум — белый.
Такая запись называется RGB (Red, Green, Blue), а в Python её удобно хранить в кортеже — это как список, только в круглых скобках и неизменяемый. Три числа от 0 до 255: (12, 14, 32) — почти чёрный с синим оттенком, наш фон. Цвета, которые понадобятся не один раз, мы называем константами и пишем заглавными буквами.
BG_COLOR = (12, 14, 32)
WHITE = (255, 255, 255)
SHIP_COLOR = (90, 200, 255)
FLAME_COLOR = (255, 170, 60)
Разбор по строкам
BG_COLOR— фон ночного неба, уже знакомая константа.WHITE— все три фонарика на максимуме: белые звёзды.SHIP_COLOR— голубой: красного мало, зелёного и синего много.FLAME_COLOR— оранжевый для пламени двигателя: красного много, зелёного поменьше, синего почти нет.
Фигуры: pygame.draw
Теперь о кисти. Модуль pygame.draw умеет рисовать готовые фигуры прямо на поверхности. Фигуры ложатся слоями, как мазки на холсте: что нарисовано позже, то лежит сверху и закрывает нижнее. Поэтому порядок строк в коде — это порядок слоёв.
| Функция | Что рисует | Главные аргументы |
|---|---|---|
pygame.draw.rect | прямоугольник | поверхность, цвет, (x, y, ширина, высота); x и y — левый верхний угол |
pygame.draw.circle | круг | поверхность, цвет, центр (x, y), радиус |
pygame.draw.line | отрезок | поверхность, цвет, начало, конец, толщина |
pygame.draw.polygon | многоугольник | поверхность, цвет, список точек [(x, y), ...] |
У каждой функции есть необязательный последний аргумент width: если его не указать, фигура закрашена, а если задать, например 2, нарисуется только контур такой толщины.
pygame.draw.rect(screen, SHIP_COLOR, (100, 100, 200, 60))
pygame.draw.circle(screen, WHITE, (480, 320), 40)
pygame.draw.line(screen, WHITE, (0, 560), (WIDTH, 560), 2)
pygame.draw.polygon(screen, FLAME_COLOR, [(600, 100), (700, 100), (650, 200)])
Разбор по строкам
rect— голубой прямоугольник с левым верхним углом в (100, 100), шириной 200 и высотой 60.circle— белый закрашенный круг радиусом 40 с центром в середине окна.line— белая линия толщиной 2 от левого края до правого на высоте 560.polygon— оранжевый треугольник по трём вершинам из списка.
Попробуйте сами
Вставьте эти четыре строки в цикл между screen.fill(BG_COLOR) и pygame.display.flip(), запустите, подвигайте числа, поменяйте порядок строк и посмотрите, как меняются слои. Потом удалите — в итоговый код они не войдут.
Звёзды: случайность и список
Звёзды нужны разные и разбросанные как попало. Вручную расставлять 120 точек никто не станет — поручим это случайности. В стандартной библиотеке Python есть модуль random («случайный»), а в нём функция random.randint(a, b): она возвращает случайное целое число от a до b, причём обе границы входят.
Каждую звезду опишем списком из трёх чисел [x, y, size]: положение и размер (радиус кружка от 1 до 3 пикселей). Все звёзды сложим в один большой список stars. Главное — создать его один раз до цикла: небо должно быть одним и тем же в каждом кадре.
import random
stars = []
for _ in range(120):
stars.append([random.randint(0, WIDTH), random.randint(0, HEIGHT), random.randint(1, 3)])
for x, y, size in stars:
pygame.draw.circle(screen, WHITE, (x, y), size)
Разбор по строкам
import random— подключаем модуль случайных чисел. Пишется в начале файла.stars = []— пустой список, в который будем складывать звёзды.for _ in range(120):— повторить 120 раз. Имя_по договорённости означает «номер повтора мне не нужен».stars.append([...])— добавляем в конец списка новую звезду: случайный x от 0 до ширины, случайный y от 0 до высоты, случайный размер 1–3.for x, y, size in stars:— обходим звёзды по одной; каждый список из трёх чисел тут же раскладывается по трём переменным, так что писатьstar[0],star[1]не нужно.pygame.draw.circle(screen, WHITE, (x, y), size)— белый кружок радиусаsizeв точке звезды. Этот цикл стоит в игровом цикле, а список — снаружи.
Корабль: три точки и треугольник
Корабль — треугольник. Чтобы его нарисовать, достаточно трёх точек: носа, левого и правого крыла. Это как вбить три гвоздика в доску и обтянуть их ниткой. У нас корабль задан центром (x, y) и размером size: нос выше центра на size, а крылья отстоят от него вбок на size * 0.7 и вниз на size * 0.6.
Расчёт точек мы выносим в отдельную функцию ship_points(x, y, size), которая только считает и возвращает список из трёх точек, но ничего не рисует. Рисует уже draw_ship(surface, x, y). Это первый шаг к главному приёму курса: логика отдельно от отрисовки. Функцию, которая лишь считает числа, легко проверить без окна — именно её вы напишете в задаче после урока.
SHIP_SIZE = 24
def ship_points(x, y, size):
nose = (x, y - size)
left = (x - size * 0.7, y + size * 0.6)
right = (x + size * 0.7, y + size * 0.6)
return [nose, left, right]
def draw_ship(surface, x, y):
pygame.draw.circle(surface, FLAME_COLOR, (x, y + SHIP_SIZE * 0.7), SHIP_SIZE // 4)
points = ship_points(x, y, SHIP_SIZE)
pygame.draw.polygon(surface, SHIP_COLOR, points)
pygame.draw.polygon(surface, WHITE, points, 2)
Разбор по строкам
SHIP_SIZE = 24— размер корабля в одном месте: захотите побольше, поменяете одну цифру.nose = (x, y - size)— нос прямо над центром. Помните: вверх — это меньше y.leftиright— крылья симметрично слева и справа, чуть ниже центра.return [nose, left, right]— функция отдаёт список из трёх кортежей, ровно то, что нужноpolygon.- Первая строка
draw_shipрисует пламя: оранжевый круг под кормой. Он идёт первым, чтобы корпус потом закрыл его верхнюю часть — торчать останется только язычок. pygame.draw.polygon(surface, SHIP_COLOR, points)— закрашенный голубой корпус поверх пламени.- Последняя строка рисует белый контур толщиной 2 поверх корпуса, чтобы корабль не терялся на тёмном фоне.
Практика: звёздное небо в main.py
Соберём всё в один файл. Откройте kometa/main.py и замените его содержимое целиком этим кодом. Круг-комета из прошлого урока исчезает вместе с COMET_COLOR, COMET_SPEED и comet_x: их место заняли звёзды и корабль.
import random
import pygame
WIDTH = 960
HEIGHT = 640
FPS = 60
TITLE = "Комета"
BG_COLOR = (12, 14, 32)
WHITE = (255, 255, 255)
SHIP_COLOR = (90, 200, 255)
FLAME_COLOR = (255, 170, 60)
SHIP_SIZE = 24
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(TITLE)
clock = pygame.time.Clock()
# Звёзды создаём один раз, до цикла
stars = []
for _ in range(120):
stars.append([random.randint(0, WIDTH), random.randint(0, HEIGHT), random.randint(1, 3)])
def ship_points(x, y, size):
nose = (x, y - size)
left = (x - size * 0.7, y + size * 0.6)
right = (x + size * 0.7, y + size * 0.6)
return [nose, left, right]
def draw_ship(surface, x, y):
pygame.draw.circle(surface, FLAME_COLOR, (x, y + SHIP_SIZE * 0.7), SHIP_SIZE // 4)
points = ship_points(x, y, SHIP_SIZE)
pygame.draw.polygon(surface, SHIP_COLOR, points)
pygame.draw.polygon(surface, WHITE, points, 2)
running = True
while running:
dt = clock.tick(FPS) / 1000 # пока не используется, пригодится в следующем модуле
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(BG_COLOR)
for x, y, size in stars:
pygame.draw.circle(screen, WHITE, (x, y), size)
draw_ship(screen, WIDTH // 2, HEIGHT - 80)
pygame.display.flip()
pygame.quit()
Что здесь по порядку: константы и цвета, затем инициализация и часы, затем список звёзд (один раз), затем две функции корабля, и только потом игровой цикл. Внутри цикла кадр собирается слоями: сначала фон, потом звёзды, потом корабль, и в самом конце flip() показывает готовую картинку.
Запустите из папки проекта с активированным окружением:
python main.py
Должно получиться тёмно-синее окно 960 на 640 с россыпью белых точек разного размера. Внизу, посередине, стоит голубой треугольник с белым контуром и оранжевым язычком пламени под ним. Картинка неподвижна, а звёзды при каждом запуске лежат по-новому. Если окно закрывается крестиком — цикл на месте.
Проверьте себя
- Звёзды остаются на месте, пока окно открыто, а не мигают каждый кадр.
- Корабль стоит по центру по горизонтали и у нижнего края, острым носом вверх.
- Пламя видно только под кормой, а корпус его не перекрывает.
- Поменяйте
SHIP_SIZEна 40: корабль и пламя выросли вместе, а в коде вы правили одну цифру.
Частые ошибки
- Звёзды дрожат и мерцают. Список
starsсоздаётся внутриwhile running, и каждый кадр получает новые случайные точки. Вынесите создание звёзд выше цикла. - Корабль вылез за верхний край или стоит не там. Вы считали y как в математике, вверх. В pygame y растёт вниз: чтобы поднять объект, y нужно уменьшать, чтобы опустить — увеличивать.
- Ошибка
ValueErrorпро неверный цвет. Одна из компонент вышла за пределы 0–255 (скажем, 256) или в кортеже не три числа. Проверьте константы цветов. - Звёзд или корабля не видно. Скорее всего, они нарисованы до
screen.fill(BG_COLOR): заливка фоном закрасила всё нарисованное раньше. Заливка идёт первой, фигуры — после неё,flip()— последним.
Что важно запомнить
- Начало координат — левый верхний угол; x растёт вправо, y растёт вниз.
- Цвет — кортеж RGB из трёх чисел от 0 до 255; повторяющиеся цвета выносим в константы.
- Функции
pygame.draw.rect,circle,line,polygonрисуют на поверхности; что нарисовано позже, то лежит сверху. - Звёзды создаём один раз до цикла, а рисуем в каждом кадре.
- Расчёт точек корабля (
ship_points) отделён от рисования (draw_ship) — так его можно проверить без окна.
В следующем модуле корабль наконец оживёт: мы разберёмся, как задавать скорость и как время кадра dt делает движение одинаковым на любом компьютере.