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

На данном этапе мы уже готовы создать анимацию. Никакого движения объектов на экране монитора нет. Просто от кадра к кадру изменяются цвета пикселей экрана. Например, пиксель с координатами (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)

События клавиатуры

Человек может управлять объектами в игре в основном с помощь клавиатуры, мыши, джойстика. Когда на "манипуляторах" что-то двигается или нажимается, то возникают события определенных типов. Обработкой событий занимается модуль pygame.event, который включает ряд функций, наиболее важная из которых уже ранее рассмотренная pygame.event.get(), которая забирает из очереди произошедшие события.

В pygame, когда фиксируется то или иное событие, создается соответствующий ему объект от класса Event. Уже с этими объектами работает программа. Экземпляры данного класса имеют только свойства, у них нет методов. У всех экземпляров есть свойство type. Набор остальных свойств события зависит от значения type.

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

Событию типа "клавиша нажата" в поле type записывается числовое значение, совпадающее со значением константы pygame.KEYDOWN. Событию типа "клавиша отпущена" в поле type записывается значение, совпадающее со значением константы pygame.KEYUP.

У обоих типов событий клавиатуры есть атрибуты key и mod. В key записывается конкретная клавиша, которая была нажата или отжата. В mod – клавиши-модификаторы (Shift, Ctrl и др.), которые были зажаты в момент нажатия или отжатия обычной клавиши. У событий KEYDOWN также есть поле unicode, куда записывается символ нажатой клавиши (тип данных str).

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

import pygame

 

FPS = 60

W = 700 # ширина экрана

H = 300 # высота экрана

WHITE = (255, 255, 255)

BLUE = (0, 70, 225)

 

pygame.init()

sc = pygame.display.set_mode((W, H))

clock = pygame.time.Clock()

 

# координаты и радиус круга

x = W // 2

y = H // 2

r = 50

 

while 1:

sc.fill(WHITE)

 

pygame.draw.circle(sc, BLUE, (x, y), r)

 

pygame.display.update()

 

for i in pygame.event.get():

   if i.type == pygame.QUIT:

       exit()

   elif i.type == pygame.KEYDOWN:

       if i.key == pygame.K_LEFT:

           x -= 3

       elif i.key == pygame.K_RIGHT:

           x += 3

 

clock.tick(FPS)

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

Часто проверку и типа и клавиши записывают в одно логическое выражение (i.type == pygame.KEYDOWN and i.key == pygame.K_LEFT). В Python так можно делать потому, что если первая часть сложного выражения возвращает ложь, то вторая часть уже не проверяется.

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

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

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

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

Во-вторых, в цикле обработки событий нам придется следить не только за нажатием клавиши, но и ее отжатием. Когда клавиша нажимается, какая-либо переменная, играющая роль флага, должна принимать одно значение, когда клавиша отпускается эта же переменная должна принимать другое значение.

В основном теле while надо проверять значение этой переменной и в зависимости от него менять или не менять значение координаты.

import pygame

 

FPS = 60

W = 700 # ширина экрана

H = 300 # высота экрана

WHITE = (255, 255, 255)

BLUE = (0, 70, 225)

RIGHT = "to the right"

LEFT = "to the left"

STOP = "stop"

 

pygame.init()

sc = pygame.display.set_mode((W, H))

clock = pygame.time.Clock()

 

# координаты и радиус круга

x = W // 2

y = H // 2

r = 50

 

motion = STOP

 

while 1:

sc.fill(WHITE)

 

pygame.draw.circle(sc, BLUE, (x, y), r)

 

pygame.display.update()

 

for i in pygame.event.get():

   if i.type == pygame.QUIT:

       exit()

   elif i.type == pygame.KEYDOWN:

       if i.key == pygame.K_LEFT:

           motion = LEFT

       elif i.key == pygame.K_RIGHT:

           motion = RIGHT

   elif i.type == pygame.KEYUP:

       if i.key in [pygame.K_LEFT, pygame.K_RIGHT]:

           motion = STOP

 

if motion == LEFT:

   x -= 3

elif motion == RIGHT:

   x += 3

 

clock.tick(FPS)

Использовать константы не обязательно, можно сразу присваивать строки или даже числа (например, motion = 1 обозначает движение вправо, -1 – влево, 0 – остановка). Однако константы позволяют легче понимать и обслуживать в дальнейшем код, делают его более информативным. Лучше привыкнуть к такому стилю.

