вторник, 31 мая 2011 г.

PyGame. Геометрические примитивы

Всем привет. В этой статье поговорим о рисовании геометрических примитивов средствами PyGame.
Перед изучением статьи читатель должен знать следующие вещи:
  • Знание синтаксиса и семантики языка Python ветки 2.х. (Если знаете С/С++, Java, PHP и т.д но не знаете Python то лучше не начинать изучать эти статьи, это разные языки и ничего общего у Python’a с ними нет).
  • Знание основ работы в IDLE(Python GUI) или умение запускать и редактировать программы на языке Python в других средах разработки.
  • Знание основных типов данных языка Python ветки 2.х. (числа, строки, последовательности/списки, словари, кортежи).
  • Умение работать в операционной системе Windows.
  • Знание информации, изложенной в предыдущих статьях.
Логически эта статья будет разделена на следующие блоки:
  • Введение в геометрические примитивы
  • Составление скелета программы на PyGame
  • Практика и Теория
  • Заключение
На этом все, приступим к изучению библиотеки PyGame.

Часть первая. Введение в геометрические примитивы.

Графический примитив — простейший геометрический объект, к графическим примитивам в Python можно отнести отрезок прямой, прямоугольник, окружность, эллипс, полигон и т.д. За рисование всех этих объектов отвечает класс Draw библиотеки PyGame. Этот класс содержит методы позволяющие рисовать на изображении графические примитивы, вот полный список методов и простое описание к ним:
  • pygame.draw.rect – метод рисует прямоугольник
  • pygame.draw.polygon – метод рисует полигон
  • pygame.draw.circle – метод рисует окружность
  • pygame.draw.ellipse – метод рисует эллипс
  • pygame.draw.arc – метод рисует часть эллипса
  • pygame.draw.line – метод рисует отрезок
  • pygame.draw.lines – метод рисует набор отрезков
  • pygame.draw.aaline – метод рисует сглаженный отрезок
  • pygame.draw.aalines – метод рисует сглаженные отрезки
Как видно класс Draw содержит только девять методов для рисования графических примитивов, ничего другого этот класс не содержит. Для всех этих девяти методов первый параметр это изображение, а последующие параметры описывают свойство примитива, который должен быть нарисован (радиус, начальные координаты, цвет и т.д). Но для начала нам следует разобраться в том, что такое изображение. Помните в предыдущей статье, когда мы писали программу “Hello, Word” там была такая строка кода?:
screen = pygame.display.set_mode((410,100),0,32)
Те, кто хорошо поняли прошлую статью, могут сказать, что этот метод создает окно с размерами 400х100. На самом деле все немного не так, вернее все так только я тогда полностью не объяснил весь метод, если простыми словами, то этот метод создает окно и возвращает изображение, которым является поверхность окна. Наверное я вас запутал, сейчас попробую это исправить, давайте рассмотрим этот метод более детально.
pygame.display.set_mode(resolution=(0,0), flags=0, depth=0): return Surface, здесь resolution – это список чисел в котором хранится размер окна по оси х и у, flags – флаг окна (полный экран, изменяемые размеры и и т.д), depth – глубина цвета. Как видно описание такое же, как и в прошлой статье, но есть одно но, тогда мы не рассмотрели то, что возвращает этот метод. Как видно этот метод возвращает объект класса Surface. Surface – если перевести на русский будет – поверхность, в целом это правильный перевод, но что тогда принимать за поверхность? Поверхность в данном контексте это объект, на котором мы можем рисовать, например если сравнивать PyGame с другими графическими библиотеками, то там поверхность рисования называется Canvas, Image и т.д. По сути, класс Surface это представление изображения в библиотеки PyGame. Далее изображение и поверхность рисования будут иметь одинаковый смысл. Про класс Surface мы с вами поговорим в следующих статьях, сейчас вам главное понять то, что Surface это изображение в PyGame.
Теперь мы знаем, что есть девять методов, которые рисуют на изображении простые фигуры, но изучать их сейчас мы не будем, все это будет потом, а сейчас нам надо построить скелет программы на PyGame.

Часть вторая. Составление скелета программы на PyGame.

