SpeedRun по pygame

Основные модули пакета Pygame

Youjin Tyan
10 min readNov 11, 2023

Игровые механизмы

Окно Pygame

# Подключение библиотеки PyGame
import pygame

# Инициализация PyGame
pygame.init()

# Окно игры: размер, позиция
gameScreen = pygame.display.set_mode((400, 300))

# Модуль os - позиция окна
import os
x = 100
y = 100
os.environ['Sp_VIDEO_WINDOW_POS'] = "%d,%d" % (x,y)

# Параметры окна
size = [500, 500]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Test drawings")
gameScreen.fill((0,0,255))
pygame.display.flip()

Цикл игры, выход из игры

# Цикл игры
runGame = True # флаг выхода из цикла игры
while runGame:
# Отслеживание события: "закрыть окно"
for event in pygame.event.get():
if event.type == pygame.QUIT: runGame = False

# Выход из игры:
pygame.quit()

Рисование базовых элементов. Модуль pygame.draw

rect(Surface, color, Rect, wiph=0) -> Rect

polygon(Surface, color, pointlist, wiph=0) -> Rect

circle(Surface, color, pos, radius, wiph=0) -> Rect

ellipse(Surface, color, Rect, wiph=0) -> Rect

arc(Surface, color, Rect, start_angle, stop_angle, wiph=1) -> Rect

line(Surface, color, start_pos, end_pos, wiph=1) -> Rect

lines(Surface, color, closed, pointlist, wiph=1) -> Rect

aaline(Surface, color, startpos, endpos, blend=1) -> Rect

aalines(Surface, color, closed, pointlist, blend=1) -> Rect

Загрузка изображения

# Модуль pygame.image позволяет загрузить изображерие из файла и возвращает объект типа Surface.
pygame.image.load("путь к файлу" )

# загрузить новое изображение из файла
load(filename) -> Surface

# Загрузить изображение (путь к файлу для Windows)
myImage = pygame.image.load('images\\\\bg1.jpg')

# определить место размещения
myRect = (0,0,600,400)

# выгрузить объект Surface, который содержит загруженное из файла изображение (myImage), в описанное место на экране (myRect)
screen.blit(myImage,myRect)

Объект Rect

pygame.Rect

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

Rect(left, top, wiph, height) -> Rect
Rect((left, top), (wiph, height)) -> Rect
Rect(object) -> Rect

Методы работы с Rect

Обработка событий

Событие — это то, как Pygame сообщает о том, что что-то случилось за пределами кода программы. События создаются, например, при нажатии клавиш клавиатуры, мыши и размещаются в очереди, дожидаясь обработки.

Функция get в модуле pygame.event возвращает последнее событие, ожидающее в очереди и удаляет его из очереди.

Объект event

Модуль pygame.event для обработки очереди событий

Pygame отслеживает все сообщения о событиях через очередь событий. Процедуры в этом модуле помогают управлять этой очередью событий. Входная очередь сильно зависит от модуля отображения (display) pygame. Если дисплей не был инициализирован и видеорежим не установлен, очередь событий не будет работать.

Существует множество способов доступа к очереди событий. Просто проверять существование событий, захватывать их непосредственно из стека.

Мышь

Модуль pygame.mouse для работы с мышью

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

Когда режим отображения (display) установлен, очередь событий начнет принимать события мыши. Кнопки мыши генерируют события pygame.MOUSEBUTTONDOWN и pygame.MOUSEBUTTONUP, когда они нажимаются и отпускаются. Эти события содержат атрибут кнопки, указывающий, какая кнопка была нажата. Колесо мыши будет генерировать pygame.MOUSEBUTTONDOWN и pygame.MOUSEBUTTONUP события при прокрутке.

Когда колесо повернуто вверх, кнопка будет установлена на 4, вниз -5. Всякий раз, когда мышь перемещается, генерируется событие pygame.MOUSEMOTION. Движение мыши разбито на небольшие и точные события движения. По мере перемещения мыши многие события движения будут помещены в очередь. События движения мыши, которые неправильно очищены от очереди событий, являются основной причиной того, что очередь событий заполняется.

