Загрузка картинок и blit
Пора заменить серые коробки на настоящие картинки. Загрузка изображений в Pygame — пара строк, но в них прячется важная оптимизация.
Суть: картинку грузят через pygame.image.load, превращают в быстрый формат через convert/convert_alpha и рисуют через screen.blit. Прозрачность даёт convert_alpha.
Серые прямоугольники отлично работают для прототипа, но игру хочется видеть красивой. Картинка в Pygame называется Surface — это «поверхность», набор пикселей, который можно нарисовать на экране. Загрузить её просто: pygame.image.load("hero.png"). Но есть нюанс, который отличает плавную игру от тормозящей.
Когда ты загружаешь PNG, он хранится в формате файла, не очень удобном для быстрой отрисовки. Метод convert() перегоняет картинку в формат экрана, и рисоваться она будет в разы быстрее. Если у картинки есть прозрачные области (а у спрайтов почти всегда есть), используй convert_alpha() — он сохранит прозрачность. Это не каприз: без convert игра из десятка спрайтов может ощутимо тормозить.
Нарисовать картинку на экране — это blit (от block transfer, «перенос блока пикселей»). Ты говоришь: возьми вот эту поверхность и впечатай её в окно в точке (x, y).
Как работает под капотом
blit копирует пиксели одной поверхности на другую. Позиция (x, y) — это левый верхний угол картинки на экране. Прозрачные пиксели при convert_alpha пропускаются, поэтому видно фон под спрайтом:
load("hero.png") -- читаем файл с диска
|
v
convert_alpha() -- быстрый формат + прозрачность
|
v
screen.blit(img, (x, y)) -- впечатываем в окно
|
v
видно картинку, прозрачные пиксели = фон
Полный пример загрузки и отрисовки (читаем):
# грузим один раз ДО цикла, а не каждый кадр!
hero_img = pygame.image.load("hero.png").convert_alpha()
bg_img = pygame.image.load("bg.png").convert()
while running:
screen.blit(bg_img, (0, 0)) # фон в углу
screen.blit(hero_img, (350, 250)) # герой
pygame.display.flip()Картинку часто нужно отмасштабировать или повернуть — это тоже Surface-операции (pygame.transform.scale, rotate). Логику выбора кадра спрайта (например, какой кадр анимации показать) можно проверить без графики. Попробуй сам — простой выбор кадра по времени:
# Какой кадр анимации показать в зависимости от времени
FRAMES = ["шаг1", "шаг2", "шаг3", "шаг4"]
FRAME_TIME = 0.1 # секунд на кадр
def current_frame(elapsed):
index = int(elapsed / FRAME_TIME) % len(FRAMES)
return FRAMES[index]
for t in (0.0, 0.05, 0.12, 0.25, 0.41):
print(f"t={t:.2f}s -> кадр {current_frame(t)}")Лист спрайтов и анимация
Хранить каждый кадр анимации отдельным файлом неудобно — их могут быть сотни. Поэтому художники складывают все кадры в одну большую картинку — лист спрайтов (sprite sheet), как полоску кадров киноплёнки. В коде мы вырезаем нужный кадр методом subsurface(область) или передаём прямоугольник-вырез прямо в blit третьим аргументом. Каждый кадр анимации — это просто другой прямоугольник на той же картинке, и мы переключаем их по таймеру, как листали флипбук в первом уроке.
Такой подход экономит и память, и время загрузки: один файл вместо сотни, одна команда convert вместо множества. А смена кадров сводится к арифметике: номер кадра равен времени, поделённому на длительность кадра, по модулю количества кадров — ровно та формула, которую ты запускал выше. Анимация, которая выглядит сложной магией, на деле оказывается выбором прямоугольника по часам. Это отличный пример того, как в геймдеве за «крутым эффектом» почти всегда прячется простая, проверяемая логика.
Заведи привычку собирать все ассеты в словарь при старте игры: images = {"hero": load("hero.png"), "coin": load("coin.png")}. Тогда по всему коду ты обращаешься к картинке по понятному имени, а не таскаешь переменные, и легко видишь полный список ресурсов игры в одном месте. Пути к файлам собирай через os.path.join и считай их от расположения программы, а не от текущей папки запуска — иначе игра, запущенная из другого каталога или упакованная в exe, не найдёт свои картинки. Это одна из самых частых причин загадочного «у меня работает, а у друга нет». Аккуратная загрузка ассетов в начале — скучная, но крайне благодарная привычка, экономящая часы будущей отладки.
Частые ошибки
- Грузить картинку внутри цикла — диск читается каждый кадр, игра дико тормозит. Грузи один раз до цикла.
- Забыть
convert_alpha()— прозрачный PNG нарисуется с чёрным квадратом вокруг. - Вызвать
convertдо создания окна — ошибка, ведь формат экрана ещё неизвестен.
Best practices
- Грузи все ассеты один раз при старте, складывай в словарь или переменные.
- Для спрайтов с прозрачностью — всегда
convert_alpha(), для фона —convert(). - Держи картинки в папке
assets/и собирай путь черезos.path.join.
Итог: load → convert_alpha → blit. Грузи один раз, рисуй каждый кадр — и серые коробки превращаются в настоящих героев.