Сейчас мы будем создавать скелет программы на PyGame, вы можете сказать, зачем нам это если мы сейчас должны разбирать средства для рисования простых фигур. Дело в том что в будущем нам не стоит отдавать часть статьи на описание того как создается скелет программы на PyGame, мы можем использовать эту часть в более нужном ручье, поэтому мы сейчас создадим этот скелет, а дальше, во всех будущих статьях будем использовать этот скелет как шаблон.
Ну что, приступим? Заходим в IDLE и открываем меню «File» и выбираем «New Window», в открывшимся окне опять заходим в меню «File» и выбираем «Save As..», сохраняем этот файл под именем pygame_template.py, не забудьте что расширение “.py” следует указать явным способом.
Ну вот, файл уже готов осталось написать код, сначала импортируем библиотеку PyGame это делается таким же способом как в прошлый раз:
import pygame, sys   
from pygame.locals import *
Теперь библиотека импортирована в наш код, смотрим на структуру программы из предыдущей статьи и видим, что теперь мы должны инициализировать сам PyGame, делается это, как мы помним одной командой:
pygame.init()
Теперь надо создать окно, но для этого надо инициализировать данные, по которым будет строиться это окно, первое, что нам надо – инициализировать значения размера окна и его заголовок, для размера мы введем переменные windows_width и windows_height, а для заголовка переменную windows_title, код инициализации этих переменных ниже:
(windows_width, windows_height, windows_title) = (600, 400, "PyGame Main Window")
Имея размер и заголовок, мы без проблем можем создать окно и установить для него заголовок:
screen = pygame.display.set_mode((windows_width,windows_height),0,32)
pygame.display.set_caption(windows_title)
Окно готово, но окну еще нужен цвет заднего фона, мы помним, что цвет в PyGame это список из трех чисел, где каждое число это вектор цвета в системе RGB, задний фон окна будет белого цвета, а назовем его windows_bgcolor:
windows_bgcolor = (255,255,255)
Теперь осталось только инициализировать флаг главного цикла:
mainLoop = True
На этом блок инициализации для нашего скелета готов, но так как этот скелет будет использоваться, как шаблон в будущем, то мы вставим комментарий, который будет говорить, о том, что блок инициализации данных находится здесь, и теперь, когда я буду писать “добавить инициализацию для какой, то переменной” то код инициализации данных будет писаться после этого комментария. Вот сам комментарий:
#initial data here
Теперь пришло время проверки флага главного цикла, если этот флаг имеет значение True, то продолжается цикл, в котором идет обработка событий, формирование кадра и отображение этого кадра, иначе будет происходить освобождение ресурсов и выход из программы. Флаг проверяется следующим кодом:
while mainLoop:
Теперь, если смотреть на схему, мы должны обработать события, обработка событий очень трудная тема для начинающих, в следующих статьях еще более подробно разберем , сейчас вам надо понять то, что для обработки событий будет введен итератор event, этот итератор будет проходить по списку, который возвращает метод pygame.event.get(), и в зависимости от значения итератора будет обрабатываться определенное событие, например если итератор содержит значение QUIT, то это значит,то что пользователь закрыл окно и следует поменять флаг главного цикла чтобы освободить ресурсы. Может казаться очень трудным ну это всего три строчки кода:
for event in pygame.event.get(): 
  if event.type == QUIT: 
   mainLoop = False
Теперь следует сформировать кадр, для начала зальем кадр цветом заднего фона, за это отвечает метод fill(color, rect=None, special_flags=0) объекта screen, как видно этот метод принимает в параметрах список из трех чисел, этот список у нас уже есть и называется он windows_bgcolor, так что код заливки выглядит так:
screen.fill(windows_bgcolor)
У скелета, кроме блока инициализации, будет еще блок формирования кадра, так что вставляем сюда комментарий:
#create frame here
Ну и окончательная часть цикла - это отображение кадра, за это отвечает метод pygame.display.update(), он не требует никаких параметров в данном случае, поэтому просто добавляем его в код:
pygame.display.update()
С циклом мы разобрались, теперь пришло время освободить ресурсы, но так как Pyton динамический язык программирования, то лучше дать это сделать ему самому, тут мы только вставим код закрытия PyGame, и комментарий, которым будет обозначать блок освобождений данных:
pygame.quit()
#destroy data here
Вот и все, на этом формирование скелета готово, теперь мы можем использовать его для изучения функций рисования простейших фигур, в итоге должен быть получится следующий листинг:
import pygame, sys 
from pygame.locals import * 
pygame.init() 
(windows_width, windows_height, windows_title) = (600, 400, "Simple Figure") 
screen = pygame.display.set_mode((windows_width,windows_height),0,32) 
pygame.display.set_caption(windows_title) 
windows_bgcolor = (255,255,255) 
mainLoop = True 
#initial data here 
while mainLoop: 
 for event in pygame.event.get(): 
  if event.type == QUIT: 
   mainLoop = False 
 screen.fill(windows_bgcolor) 
 #create frame here 
 pygame.display.update() 