Пример. Нарисовать курсор под текущей позицией мыши.

x, y = pygame.mouse.get_pos()
x -= mouse_cursor.get_width()/2
y -= mouse_cursor.get_height()/2
screen.blit(mouse_cursor, (x, y))

Определить какая кнопка была нажата на мышке можно используя значение event.button:

1 - left click
2 - middle click
3 - right click
4 - scroll up
5 - scroll down

Координаты курсора при нажатии кнопки мыши находятся в event.pos.

Пример. Перемещать картинку курсором мыши.

import pygame, sys, time
from pygame.locals import *
pygame.init()
FPS=30
fpsClock=pygame.time.Clock()
width=500
height=500
mainSurface=pygame.display.set_mode((width,height),0,32)
pygame.display.set_caption('Keyb moves')
background=pygame.image.load('images//bg1.jpg')
sprite=pygame.image.load('images//pict2.gif')

# Place image to the center of mainSurface
image_pos = ((mainSurface.get_width() - sprite.get_width())/2, (mainSurface.get_height() - sprite.get_height())/2)
doMove = False

# game loop
while True:
fpsClock.tick(FPS) # frame rate
mainSurface.blit(background,(0,0))

# get all events from the queue
for event in pygame.event.get():
# loop events queue
if event.type == QUIT:
# window close X pressed
pygame.quit()
sys.exit()

if event.type == KEYDOWN and event.key == K_ESCAPE:
# ESC key pressed
pygame.quit()
sys.exit()

if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # левая кнопка мыши
doMove = True
if event.button == 3: # правая кнопка мыши
image_pos = ((mainSurface.get_width() - sprite.get_width())/2, (mainSurface.get_height() - sprite.get_height())/2)
doMove = False

if event.type == pygame.MOUSEBUTTONUP: doMove = False

if event.type == MOUSEMOTION and doMove:
image_pos = event.pos

mainSurface.blit(sprite,image_pos)
pygame.display.update()

Клавиатура

Модуль pygame.key

Таблица клавиш

Этот модуль содержит функции для работы с клавиатурой.Очередь событий получает события pygame.KEYDOWN и pygame.KEYUP при нажатии и отпускании клавиш клавиатуры.

Оба события имеют ключевой атрибут, который представляет собой целочисленный идентификатор, представляющий каждую клавишу на клавиатуре.

Событие pygame.KEYDOWN имеет дополнительные атрибуты: unicode и scancode. unicode представляет собой одну символьную строку, которая соответствует введённому символу. Scancode представляет собой код для конкретной платформы.

Получить код клавиши:

pressed_keys = pygame.key.get_pressed()
if pressed_keys[K_SPACE]:
# Space key has been pressed
fire()

Существует много клавиатурных констант, они используются для представления клавиш на клавиатуре. Ниже приведен список всех клавиатурных констант:

Направленное движение с помощью клавиш

Можно перемещать изображение на экране с клавиатуры, назначая клавиши для перемещений: вверх, вниз, влево, вправо.

Создать картинку, например: sprite=pygame.image.load('images//pict2.gif')

Проверить очередь событий: pygame.event.get()

Проверить, является ли полученное событие нажатием на клавиши со стрелками: event.type == KEYDOWN

Если — да, то получмить код нажатой клавиши и сформировать новые координаты для картинки: spritex,spritey

И нарисовать картинку в новом месте: blit(sprite,(spritex,spritey))

import pygame, sys, time
from pygame.locals import *

pygame.init()

FPS=30
fpsClock=pygame.time.Clock()
width=500
height=500
mainSurface=pygame.display.set_mode((width,height),0,32)
pygame.display.set_caption('Keyb moves')

background=pygame.image.load('images//bg1.jpg')

sprite=pygame.image.load('images//pict2.gif') # Create moving image

