Модуль pygame.draw – геометрические примитивы

Функции модуля pygame.draw рисуют геометрические примитивы на поверхности – экземпляре класса Surface. В качестве первого аргумента они принимают поверхность. Поэтому при создании той или иной поверхности ее надо связать с переменной, чтобы потом было что передать в функции модуля draw. Поскольку мы пока используем только одну поверхность – главную оконную, то ее будем указывать в качестве первого параметра, а при создании свяжем с переменной:

import pygame
 
pygame.init()
 
sc = pygame.display.set_mode((300, 200))
 
# здесь будут рисоваться фигуры
 
pygame.display.update()
 
while 1:
    pygame.time.delay(1000)
    for i in pygame.event.get():
        if i.type == pygame.QUIT: exit()

В большинстве случаев фигуры прорисовывают внутри главного цикла, так как от кадра к кадру картинка на экране должна меняться. Поэтому на каждой итерации цикла в функции модуля draw передаются несколько измененные аргументы (например, каждый раз меняется координата x).

Однако у нас пока не будет никакой анимации, и нет смысла перерисовывать фигуры на одном и том же месте на каждой итерации цикла. Поэтому создавать примитивы будем в основной ветке программы. На данном этапе цикл while нужен лишь для того, чтобы программа самопроизвольно не завершалась.

После прорисовки, чтобы увидеть изменения в окне игры, необходимо выполнить функцию update() или flip() модуля display. Иначе окно не обновится. Рисование на поверхности – одно, а обновление состояния главного окна – другое. Представьте, что в разных местах тела главного цикла на поверхности прорисовываются разные объекты. Если бы каждое такое действие приводило к автоматическому обновлению окна, то за одну итерацию оно обновлялось бы несколько раз. Это приводило бы как минимум к бессмысленной трате ресурсов, так как скорость цикла связана с FPS.

Итак, первый аргумент функций рисования – поверхность, на которой размещается фигура. В нашем случае это будет sc. Вторым обязательным аргументом является цвет. Цвет задается в формате RGB, используется трехэлементный целочисленный кортеж. Например, (255, 0, 0) определяет красный цвет.

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

Все функции модуля draw возвращают экземпляры класса Rect – прямоугольные области, имеющие координаты, длину и ширину. Не путайте функцию rect() модуля draw и класс Rect, это разные вещи.

Начнем с функции rect() модуля draw:

pygame.draw.rect(sc, (255, 255, 255), (20, 20, 100, 75))
pygame.draw.rect(sc, (64, 128, 255), (150, 20, 100, 75), 8)

Прямоугольники

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

Следует отметить, что в функцию draw.rect() и некоторые другие третьим аргументом можно передавать не кортеж, а заранее созданный экземпляр Rect. В примере ниже показан такой вариант.

Обычно цвета выносят в отдельные переменные-константы. Это облегчает чтение кода:

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (125, 125, 125)
LIGHT_BLUE = (64, 128, 255)
GREEN = (0, 200, 64)
YELLOW = (225, 225, 0)
PINK = (230, 50, 230)
 
r1 = pygame.Rect((150, 20, 100, 75))
 
pygame.draw.rect(sc, WHITE, (20, 20, 100, 75))
pygame.draw.rect(sc, LIGHT_BLUE, r1, 8)

Чтобы нарисовать линию, а точнее – отрезок, надо указать координаты его концов. При этом функция line() рисует обычную линию, aaline() – сглаженную (толщину для последней указать нельзя):

pygame.draw.line(sc, WHITE, [10, 30], [290, 15], 3)
pygame.draw.line(sc, WHITE, [10, 50], [290, 35])
pygame.draw.aaline(sc, WHITE, [10, 70], [290, 55])

Линии

Координаты можно передавать как в виде списка, так и кортежа.

Функции lines() и aalines() рисуют ломанные линии:

pygame.draw.lines(sc, WHITE, True, [[10, 10], [140, 70], [280, 20]], 2)
pygame.draw.aalines(sc, WHITE, False, [[10, 100], [140, 170], [280, 110]])

Ломаные

Координаты определяют места излома. Количество точек может быть произвольным. Третий параметр (True или False) указывает замыкать ли крайние точки.

Функция polygon() рисует произвольный многоугольник. Задаются координаты вершин.