pygame.quit() 
#destroy data here

Часть третья. Практика и теория.

Для начала открываем pygame_template.py, и сохраняем его прд название simpleDraw.py, и изменяем значение переменной windows_title на “Simple Figure”. Первый метод в списке методов рисования это pygame.draw.rect(Surface, color, Rect, width=0): return Rect, где Surface – изображение на котором будет рисоваться прямоугольник, color – цвет прямоугольника, Rect – прямоугольная область в которой будет рисоваться прямоугольник, width – ширина обводки фигуры, если width принимает значение 0, то рисуется закрашенная фигура. Теперь нам надо инициализировать данные, которые будут использоваться в качестве параметров для этого метода. Давайте нарисуем два прямоугольника - один будет залитый, а второй с обводкой, для начала в блоке инициализации опишем две переменных, которые будут отвечать за цвет этих двух фигур, названия у них - rect_1_color и rect_1_color, первый будет фиолетовый, а второй – синим, пишем код инициализации в блоке инициализации данных:
rect_1_color = (255,0,255) 
rect_2_color = (0, 0, 255)
Теперь надо инициализировать область, в которой будет рисоваться прямоугольник, прямоугольная область это класс Rect, в его конструкторе используется два списка, в первом списке хранятся две координаты – это позиции по оси х и у, от которых будет рисоваться прямоугольник, во втором списке хранится два числа - это ширина и высота прямоугольника. Теперь настало время инициализировать эти прямоугольные области, в которых будут рисоваться фигуры, назовем их rect_1_rect и rect_2_rect, пишем код инициализации этих зон:
rect_1_rect = Rect((10,10),(100,100)) 
rect_2_rect = Rect((150,150),(300,100))
Как видите, в этом коде инициализируются две прямоугольные области, первая начитается с координат 10х10 и имеет ширину в 100px и высоту в 100px, а вторая область начинается с координат 150х150 и имеет ширину 300px и высоту в 100px.
Теперь надо объявить переменные, которые будут отвечать за ширину обводки, так как первый прямоугольник будет закрашен, то его ширина равна 0, у второго ширина линии обводки будет равна 4, назовем эти переменные rect_1_width и rect_2_width, и пишем следующий код:
rect_1_width = 0 
rect_2_width = 4
Все данные для того чтобы нарисовать прямоугольники есть, теперь осталось только реализовать рисование, этот код следует писать в блоке формирования кадра:
pygame.draw.rect(screen, rect_1_color, rect_1_rect, rect_1_width) 
  pygame.draw.rect(screen, rect_2_color, rect_2_rect, rect_2_width)