Должно проверяться отжатие только двух клавиш. Если проверять исключительно KEYUP без последующей конкретизации, то отжатие любой клавиши приведет к остановке, даже если в это время будет по-прежнему зажиматься клавиша влево или вправо. Выражение i.key in [pygame.K_LEFT, pygame.K_RIGHT] обозначает, что если значение i.key совпадает с одним из значений в списке, то все выражение возвращает истину.

На самом деле существует способ по-проще. В библиотеке pygame с событиями работает не только модуль event. Так модуль pygame.key включает функции, связанные исключительно с клавиатурой. Здесь есть функция pygame.key.get_pressed(), которая возвращает кортеж двоичных значений. Индекс каждого значения соответствует своей клавиатурной константе. Само значение равно 1, если клавиша нажата, и 0 – если не нажата.

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

...

while 1:

sc.fill(WHITE)

 

pygame.draw.circle(sc, BLUE, (x, y), r)

 

pygame.display.update()

 

for i in pygame.event.get():

   if i.type == pygame.QUIT:

       exit()

 

keys = pygame.key.get_pressed()

if keys[pygame.K_LEFT]:

   x -= 3

elif keys[pygame.K_RIGHT]:

   x += 3

 

clock.tick(FPS)

Можно сказать, вызов get_pressed() снимает "маску" зажатых клавиш. Мы ее снимаем на каждой итерации главного цикла. Это даже не регистрация событий как таковых.

Выражение типа keys[pygame.K_LEFT] извлекает значение из кортежа по индексу, значение которого записано в константе K_LEFT. Если извлеченное значение True, то координата меняется.

Если необходимо, чтобы событие обрабатывалось при нажатии двух и более клавиш, то работает такое логическое выражение: keys[pygame.K_LEFT] and keys[pygame.K_a] (одновременное нажатие стрелки 'влево' и буквы 'a'). Однако если нужно задействовать не обычные клавиши, а модификаторы, то данный номер не проходит. По видимому в кортеже соответствующие модификаторам значения не хранятся.

В таком случае приходится возвращаться к первому варианту – перебирать события в цикле for:

...

elif i.type == pygame.KEYDOWN:

if i.key == pygame.K_LEFT and i.mod == pygame.KMOD_LSHIFT:

 

...

Здесь при if будет True, если перед нажатием стрелки был зажат левый Shift. Причем обратная последовательность: сначала зажать стрелку, потом Shift не сработает. Видимо модификаторы обрабатываются библиотекой pygame несколько отлично от обычных клавиш. Допустим, если при зажатии обычных клавиш генерируется только одно событие, то для модификаторов они генерируются постоянно или хранятся до отпускания в другой очереди.

Таким образом, если первым зажимается K_LEFT, то событие сразу обрабатывается. При этом в i.mod записывается отсутствие модификатора. Поэтому условие не срабатывает.

Если же первым зажимается модификатор, то это событие не теряется и позволяет условию при if выполнится в случае нажатия при этом обычной клавиши.

Весь перечень констант pygame, соответствующих клавишам клавиатуры, смотрите в документации: https://www.pygame.org/docs/ref/key.html

События мыши

В Pygame обрабатываются три типа событий мыши:

● нажатие кнопки (значение свойства type события соответствует константе pygame.MOUSEBUTTONDOWN),

● отпускание кнопки (MOUSEBUTTONUP),

● перемещение мыши (MOUSEMOTION).

Какая именно кнопка была нажата, записывается в другое свойство события – button. Для левой кнопки это число 1, для средней – 2, для правой – 3, для прокручивания вперед – 4, для прокручивания назад – 5. У событий MOUSEMOTION вместо button используется свойство buttons, в которое записывается состояние трех кнопок мыши (кортеж из трех элементов).

Другим атрибутом мышиных типов событий является свойство pos, в которое записываются координаты происшествия (кортеж из двух чисел).

Таким образом, если вы нажали правую кнопку мыши точно в середине окна размером 200x200, то будет создан объект типа Event с полями event.type = pygame.MOUSEBUTTONDOWN, event.button = 3, event.pos = (100, 100).

У событий MOUSEMOTION есть еще один атрибут – rel. Он показывает относительное смещение по обоим осям. С помощью него, например, можно отслеживать скорость движения мыши.

Код ниже создает фигуры в местах клика мыши. Нажатие средней кнопки очищает поверхность.

import pygame

 

WHITE = (255, 255, 255)

RED = (225, 0, 50)

GREEN = (0, 225, 0)

BLUE = (0, 0, 225)

 