pygame.draw.polygon(sc, WHITE, [[150, 10], [180, 50], [90, 90], [30, 30]])
pygame.draw.polygon(sc, WHITE, [[250, 110], [280, 150], [190, 190], [130, 130]])
pygame.draw.aalines(sc, WHITE, True, [[250, 110], [280, 150], [190, 190], [130, 130]])

Многоугольники

Сглаженная ломаная здесь повторяет контур многоугольника, чем сглаживает его ребра.

Так же как в случае rect() для polygon() можно указать толщину контура.

Функция circle() рисует круги. Указывается центр окружности и радиус:

pygame.draw.circle(sc, YELLOW, (100, 100), 50)
pygame.draw.circle(sc, PINK, (200, 100), 50, 10)

Круги

В случае эллипса передается описывающая его прямоугольная область:

pygame.draw.ellipse(sc, GREEN, (10, 50, 280, 100))

Эллипс

Наконец, дуга:

pi = 3.14
pygame.draw.arc(sc, WHITE, (10, 50, 280, 100), 0, pi)
pygame.draw.arc(sc, PINK, (50, 30, 200, 150), pi, 2*pi, 3)

Дуги

Указывается прямоугольник, описывающий эллипс, из которого вырезается дуга. Четвертый и пятый аргументы – начало и конец дуги, выраженные в радианах. Нулевая точка справа.

Практическая работа. Анимация

На данном этапе мы уже готовы создать анимацию. Никакого движения объектов на экране монитора нет. Просто от кадра к кадру изменяются цвета пикселей экрана. Например, пиксель с координатами (10, 10) светится синим цветом, в следующем кадре синим загорается пиксель (11, 11), в то время как (10, 10) становится таким же как фон. В следующем кадре синей будет только точка (12, 12) и так далее. При этом человеку будет казаться, что синяя точка движется по экрану по диагонали.

Суть алгоритма в следующем. Берем фигуру. Рисуем ее на поверхности. Обновляем главное окно, человек видит картинку. Стираем фигуру. Рисуем ее с небольшим смещением от первоначальной позиции. Снова обновляем окно и так далее.

Как "стереть" старую фигуру? Для этого используется метод fill() объекта Surface. В качестве аргумента передается цвет, т. е. фон можно сделать любым, а не только черным, который задан по-умолчанию.

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

import pygame
 
FPS = 60
WIN_WIDTH = 500
WIN_HEIGHT = 100
 
WHITE = (255, 255, 255)
ORANGE = (255, 150, 100)
 
pygame.init()
 
clock = pygame.time.Clock()
 
sc = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
 
# радиус и координаты круга
r = 30
x = 0 - r  # скрываем за левой границей
y = WIN_HEIGHT // 2  # выравнивание по центру по вертикали
 
while 1:
    sc.fill(WHITE)
 
    for i in pygame.event.get():
        if i.type == pygame.QUIT: exit()
 
    pygame.draw.circle(sc, ORANGE, (x, y), r)
 
    pygame.display.update()
 
    # если полностью скрылся за правой границей
    if x >= WIN_WIDTH + r:
        x = 0 - r
    else:  # во всех остальных случаях
        x += 2  # в следующем кадре круг сместится,
        # от значения зависит "скорость движения"
 
    clock.tick(FPS)

Курс с примерами решений практических работ: android-приложение, pdf-версия.

Комментарии

Ответ на от Вадим

Вадим, добрый день. Возможно вы имеете ввиду встроенную функцию Clock в модуль pygame, описанную в более ранних статьях
import pygame # Импорт модуля
clock = pygame.time.Clock() # Создание инструкции
clock.tick(5) # Вызов инструкции с частотой кадров, в данном случае, равной 5
Более подробно можете прочесть, вернувшись назад.

import pygame
 
FPS = 60
WIN_WIDTH = 500
WIN_HEIGHT = 140
 
x1=0   #координата х
y1=30   #координата y
d=80        #длина квадрата
w=80        #ширина квадрата
WHITE = (255, 255, 255)#цвет белый
ORANGE = (255, 150, 100)#цвет оранжевый
 
pygame.init() 
clock = pygame.time.Clock()    
sc=pygame.display.set_mode((WIN_WIDTH,WIN_HEIGHT))#создаем окно игры
 