# Place image to the center of mainSurface
spritex=(mainSurface.get_width() - sprite.get_width())/2
spritey=(mainSurface.get_height() - sprite.get_height())/2
direction=False

# --->
def move(direction, spritex, spritey):
# This function moves recalculates new image coordinates
if direction:
if direction == K_UP:
spritey-=5
elif direction == K_DOWN:
spritey+=5
if direction == K_LEFT:
spritex-=5
elif direction == K_RIGHT:
spritex+=5
return spritex, spritey
# --->

# game loop
while True:
fpsClock.tick(FPS) # define frame rate
mainSurface.blit(background,(0,0))
mainSurface.blit(sprite,(spritex,spritey))

# get all events from the queue
for event in pygame.event.get():
# loop events queue, remember in 'direction' pressed key code
if event.type==QUIT:
# window close X pressed
pygame.quit()
sys.exit()
if event.type==KEYDOWN and event.key == K_ESCAPE:
# ESC key pressed
pygame.quit()
sys.exit()
if event.type == KEYDOWN: direction = event.key # Other key pressed
if event.type == KEYUP: direction = False # Key released

# calculate new image position
spritex, spritey = move(direction, spritex, spritey)

pygame.display.update()

Объект Surface

pygame.Surface — объект pygame для представления изображений

Surface((width, height), flags=0, depth=0, masks=None) -> Surface
Surface((width, height), flags=0, Surface) -> Surface

Наложение поверхностей, прозрачность.

#-*-coding: utf-8-*-
import pygame
pygame.init()

# Create base surface
screen = pygame.display.set_mode((500,500))

# Create new surface
surface1 = pygame.Surface((150,150))
surface1.fill((255,0,0))
surface1.set_alpha(150)
#
surface2 = pygame.Surface((100,100))
surface2.fill((255,255,0))
surface2.set_alpha(100)

# Create image
bgImg = pygame.image.load("images//bg3.jpg")
bgImg = pygame.transform.scale(bgImg,(500,500))
#
pict1 = pygame.image.load("images//pict1.jpg")
pict1 = pygame.transform.scale(pict1,(130,130))
pict1.set_alpha(100)
#
pict2 = pygame.image.load("images//pict2.gif")
pict2 = pygame.transform.scale(pict2,(50,50))

clock = pygame.time.Clock()
running = 1
dX = dY = 1
x = y = 0

while running:
clock.tick(50)
event = pygame.event.poll()
if event.type == pygame.QUIT: running = 0
x += 8 * dX
y += 6 * dY
if (y<0 or y>= (screen.get_height() - pict2.get_height())) :
dY *= -1
if (x<0 or x>= (screen.get_width() - pict2.get_width())) :
dX *= -1

screen.blit(bgImg,(0,0))
surface1.blit(pict1,(0,0))
screen.blit(surface1,(20,50))
screen.blit(surface2,(150,150))
screen.blit(pict2,(x,y))

pygame.display.flip()

pygame.quit()

Управление временем

Модуль pygame.time содержит объект Clock, который можно использовать для отслеживания времени. Чтобы создать объект типа: время, вызывается конструктор pygame.time.Clock: clock = pygame.time.Clock()

Когда создан объект clock, можно вызвать его функцию tick один раз за кадр, которая возвращает время, прошедшее со времени предыдущего вызова в миллисекундах: time_passed = clock.tick ()

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

Игра будет работать со скоростью не более 30 кадров в секунду time_passed = clock.tick (30)

Звуки

Для управления звуком используется модуль pygame.mixer. Он отвечает за любые действия со звуками.

Загружаем звуковой файл в формате *.wav sound = pygame.mixer.Sound("sound.wav") (загружаем до игрового цикла, т.к. это очень долгая операция)

Проигрываем звук sound.play()

Столкновения (collisions)

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

Эта задача может быть реализована разными способами.

Например, используя объект Rect