pygame.init()

sc = pygame.display.set_mode((400, 300))

sc.fill(WHITE)

pygame.display.update()

 

while 1:

for i in pygame.event.get():

   if i.type == pygame.QUIT:

       exit()

   if i.type == pygame.MOUSEBUTTONDOWN:

       if i.button == 1:

           pygame.draw.circle(sc, RED, i.pos, 20)

           pygame.display.update()

       elif i.button == 3:

           pygame.draw.circle(sc, BLUE, i.pos, 20)

           pygame.draw.rect(sc, GREEN, (i.pos[0]-10, i.pos[1]-10, 20, 20))

           pygame.display.update()

       elif i.button == 2:

           sc.fill(WHITE)

           pygame.display.update()

 

pygame.time.delay(20)

В функции модуля draw вместо координат передается значение поля pos события. В pos хранятся координаты клика. В случае с функцией rect() извлекаются отдельные элементы кортежа pos. Вычитание числа 10 используется для того, чтобы середина квадрата, сторона которого равна 20-ти пикселям, точно соответствовала месту клика. Иначе в месте клика будет находиться верхний левый угол квадрата.

Функцию update() не обязательно вызывать три раза в ветках if-elif-elif. Ее можно вызвать в основном теле главного цикла. Однако в этом случае, когда кликов не происходит, она будет выполнять зря.

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

...

 

while 1:

for i in pygame.event.get():

   if i.type == pygame.QUIT:

       exit()

 

pressed = pygame.mouse.get_pressed()

pos = pygame.mouse.get_pos()

if pressed[0]:

   pygame.draw.circle(sc, BLUE, pos, 5)

   pygame.display.update()

 

pygame.time.delay(20)

Функция mouse.get_pressed() возвращает трехэлементный кортеж. Первый элемент (с индексом 0) соответствует левой кнопке мыши, второй – средней, третий – правой. Если значение элемента равно единице, значит, кнопка нажата. Если нулю, значит – нет. Так выражение pressed[0] есть истина, если под нулевым индексом содержится единица.

Чтобы скрыть курсор (например, в игре, где управление осуществляется исключительно клавиатурой), надо воспользоваться функцией pygame.mouse.set_visible(), передав в качестве аргумента False.

Так можно привязать графический объект к курсору (в данном случае привязывается квадрат):

...

pygame.mouse.set_visible(False)

 

while 1:

for i in pygame.event.get():

   if i.type == pygame.QUIT:

       exit()

 

sc.fill(WHITE)

 

if pygame.mouse.get_focused():

   pos = pygame.mouse.get_pos()

   pygame.draw.rect(sc, BLUE, (pos[0]-10, pos[1]-10, 20, 20))

 

pygame.display.update()

 

pygame.time.delay(20)

Функцией get_pos() мы можем считывать позицию курсора, даже если он не виден. Далее в этой позиции рисуем фигуру в каждом кадре.

Функция get_focused() проверяет, находится ли курсор в фокусе окна игры. Если не делать эту проверку, то при выходе курсора за пределы окна, квадрат будет постоянно прорисовываться у края окна, где произошел выход, т. е. не будет исчезать.

 

                                                        

                                   

                                               

                                                           


 

В графических программах часто приходится копировать пик- селы с места на место (например копировать из переменной на поверхность или с одной поверхности на другую). В программировании эта операция копирования битового массива носит специальное название блитирования (blitting). Мы блитируем (blit) изображение (или часть изображения, или просто группу пикселов) с одного места на другое. Фактически это всего лишь еще один из вариантов обозначения процедуры копирования. Просто слово «блитировать» сразу дает понять, что копируем мы пикселы, а не что-либо другое.

 

В модуле Pygame поверхность (surface) — это то, на чем вы рисуете. Поверхность отображения (display surface) — то, что вы видите на экране. Именно она в листинге получила имя screen. Но в Pygame-программе может быть множество поверхностей, причем существует возможность копировать изображения с одной по- верхности на другую. Более того, сами поверхности можно поворачивать и масштабировать (делать больше или меньше).

                              

Соединяем точки

 В модуле Pygame есть не только метод, позволяющий рисовать линию сразу, но и метод, рисующий линии между группами точек. Он называется pygame.draw.lines() и содержит пять параметров:

                                                                                                                                     
поверхность surface, на которой вы будете рисовать;
                                                                                                                         
цвет линии color;
                                          
параметр замкнутости closed замкнет линию, соединив последнюю точку с первой.








Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: