Модулі
Якщо код програми є складним, розбиття її на окремі функції допомагає спростити його для візуального сприйняття. Якщо цього недостатньо, є сенс винести частину функцій та пов’язаних з ними оголошень за межі основного файлу програми.
Такі додаткові файли з кодом, що використовується в програмі, називаються модулями. Найчастіше вони містять оголошення функцій та констант, які далі можуть бути підключені (імпортовані) в головну програму і вільно в ній використовуватися. Об’єкти з модуля можуть бути імпортовані в інші модулі. Файл утворюється шляхом додавання до імені модуля розширення .py. При імпорті модуля інтерпретатор шукає файл спочатку в поточному каталозі, потім в каталогах, зазначених у змінній оточення PYTHONPATH, потім в залежних від платформи шляхах за замовчуванням.
Великі програми, як правило, складаються з стартового файлу – файлу верхнього рівня, і набору файлів-модулів. Головний файл займається контролем програми. У той же час модуль – це не тільки фізичний файл. Модуль являє собою колекцію компонентів. У цьому сенсі модуль – це простір імен, – namespace, і всі імена всередині модуля ще називаються атрибутами – такими, наприклад, як функції і змінні.
Імпорт модулів
Імпортувати модуль можна за допомогою оператора import, після якого вказується назва модуля, що буде імпортовано:
import назва_модуля
Після імпортування модуль стане доступним в поточній відкритій програмі, в якій його було імпортовано. Назва ж цього модуля стане змінною, через яку можна отримати доступ до атрибутів модуля: назва_модуля.атрибут
Наприклад, звернення до константи pi, розташованої в модулі math:
import math
math.pi
Якщо назва модуля занадто довга, або вона вам не подобається з інших причин, то замість неї може бути використаний псевдонім. Для вказання псевдоніму модуля передбачене ключове слово as:
import назва_модуля as псевдонім
Тоді наш приклад виглядатиме так:
import math as m
m.pi
Досить часто виникає необхідність у використанні лише деяких атрибутів модуля. В такому випадку немає сенсу підключати весь модуль, а можна підключити лише ті атрибути, які є необхідними. Підключити певні атрибути модуля можна за допомогою інструкції from … import, яка має кілька форматів:
from назва_модуля import
атрибут1 [as псевдонім1 ],
[атрибут2 [as псевдонім2 ] ...]
from назва_модуля import *
Перший формат дозволяє підключити з модуля тільки зазначені вами атрибути. Другий формат інструкції дозволяє підключити всі атрибути з модуля.
Є велика кількість вбудованих модулів, що дозволяють виконувати складні математичні операції (math), працювати з датами (datatime)/часом (time), випадковими числами (random), операційною та файловою системами (os) та ін.
Модуль pygame
Pygame - бібліотека, призначена для розробки мультимедійних програм з графічним інтерфейсом, наприклад ігор. Вона дає необхідний мінімальний інструментарій для:
малювання графічних об'єктів;
відстеження різних подій (клавіатури, миші, таймера і т.п.);
відстеження та зміни стану об'єктів (створення анімації, контроль зіткнень);
швидкої відмальовки змін на екрані пристрою користувача;
роботи зі звуковими ефектами.
Використовуючи ці можливості, програміст має можливість створювати прості графічні додатки, в тому числі і нескладні ігри.
На відміну від модулів os та random модуль pygame потрібно інсталювати.
Документація, навчальні матеріали та додаткова інформація доступна розробникам на сайті проекту: https://www.pygame.org/docs/
Початок роботи з pygame
В першу чергу імпортуємо модуль pygame до нашого файлу з вихідним кодом:
import pygame
Потім викликаємо функцію init() для підготовки модулів pygame до роботи. Вона ініціалізує налаштування, необхідні Pygame для нормальної роботи
pygame.init()
Після цього створюємо графічне вікно, передавши як аргумент у функцію set_mode() його роздільну здатність у вигляді кортежу з пари цілих чисел. У свою чергу, функція поверне нам об'єкт типу Surface, який використовується для представлення зображень:
screen = pygame.display.set_mode((1200, 800))
Далі, запускаємо основний цикл програми, в якому серед усіх подій, що відбуваються в нашому додатку, перехоплюємо подію закриття основного графічного вікна користувачем:
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
Після того, як очікувана подія настала, завершуємо роботу з бібліотекою pygame викликом функції exit() з модуля sys.
Затримка та формування потрібного FPS
Однак у такій реалізації є одна істотна вада. Головний цикл працює надто швидко: кожні кілька мілісекунд перевіряють чергу подій. Це надмірно навантажує процесор комп'ютера. Події так швидко не генеруються.
Тому правильніше використовувати вбудований в PyGame інструмент тіків на основі класу Clock.
clock = pygame.time.Clock()
clock.tick(60)
Ось цю величину (60) називають Frames Per Second (FPS).
У багатьох випадках виникає необхідність виконати певну дію через визначений час. Для цього доцільно запустити таймер. У pygame це можна зробити так. Клас time містить метод set_timer(), що має два параметри. Перший параметр - код події, другий - час у мілісекундах, що відраховується. Коли час минає, генерується подія з визначеним кодом, а таймер запускається повторно. Нагадаємо, що код події - це значення атрибута type.
Клас Surface
За допомогою класу pygame.Surface можна створювати додаткові поверхні. Після цього малювати їх на основний, який створюється методом pygame.display.set_mode(), або один на одному. Зображення виконується за допомогою методу blit().
При створенні екземпляра Surface безпосередньо від класу необхідно вказати ширину та висоту, подібно до того, як це відбувається при виклику set_mode(). Наприклад:
surf = pygame.Surface((150, 150))
Метод blit() застосовується до батьківської Surface, тоді як дочірня передається як аргумент. Також у метод треба передати координати розміщення верхнього лівого кута дочірньої поверхні в координатній батьківській системі. Наприклад:
screen.blit(surf, (50, 20))
Колір поверхні можна змінити методом fill(колір). Кольори в бібліотеці pygame представлені відповідно до моделі RGB кортежем з троьх чисел від 0 до 255.
surf.fill((0, 125, 255))
Колір можна задати назвою в лапках. Повний список назв кольорів можна знайти за цим посиланням.
surf.fill(‘red’)
Поверхні можна робити прозорими за допомогою їхнього методу set_alpha(). Аргумент змінюється від 0 (повна прозорість) до 255 (повна непрозорість).
surf.set_alpha(200)
З вивчених команд можемо написати невелику програму:
import pygame
pygame.init()
screen = pygame.display.set_mode((1000, 800))
surf = pygame.Surface((150, 150))
surf.fill(‘red’)
surf.set_alpha(200)
is_working = True
while is_working:
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_working = False
screen.blit(surf, (50, 20))
pygame.display.flip()
Зверніть увагу на останній рядок нашої програми. Насправді графічні об'єкти, які ми хочемо розмістити на основному екрані, спочатку потрапляють у спеціальний буфер. Щоб відобразити зміни стали видно, викликаємо функцію flip().
Подібною до flip() є функція pygame.display.update(), вони обидві оновлюють вміст вікна гри. Якщо функції update() не передавати аргументи, то оновлюватимуться значення всієї поверхні вікна. Однак можна передати дрібнішу прямокутну область або список таких. У цьому випадку оновлюватимуться лише вони.
Функція flip() вирішує проблему в інший спосіб. Вона дає виграш, якщо set_mode() були передані певні прапори (апаратне прискорення + повноекранний режим - pygame.HWSERFACE | pygame.FULLSCREEN, подвійна буферизація - pygame.DOUBLEBUFF, використання OpenGL - pygame.OPENGL). Можна всі прапори комбінувати разом (через |). При цьому, згідно з документацією, апаратне прискорення працює лише у повноекранному режимі.
Клас Rect
Ще одним ключовим класом у Pygame є Rect. Його примірниками є прямокутні області. Вони не мають графічного уявлення у вікні гри. Цінність класу полягає у властивостях та методах, що дозволяють керувати розміщенням поверхонь, виконувати перевірку їх перекриття та ін. Задаються прямокутники так:
Rect(left, top, width, height)
Rect((left, top), (width, height))
де *left, top* – координати лівого верхнього кута прямокутника, width, height – ширина та висота. Зверніть увагу, що початок координат (0, 0) знаходиться в лівому верхньому куті вікна.
Rect'и можна передавати у функцію pygame.display.update(). У цьому випадку оновлюватимуться лише відповідні області.
Метод поверхні get_rect() повертає примірник Rect, ширина і висота якого збігаються з такими поверхні. У прикладі метод get_width() повертає ширину поверхні, виводиться також ширина прямокутника (rect.width), щоб показати, що вони рівні.
Якщо в get_rect() не передавати аргументи, то верхній лівий кут екземпляра Rect буде в точці (0, 0).
Модуль pygame.draw
Функції модуля pygame.draw малюють геометричні примітиви на поверхні – екземплярі класу Surface. Як перший аргумент вони приймають поверхню. Тому при створенні тієї чи іншої поверхні її треба зв'язати зі змінною, щоб потім було що передати функції модуля draw.
pygame.draw.rect(Surface, color, Rect, width=0)
Намалювати прямокутник Rect на поверхні Surface, кольором color. Якщо товщина лінії width дорівнює нулю, прямокутник зафарбовується.
pygame.draw.line(Surface, color, start_pos, end_pos, width=1)
Намалювати лінію на поверхні Surface, кольором color, з початком у точці start_pos, кінцем у точці end_pos та товщиною лінії width.
pygame.draw.lines(Surface, color, closed, pointlist, width=1)
Намалювати лінію, що з'єднує точки з послідовності pointlist на поверхні Surface, кольором color, з товщиною лінії width. Кожна точка представлена парою координат. Якщо параметр closed дорівнює True, то кінцева точка з'єднується з початковою.
pygame.draw.circle(Surface, color, pos, radius, width=0)
Намалювати коло на поверхні Surface, кольором color, з центром у точці pos та радіусом radius. Якщо товщина лінії width дорівнює нулю, коло зафарбовується.
pygame.draw.ellipse(Surface, color, Rect, width=0)
Намалювати еліпс, обмежений прямокутником Rect, поверхні Surface, кольором color. Якщо товщина лінії width дорівнює нулю, еліпс зафарбовується.
pygame.draw.polygon(Surface, color, pointlist, width=0)
Намалювати багатокутник по точках з послідовності pointlist на поверхні Surface, кольором color, з товщиною лінії width. Кожна точка представлена парою координат. Якщо товщина лінії width дорівнює нулю, багатокутник зафарбовується.
Модуль pygame.font
Класи Font та SysFont знаходяться в модулі pygame.font і призначені для роботи зі шрифтами та текстом. Щоб створювати від цих класів об'єкти, модуль pygame.font необхідно попередньо ініціалізувати командою pygame.font.init() або виконати ініціалізацію всіх вкладених модулів бібліотеки Pygame командою pygame.init().
Від класів pygame.font.Font та pygame.font.SysFont створюються об'єкти-шрифти. Другий клас бере системні шрифти, тому конструктору достатньо передати ім'я шрифту. Конструктору Font треба передавати ім'я файлу шрифту. Наприклад:
font = pygame.font.SysFont('arial', 36)
font = pygame.font.Font('/адреса/Arial.ttf', 36)
Другий аргумент – це розмір шрифту у пікселях.
Дізнатися, які шрифти є в системі можна за допомогою функції pygame.font.get_fonts().
Для виведення написів використовуємо команду:
screen.blit(font.render(str(text), True, color), (left , top))
Події клавіатури
Взаємодія користувача з комп'ютером заснована на подіях, будь-які дії, що виконуються користувачем, породжують події - рух миші, натискання клавіш, спеціальних кнопок. Обробкою подій займається модуль pygame.event, який включає ряд функцій, найбільш важлива з яких pygame.event.get(), яка забирає з черги події, що відбулися.
У pygame, коли фіксується ту чи іншу подію, створюється відповідний об'єкт від класу Event. Вже із цими об'єктами працює програма. Примірники даного класу мають лише властивості, вони не мають методів. Усі екземпляри мають властивість type. Набір інших властивостей події залежить від type.
Крім події виходу із програми pygame.QUIT, події клавіатури може бути двох типів (мати одне з двох значень type) – клавіша була натиснута, клавіша була відпущена. Якщо ви натиснули і відпустили, то в чергу подій будуть записані обидва. Яке їх обробляти, залежить від контексту гри. Якщо ви затиснули і не відпускаєте її, то в чергу записується тільки один варіант - клавіша натиснута.
Події типу "клавіша натиснута" у полі type записується числове значення, що збігається зі значенням константи pygame.KEYDOWN. Події типу "клавіша відпущена" у полі type записується значення, що збігається зі значенням константи pygame.KEYUP.
Обидва типи подій клавіатури мають атрибути key і mod. У key записується конкретна кнопка, яка була натиснута або віджата. У mod – клавіші-модифікатори (Shift, Ctrl та ін), які були затиснуті в момент натискання або відтискання звичайної клавіші. Подій KEYDOWN також має поле unicode, куди записується символ натиснутої клавіші (тип даних str).
У бібліотеці pygame з подіями працює не лише модуль event. Так, модуль pygame.key включає функції, пов'язані виключно з клавіатурою. Тут функція pygame.key.get_pressed(), яка повертає кортеж двійкових значень. Індекс кожного значення відповідає своїй клавіатурній константі. Саме значення дорівнює 1, якщо кнопка натиснута, і 0 – якщо не натиснута. Наприклад,
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
print(‘Натиснута клавіша вліво’)
Весь перелік констант pygame, що відповідають клавішам клавіатури, дивіться у документації
Події миші
У 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. Він показує відносне усунення обох осях. За допомогою нього, наприклад, можна відстежувати швидкість руху миші.
Так само, як у випадку з клавіатурою в pygame є свій модуль для подій миші. Якщо потрібно відстежувати тривале затискання її кнопок, слід скористатися функцією get_pressed() модуля pygame.mouse. Тут є функція для зчитування позиції курсору – get_pos().
Функція mouse.get_pressed() повертає триелементний кортеж. Перший елемент (з індексом 0) відповідає лівій кнопці миші, другий – середній, третій – правою. Якщо значення елемента дорівнює True, то кнопка натиснута. Якщо False, значить – ні. Так вираз pressed[0] є істиною, якщо під нульовим індексом міститься True.
Щоб приховати курсор (наприклад, у грі, де керування здійснюється виключно клавіатурою), треба скористатися функцією pygame.mouse.set_visible(), передавши як аргумент False.
Створення власних подій
Події Pygame, створені користувачем, називаються «подіями користувача» або UserEvent.
my_event = pygame.USEREVENT + 0
У наведеному вище коді ми створюємо UserEvent, який викликається my_event, призначаючи йому ідентифікатор події. Pygame має загалом 32 слоти подій (ID), з яких перші 23 використовуються Pygame (попередньо визначені події). Ідентифікатори подій від 24 до 32 доступні для нашого використання.
pygame.USEREVENT має значення 24, яке ми можемо призначити визначеній нами події. Щоб створити другу подію, ви зробите pygame.USEREVENT + 1 (для ідентифікатора 25) і так далі.
Створення UserEvent — це лише перший крок. Наступним кроком є початок періодичної трансляції події за допомогою таймерів pygame. Ми будемо використовувати функцію set_timer(), яка прийматиме два параметри: UserEvent і часовий інтервал. UserEvent буде надіслано як сигнал події, який періодично повторюватиметься через вказаний проміжок часу. Простіше кажучи, якщо 1500 мілісекунд є інтервалом часу, сигнал події буде генеруватися кожні 1500 мілісекунд.
pygame.time.set_timer(my_event, 1500)
Як і зі звичайною подією , у ігровому циклі ми будемо переглядати список подій, pygame.event.get() і шукати подію, яку ми щойно створили. Якщо ця подія знайдена, ми перейдемо до коду, який хочемо виконати.
Модулі pygame.image та pygame.transform
Функція load() модуля pygame.image завантажує зображення та створює екземпляр Surface, на якому відображено це зображення. У load() передається ім'я файлу. "Рідним" форматом є BMP, проте якщо функція pygame.image.get_extended() повертає істину, то можна завантажувати ряд інших форматів: PNG, GIF, JPG та ін.
Якщо зображення немає прозорого шару, але він необхідний, то слід скористатися методом set_colorkey() класу Surface.
У форматі PNG з альфа-каналом (коли для точок можна налаштовувати ступінь прозорості; зазвичай встановлюється повністю прозоре тло) таких проблем немає.
До всіх екземплярів Surface рекомендується застосовувати метод convert(), який, якщо не передавати аргументи, переводить формат кодування пікселів поверхні у формат кодування пікселів головної поверхні. При виконанні гри це прискорює відмальовування поверхонь.
Якщо поверхня була створена на базі зображення з альфа-каналом, замість convert() треба використовувати метод convert_alpha(), так як перший видаляє прозорі пікселі (замість них буде чорний колір).
Функції модуля pygame.transform(), що змінюють поверхні, повертають нові. Першим аргументом їм передається вихідний Surface, другим – кортеж нових розмірів.
new_surf = pygame.transform.scale(surf, (surf.get_width() // 2, surf.get_height() // 2))
Функція flip(Surface, xbool, ybool) перевертає Surface по горизонталі та вертикалі, до втрати якості не призводить. Вказується поверхня та булевими значеннями осі перевороту.
Функція rotate(surface, angle) здійснює нефільтрований поворот проти годинникової стрілки. Аргумент кута є градусами і може бути будь-яким значенням з плаваючою точкою. Негативні значення кута будуть обертатися за годинниковою стрілкою.
Переміщення об'єктів
Для руху об'єктів використовуються методи move() і move_ip(), які мають об'єкти Rect, створені за допомогою функцій з pygame.draw. Перший створює нову фігуру такого ж типу, що знаходиться за заданим зміщенням, другий безпосередньо змінює положення наявної фігури.
Обробка зіткнень
Одна з багатьох важливих тем у розробці ігор – це виявлення сутичок. В основі алгоритмів обробки зіткнень лежить клас Rect. Примірниками цього класу є прямокутні області. Вони не мають графічного подання у вікні гри, але мають методи для виявлення зіткнень: якщо два або більше прямокутних об'єктів перетинаються один з одним або перебувають у контакті, це і є зіткнення. Будемо використовувати методи:
collidepoint(x, y) – перевірка попадання крапки у прямокутник;
colliderect(Rect) – перевірка перетину двох прямокутників.
Додаємо звук в ігровий процес
У Pygame для роботи з аудіо призначені модулі pygame.mixer та pygame.mixer.music. Модулі схожі, проте pygame.mixer в першу чергу адаптований для додавання та настроювання звукових ефектів у грі. В той час, як pygame.mixer.music – для додавання фонової музики.
Функція pygame.mixer.music.load() завантажує потокове аудіо, тобто не вантажить файл повністю, а робить це окремими порціями. В результаті можна програвати лише один файл за один раз. Однак можна ставити файли у чергу функцією queue(). Підтримує формат mp3 (але не в Ubuntu).
За допомогою функції music.play(play(loops=0, start=0.0, fade_ms = 0) файл починає відтворюватися, де loops - число повторень (0 - програвати один раз, 1 - двічі і т.д, -1 - нескінченне повторення); start – початковий час відтворення файлу (у секундах); fade_ms – загасання звуку після закінчення програвання (мс).
pygame.mixer.music.load('bach.ogg')
pygame.mixer.music.play(5) # Грає шість разів, а не п'ять!
pygame.mixer.music.queue('mozart.ogg')
Також можна використовувати дві функції: pause() – встановити відтворення на паузу; unpause() – продовжити відтворення з місця паузи.
Якщо нам потрібно зупинити відтворення, то використовується метод pygame.mixer.music.stop(), а для відтворення спочатку метод pygame.mixer.music.rewind(). Також часто використовуються методи set_volume(volume) – встановити рівень гучності (volume – речове значення від 0 до 1) та get_volume() – отримати поточне значення рівня гучності.
У pygame.mixer ключовим є клас Sound. Він дозволяє завантажувати, програвати та виконувати ряд інших дій із файлами форматів wav або ogg. При створенні екземпляра Sound конструктор передається ім'я файлу.
s = pygame.mixer.Sound("sounds/catch.ogg")
І далі, вже через змінну s можемо працювати з відтворенням даного ефекту. Клас Sound підтримує такі основні методи:
play (loops = 0, maxtime = 0, fade_ms = 0) - відтворення звуку;
stop() – зупинка відтворення;
set_volume(value) – налаштування гучності звучання;
get_volume() – повертає поточне значення гучності (число від 0 до 1).
import pygame
import sys
pygame.init()
sc = pygame.display.set_mode((400, 300))
pygame.mixer.music.load('Beethoven.ogg')
pygame.mixer.music.play()
sound1 = pygame.mixer.Sound('boom.wav')
sound2 = pygame.mixer.Sound('one.ogg')
while True:
for i in pygame.event.get():
if i.type == pygame.QUIT:
sys.exit()
elif i.type == pygame.KEYUP:
if i.key == pygame.K_1:
pygame.mixer.music.pause()
# pygame.mixer.music.stop()
elif i.key == pygame.K_2:
pygame.mixer.music.unpause()
# pygame.mixer.music.play()
pygame.mixer.music.set_volume(0.5)
elif i.key == pygame.K_3:
pygame.mixer.music.unpause()
# pygame.mixer.music.play()
pygame.mixer.music.set_volume(1)
elif i.type == pygame.MOUSEBUTTONUP:
if i.button == 1:
sound1.play()
elif i.button == 3:
sound2.play()
pygame.time.delay(20)
У прикладі вище завантажується фонова музика: pygame.mixer.music.load(). Функція не повертає жодного "музичного" об'єкта, тому результат її виклику не присвоюється змінною. За допомогою функції music.play() файл починає відтворюватися.
У програмі при натисканні клавіші 1 клавіатури музика ставиться на паузу: music.pause(). Клавіша 2 зменшує гучність удвічі: music.set_volume(0.5). Натискання 3 повертає гучність до попереднього рівня. Функція unpause() викликається на випадок, якщо музика була вимкнена (клавішою 1).
У прикладі створюються два об'єкти типу Sound. Вони мають свій метод play(). У цьому випадку файли програються при натисканні лівої та правої кнопками миші. Об'єкти Sound можуть програватися одночасно, оскільки зазвичай належать до різних каналів.
Якщо закоментувати виклики функцій pause() і unpause() і розкоментувати stop() і play(), результат буде схожий. Різниця в тому, що при використанні комбінації stop-play файл почне програватися спочатку, а при pause-unpause продовжиться з місця зупинки.