Как видите, тут используются все данные, которые мы объявили. Давайте проверим что получилось, заходим в меню «Run» и выбираем пункт «Run Module», как видите на окне белого цвета нарисовано два прямоугольник, один закрашенный фиолетовым цветом, а второй имеет синею обводку размером 4px.
Прямоугольники это кончено круто, но рисовать различные фигуру только с помощь прямоугольника проблематично, особенно если надо нарисовать звезду или треугольник, поможет нам в этом метод polygon класса draw. Расмотрим этот метот pygame.draw.polygon(Surface, color, pointlist, width=0): return Rect, здесь Surface – изображение на котором будет нарисована фигура, color – цвет фигуры, pointlist – коржет из списков, где у списка есть два значение – позиция на экране по двум осям х и у, минимальное количество списков в кортеже – 2, width – ширина обводки фигуры, если width принимает значение 0, то рисуется закрашенная фигура. Вроде бы все понятно и ничего трудного нет, если нет, то приступим. Первое что нам надо это определить цвет будущего полигона, сделаем его красным и дадим название переменной - poligon_color, пишем код в блок инициализации:
poligon_color = (255, 0, 0)
Цвет полигона уже есть, теперь надо создать кортеж из списков, которые представляют собой координаты точек, по которым будет строиться полигон. В этом примере будет рисоваться простой треугольник, давайте опишем в блоке инициализации простой кортеж из трех списков, назовем кортеж poligon_points:
poligon_points = [(200,200),(300,300),(400,150)]
Осталось только описать переменную poligon_width, которая отвечает за ширину обводки полигона, сделаем ее равной 0, а это значит то, что полигон будет полностью закрашен:
poligon_width = 0
Вот и все, для метода Draw.poligon у нас есть все данные, теперь осталось только в блоке формирования кадра написать код который рисует полигон:
pygame.draw.polygon(screen, poligon_color, poligon_points, poligon_width)
Теперь можно проверить что получилось, запускаем программу и видим два старых прямоугольника и новый треугольник красного цвета:
Следующий в очереди это метод рисования круга pygame.draw.circle(Surface, color, pos, radius, width=0): return Rect, где Surface – изображение в котором будет рисоваться круг, color – цвет фигуры, pos – позиция центра фигуры, radius – радиус круга. Для начала в блоке инициализации данных опишем цвет круга, назовем его circle_color, и пусть он будит синего цвета:
circle_color = (0,0,255)
В школе нас учили тому, что чтобы нарисовать круг нам надо знать координаты его центра и радиус, координаты в PyGame как мы уже поняли это список из двух чисел, радиус это обычное число, для радиуса введем переменную circle_radius, а для позиции список circle_pos, вот код из блока инициализации данных:
circle_pos = (100,300) 
circle_radius = 75
Круг будет залитым это значит, то, что переменная circle_width которая отвечает за ширину обводки фигуры должна иметь значение 0:
circle_width = 0
Все данные, которые должны участвовать в методе pygame.draw.circle готовы, теперь мы можем написать следующий код в блоке формирования кадра:
pygame.draw.circle(screen, circle_color, circle_pos, circle_radius, circle_width)
Запустим программу и проверим, что у нас получилось:
Как видно круг синего цвета нарисуется на ура.
Теперь пришло время для рисования эллипса, за это отвечает метод pygame.draw.ellipse(Surface, color, Rect, width=0): return Rect, где Surface – изображение на котором будет рисоваться фигура, color – цвет фигуры, Rect – прямоугольная область в которой будет рисоваться эллипс , width – ширина обводки фигуры, если принимает значение 0, то фигура закрашена. Ну что, приступим к инициализации данных для этого метода, вначале описываем цвет, для этого введем переменную ellipse_color, эллипс будет зеленого цвета:
ellipse_color = (0,255,0)
Теперь надо инициализировать прямоугольную область, в которой будет рисоваться эллипс, как мы помним, прямоугольная область - это объект класса Rect, назовем этот объект ellipse_rect, вот код инициализации для него:
ellipse_rect = Rect((200,10),(350,150))
Ни и в конце осталось описать размер линии обводки, как и в предыдущем примере будем использовать залитую фигуры, для этого переменная ellipse_width которая отвечает за ширину обводки должна принять значение 0:
ellipse_width = 0
Для метода Draw.ellipse все данные готовы, теперь можем нарисовать этот эллипс, пишем код в блоке формирования кадра:
pygame.draw.ellipse(screen, ellipse_color, ellipse_rect, ellipse_width)
Пришло время посмотреть на то, что же получилось, запускаем программу, и видим то что зеленый эллипс нарисован.
Теперь настало время для самой странной функции рисования примитивов, это функция рисования части эллипса, давайте ее рассмотрим - pygame.draw.arc(Surface, color, Rect, start_angle, stop_angle, width=1): return Rect – здесь, Surface – изображение на котором будем рисоваться фигура, Rect – прямоугольная область в котором будет нарисован эллипс, start_angle – начальный угол рисования, stop_angle – конечный угол рисования, width – ширина линии обводки, по умолчанию равно 1, если будет задано 0 то фигура не рисуется. Практически это полная копия метода Draw.ellipse, но есть два дополнительных параметра - начальный угол и конечный, эти значения в системе радиан, для этого нам понадобится модуль math, находим код, где находится импортирование библиотеки PyGame, и перед этим кодом вставляем вот этот код:
import math
Теперь надо задать цвет обводки, назовем его arc_color и сделаем черным цветом, пишем код в блоке инициализации:
arc_color = (0,0,0)
Теперь надо задать прямоугольную область для рисования эллипса, назовем эту область arc_rect:
arc_rect = Rect((200,250),(300,100))
Теперь пришло время для задания начального угла, как известно значения в радианах могут быть в диапазоне –ПИ..ПИ, где ПИ – математическая константа, это значит то что если начальный угол будет –ПИ, а конечный ПИ, то будет нарисована полная обводка фигуры, в нашем случае начальный угол будет равен ПИ а конечный ПИ*0.5, назовем начальный и конечный угол следующими именами - arc_start_angle и arc_end_angle, вот код инициализации этих переменных:
arc_start_angle = -math.pi 
arc_end_angle = math.pi * 0.5
Все готово для того чтобы написать код рисования сегмента эллипса в блоке формирования кадра:
pygame.draw.arc(screen, arc_color, arc_rect, arc_start_angle, arc_end_angle)
Давайте запустим и посмотрим что получилось:
Как видите, нарисован сегмент эллипса вернее обводка сегмента ширенной в 1px, параметр width мы не указывали.
Теперь пришло время разобрать методы, которые рисуют линии. Давайте рассмотрим два метода, которые практически идентичны, первый рисует обычный отрезок - pygame.draw.line(Surface, color, start_pos, end_pos, width=1): return Rect, а второй рисует сглаженный отрезок - pygame.draw.aaline(Surface, color, start_pos, end_pos, blend=1): return Rect. Практически все параметры похожи кроме последнего, давайте рассмотрим все эти параметры. Surface – изображение, на котором будет рисоваться отрезок, color – цвет отрезка, start_pos – координата начальной точки отрезка, end_pos – координата последней точки отрезка, ну а теперь два разных параметра, у первого метода это width – ширина отрезка, у второго это – blend – коэффициент сглаживания отрезка. Теперь настало время инициализировать данные для этих методов, для начала нам понадобятся координаты начальных и конечных точек отрезка:
line_start_pos = (100,100) 
line_end_pos = (300,300) 
aaline_start_pos = (110,100) 
aaline_end_pos = (310,300)
Еще нам понадобится цвет отрезка, но я решил для всех отрезков использовать черный цвет и назвал переменную lines_color, осталось только написать ее инициализацию:
lines_color = (0,0,0)
Ширину линии для первого метода мы указывать не будем, по умолчанию рисуется линия размером в 1px, а для второго оставим тоже по умолчанию коэффициент сглаживания, чтобы посмотреть, чем отличаются эти методы, так что пишем код в блоке формирования кадра:
pygame.draw.line(screen, lines_color, line_start_pos, line_end_pos) 
pygame.draw.aaline(screen, lines_color, aaline_start_pos, aaline_end_pos, 0)
Запускаем, чтобы посмотреть что получилось, как видим первая линия, сделанна из пикселей и это видно, а вторая кажется сглаженной, так и есть.
Теперь пришло время рассмотреть последний метод. Но как последний, ведь есть еще aalines? Дело в том, что смысл методов аа* нам уже понятен, да и два эти последние методы очень похожи, так что не вижу смысла полностью описывать метод aalines, так что рассмотрим только метод pygame.draw.lines(Surface, color, closed, pointlist, width=1): return Rect, здесь Surface – изображение на котором будет рисоваться набор отрезков, color – цвет отрезков, closed – флаг, по которому определяется соединять последнею точку последнего отрезка с начальной точкой первого отрезка, то есть возможность замкнуть все отрезки, pointlist – кортеж из координат отрезков, минимальное количество координат - 2, width – ширина отрезков. Теперь начнем инициализацию данных, цвет отрезков уже есть, осталось только описать кортеж координат, назовем его lines_width, вот код инициализации этого кортежа:
lines_points = [(40,50),(300,90),(70,300),(400, 232),(245,100)]
Так же еще понадобится флаг замкнутости отрезков, назовем его lines_closed но присвоим ему значение False, в этом примере отрезки не будут замкнуты:
lines_closed = False
Теперь осталось описать ширину отрезков, пусть отрезки будут шириной в 8px:
lines_width = 8
Все готово, осталось только добавить код в блок формирование кадра:
pygame.draw.lines(screen, lines_color, lines_closed,lines_points,lines_width)
На этом все, теперь можно запустить и посмотреть, что же вышло:
В итоге должен быть написан следующий код:
import math 
import pygame, sys 
from pygame.locals import * 
pygame.init() 
(windows_width, windows_height, windows_title) = (600, 400, "Simple Figure") 
screen = pygame.display.set_mode((windows_width,windows_height),0,32) 
pygame.display.set_caption(windows_title) 
windows_bgcolor = (255,255,255) 
mainLoop = True 
#initial data here 
rect_1_color = (255,0,255) 
rect_1_rect = Rect((10,10),(100,100)) 
rect_1_width = 0 
rect_2_color = (0, 0, 255) 
rect_2_rect = Rect((150,150),(300,100)) 
rect_2_width = 4 
poligon_color = (255, 0, 0) 
poligon_points = [(200,200),(300,300),(400,150)] 
poligon_width = 0 
circle_color = (0,0,255) 
circle_pos = (100,300) 
circle_radius = 75 
circle_width = 0 
ellipse_color = (0,255,0) 
ellipse_rect = Rect((200,10),(350,150)) 
ellipse_width = 0 
arc_color = (0,0,0) 
arc_rect = Rect((200,250),(300,100)) 
arc_start_angle = -math.pi 
arc_end_angle = math.pi * 0.5 
line_start_pos = (100,100) 
line_end_pos = (300,300) 
aaline_start_pos = (110,100) 
aaline_end_pos = (310,300) 
lines_color = (0,0,0) 
lines_points = [(40,50),(300,90),(70,300),(400, 232),(245,100)] 
lines_closed = False 
lines_width = 8 
while mainLoop: 
  for event in pygame.event.get(): 
  if event.type == QUIT: 
  mainLoop = False 
  screen.fill(windows_bgcolor) 
  #create frame here 
  pygame.draw.rect(screen, rect_1_color, rect_1_rect, rect_1_width) 
  pygame.draw.rect(screen, rect_2_color, rect_2_rect, rect_2_width) 
  pygame.draw.polygon(screen, poligon_color, poligon_points, poligon_width) 
  pygame.draw.circle(screen, circle_color, circle_pos, circle_radius, circle_width) 
  pygame.draw.ellipse(screen, ellipse_color, ellipse_rect, ellipse_width) 
  pygame.draw.arc(screen, arc_color, arc_rect, arc_start_angle, arc_end_angle) 
  pygame.draw.line(screen, lines_color, line_start_pos, line_end_pos) 
  pygame.draw.aaline(screen, lines_color, aaline_start_pos, aaline_end_pos, 0) 
  pygame.draw.lines(screen, lines_color, lines_closed,lines_points,lines_width) 
  pygame.display.update() 
pygame.quit() 
#destroy data here

Часть четвертая. Заключение.


В итоге мы рассмотрели полностью класс Draw библиотеки PyGame, поняли, что из себя представляет класс Surface и написали шаблон, который мы будем использовать в следующих статьях. В следующей статье мы поговорим об событиях в PyGame.
Если есть, вопросы или проблемы по статье обращайтесь ко мне в Л.С.
Все всем пока, желаю удачи в геймдеве. Спасибо за внимание с вами был noTformaT.

P.S.


Если вы просто скопируете программный код с этой статьи то он возможно не запустится, связано это с тем что табуляций и пробелы в Python имеют определенный смысл. Если это произошло, и программа зависла, то требуется перезагрузить терминал интерпретатора открыв в терминале меню «Shell» и выбрав пункт «Restart Shell».
И на последок для тех, кто ничего не узнал нового для себя в этой статье, введите в поиске google фразу «скорость света в попугаях/сек», узнаете для себя очень много. И что будет, если в слове гугл, заменить все буквы «г» на букву «ф»? Толком ничего, убедитесь в этом и попробуйте зайти на сайт foofle.com