#-*-coding:utf-8-*-
import pygame, sys, time
from pygame.locals import *

pygame.init()
FPS = 30
fpsClock = pygame.time.Clock()
winWidth = 500
winHeight = 500
mainSurface = pygame.display.set_mode((winWidth,winHeight),0,32)
pygame.display.set_caption('Collisions test')
background = pygame.image.load('images//bg1.jpg')

# Рисуем неподвижные компоненты на поверхности background
# Места расположения границ
border1Rect = pygame.Rect(0,0,100,500)
border2Rect = pygame.Rect(0,150,300,50)
# границы
border1 = pygame.draw.rect(background, (0,0,0), border1Rect, 0)
border2 = pygame.draw.rect(background, (0,0,0), border2Rect, 0)
# Записываем их в массив
borders = []
borders.append((border1,border1Rect))
borders.append((border2,border2Rect))
# Подвижный блок
blockWidth = 50
blockHeight = 50
blockStep = 1
blockColor = (255,0,0)
# Начальные координаты подвижного блока - по центру
blockX = (mainSurface.get_width() - blockWidth)/2
blockY = (mainSurface.get_height() - blockHeight)/2
blockPosition = (blockX, blockY)
direction = False

# описание функции --->
def newPosition(dirFlag, pos):
(x,y) = pos
# Функция пересчитывает координаты для подвижного объекта
if dirFlag:
if dirFlag == K_UP:
y -= blockStep
elif dirFlag == K_DOWN:
y += blockStep
if dirFlag == K_LEFT:
x -= blockStep
elif dirFlag == K_RIGHT:
x += blockStep
return (x, y)
# --->

while True: # Игра - начало
# Частота обновления экрана
fpsClock.tick(FPS)
# Рисуем неподвижные компоненты
mainSurface.blit(background,(0,0))
# Рисуем подвижный компонент
blockRect = pygame.Rect(blockPosition, (blockWidth, blockHeight))
block = pygame.draw.rect(mainSurface, blockColor, blockRect, 1)
# просматриваем очередь событий
for event in pygame.event.get():
# loop events queue, remember in 'direction' pressed key code
if event.type == QUIT:
# window close X pressed
pygame.quit()
sys.exit()
if event.type == KEYDOWN and event.key == K_ESCAPE:
# ESC key pressed
pygame.quit()
sys.exit()
if event.type == KEYDOWN: direction = event.key # Other key pressed
if event.type == KEYUP: direction = False # Key released
# Cохраняем старые координаты подвижного блока
savedPosition = blockPosition
# Рассчитываем новые координаты подвижного блока
blockPosition = newPosition(direction, blockPosition)
# Проверяем их корректность
for border in borders:
testRect = pygame.Rect(blockPosition, (blockWidth, blockHeight))
if testRect.colliderect(border[1]):
print ("Столкновение !")
# Возвращаем старые координату
blockPosition = savedPosition
else: print ("ok")
pygame.display.update()
# Игра - конец

Или используя поверхности — surface

#-*-coding:utf-8-*-
import pygame, sys, time
from pygame.locals import *

# Блоки-ограничители рисуются как отдельные поверхности
# Между ними курсором перемещаем мячик

FPS = 30 # кадров в сек
# Размеры окна игры
width = 500
height = 500
# Заголовок окна игры
title = "Collisions detection test"
# Сообщение в консоль игры
info = "Нет столкновений \\n"
# Переменная - индикатор движения
direction = False
# Шаг движения
myStep = 2
pygame.mixer.init()
pygame.mixer.music.load("voice-prompts-reaction-reaction-1-child-3-yrs-oops-human-voice-kid-speak-talk.mp3")

pygame.init()
fpsClock = pygame.time.Clock()
mainSurface=pygame.display.set_mode((width,height),0,32)
pygame.display.set_caption(title)
background=pygame.image.load('images//bg1.jpg') # Фон
sprite = pygame.image.load('images//pict2.gif') # Подвижная картинка