k=2     #константа направления движения, тут чтобы движение было вначале направо
while 1:
    sc.fill(WHITE)#удаление фигуры
 
    a=pygame.event.get()  # присваиваем переменную, чтобы окно закрылось без зависаний
    for j in a:#закрытие окна
        if j.type==pygame.QUIT:
            pygame.quit()
    pygame.draw.rect(sc, ORANGE, (x1,y1,d,w))#рисуем квадрат
    pygame.display.update()
 
    if x1==WIN_WIDTH-80:#  если кооснулось правой стенки то
        k=-2               #движение в обратную сторону
        x1+=k
    elif x1==0:     # движение влево после того как координата х = 0
        k=2
        x1+=k
    else:           # движение в правую сторону от левой стенки
        x1+=k     
 
    clock.tick(FPS)

import pygame
import random
 
pygame.init()
 
FPS = (60)
ww = 800
wh = 600
WHITE = (255,255,255)
BLACK = (0,0,0)
t = 10
x = 0
y = (wh // 2) + (t // 2)
 
clock = pygame.time.Clock()
 
sc = pygame.display.set_mode ((ww,wh))
 
while True:
	clock.tick(FPS)
 
	sc.fill(WHITE)
 
	pygame.draw.rect(sc,BLACK, (x - t, y, t, t))
 
	if x - t <= ww:
		x = x + 2
	else:
		x = 0
		y = random.randrange(0,wh)
 
	for i in pygame.event.get():
		if i.type == pygame.QUIT:
			exit()
 
 
	pygame.display.update()

import pygame
 
FPS = 60
SCR_WIDTH = 500
SCR_HEIGHT = 100
 
WHITE = (255, 255, 255)
ORANGE = (255, 150, 100)
 
pygame.init()
clock = pygame.time.Clock()
wn = pygame.display.set_mode((SCR_WIDTH, SCR_HEIGHT))
 
size = 50
x = 0
y = SCR_HEIGHT // 2 - 25
 
while True:
    wn.fill(WHITE)
    pygame.draw.rect(wn, ORANGE, (x, y, size, size))
    pygame.display.update()
 
    if x >= SCR_WIDTH - size:
        reach = True
    elif x <= 0:
        reach = False
 
    if reach:
        x -= 4
    if not reach:
        x += 4
 
    clock.tick(FPS)
    for i in pygame.event.get():
        if i.type == pygame.QUIT:
            exit()

Ответ на от Гавриил

"""Данный код чень неоптимален даже для такой простой задачки как движение объекта. Так как скорость его движения 
привязана к частоте цикла, в нашем случае к значению фпс. По этой причине износ некоторых объектов в играх зависит от фпс
"""
 
import pygame
 
 
SCREEN = (600, 400)
FPS = 60
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
pygame.init()
 
sc = pygame.display.set_mode(SCREEN)
pygame.display.set_caption("MY SUPPERR PUPPERR GAME")
play = True
 
x = 10
y = 10
w = 100
h = 100
step = 5
 
# pygame.draw.rect(sc, BLUE, (x, y, w, h))
# pygame.display.update()
 
while play:
    sc.fill(BLACK)
    pygame.draw.rect(sc, BLUE, (x, y, w, h))
    pygame.display.update()
    // если уходим за правую или левую границу, то меняем направление шага
    if x + w >= SCREEN[0] or x <= 0:
        step = -step
    // на каждой итерации меняем координату X
    x += step
 
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            play = False
 
 
    pygame.time.Clock().tick(FPS)

import pygame
 
pygame.init()
 
WIN_WIDTH = 500
WIN_HEIGHT = 500
FPS = 60
 
clock = pygame.time.Clock()
win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
 
Rect_Width = 70
Rect_Heigth = 50
x = 0
y = WIN_HEIGHT // 2
move = "right"
acc = 1
while True:
    win.fill((255, 255, 255))
    clock.tick(FPS)
 
    for event in pygame.event.get():
 
        if event.type == pygame.QUIT:
            exit()
    pygame.draw.rect(win, (0, 255, 255), (x, y - Rect_Heigth, Rect_Width, Rect_Heigth))
    if move == "right":
        x += acc
    if move == "left":
        x -= acc
    if x >= WIN_WIDTH - Rect_Width:
        move = "left"
        acc = 0
    if x <= 0:
        move = "right"
        acc = 0
    acc += 0.25
    pygame.display.update()