# Начальные координаты подвижной картинки
# Важно, чтобы в начале работы она не перекрывала ни один блок
spriteX=mainSurface.get_width() - sprite.get_width()
spriteY=mainSurface.get_height() - sprite.get_height()

# Неподвижные блоки - все сохраняем в одном списке
# Структура списка blocks:
# каждый элемент - пара значений: (поверхность, область её размещения)
# blocks[0] - первый элемент списка, blocks[0][0] - surface первого элемента,blocks[0][1] - Rect первого элемента
blocks = []
block1 = pygame.Surface((200,200))
block1.set_alpha(80)
block1.fill((255,0,0))
block1Rect = pygame.Rect(0,0,block1.get_width(), block1.get_height())
blocks.append((block1,block1Rect))
#
block2 = pygame.Surface((100,200))
block2.fill((0,255,0))
block2Rect = pygame.Rect(400,0,block2.get_width(), block2.get_height())
blocks.append((block2,block2Rect))
#
block3 = pygame.Surface((300,100))
block3.fill((0,0,255))
block3Rect = pygame.Rect(0,350,block3.get_width(), block3.get_height())
blocks.append((block3,block3Rect))
# закончили с описанием 3-х блоков

# *********>
def newPosition (direction, spriteX, spriteY):
# Функция пересчитывает координаты новой позиции подвижного объекта
# Проверяем столкновений со всеми блоками-границаи
global myStep
if direction:
if direction == K_UP:
spriteY -= myStep
elif direction == K_DOWN:
spriteY += myStep
elif direction == K_LEFT:
spriteX -= myStep
elif direction == K_RIGHT:
spriteX += myStep
return spriteX, spriteY
# *********>

# *********>
def collisionDetected():
global blocks
global spriteRectNew
colFlag = False
# Проверка столкновений со всеми блоками в массиве блоков
for block in blocks:
if spriteRectNew.colliderect(block[1]):
collisionDir = direction
colFlag = True
return colFlag
# *********>

# Цикл игры
while True:
fpsClock.tick(FPS) # Частота обновления экрана

# Обрабатываем очередь событий - начало
for event in pygame.event.get():
# В цикле берём из очереди очередное событие, запоминаем в переменной event
# Проверяем тип события и выполняем соответствующие лействия
if event.type == QUIT:
# Тип проверяемого события НАЖАТ Х В ОКНЕ ИГРЫ
pygame.quit()
sys.exit()
if event.type == KEYDOWN and event.key == K_ESCAPE:
# ESC key pressed
pygame.quit()
sys.exit()

# Следующая строка получает управление только тогда, когда не отработали предыдущие проверки кода события
# то есть произошло событие, отличное от перечисленных выше
if event.type == KEYDOWN:
direction = event.key
if event.type == KEYUP:
direction = False # Кнопка отпущена
# Обрабатываем очередь событий - конец

# Текущее место расположения подвижной картинки
spriteRect = pygame.Rect(spriteX,spriteY,sprite.get_width(), sprite.get_height())

# Сохраняем старые координаты
oldPos = (spriteX, spriteY)

# Вычмсляем новые координаты, анализируя нажатые кнопки
spriteX, spriteY = newPosition(direction, spriteX, spriteY)

# Вычисляем новое место расположения картинки
spriteRectNew = pygame.Rect(spriteX,spriteY,sprite.get_width(), sprite.get_height())

# Проверяем, не пересекает ли новое место блоки. Если пересекает, то вовращпни картинке старые координаты
if collisionDetected():
(spriteX, spriteY) = oldPos
# Play OOPS!
pygame.mixer.music.play()

# Рисуем всё на экране
# Фон
mainSurface.blit(background,(0,0))
# Блоки
for block in blocks:
mainSurface.blit(block[0],(block[1].x,block[1].y))
# Картинку
mainSurface.blit(sprite,(spriteRect.x,spriteRect.y))
# Обновляем экран
pygame.display.update()

--

--