Последнее время из каждого чайника напористо лезет реклама обучения языку Python. И это не удивительно, поскольку Python уверенно занял лидирующее положение в качестве первого языка для изучения программирования. А его простота, обилие вспомогательных модулей и скорость написания кода сделали его лидером для обучения детей программированию, полностью вытеснив, популярные лет 20 назад QBasic и Pascal.
Обучаем играючи...
Последние тренды в обучении детей сводятся к игровым механикам. Чтобы заинтересовать ребенка программированием - преврати обучение в игру или предложи научиться игры создавать. Из-за этой логики одним из самых популярных модулей для Python, используемых в обучении стал Pygame, позволяющий создавать простые и не очень игры, без глубокого погружения в тонкости реализации, что очень удобно для детей в процессе начального освоения программирования.
О чем статья?
В этой статье я хотел бы поделиться с хабравцеми и маленькими хабрятами одним очень удобным дополнением для Pygame, найденным на просторах интернета буквально пару дней назад, но покорившем мое сердце. А все от того, что модуль, о котором пойдет речь, упрощает процесс создания 2D игрушек, делая его простым, наглядным, быстрым и увлекательным.
Маленький ликбез о плиточных играх
Огромное количество плоских 2D игрушек относятся к так называемым "плиточным" играм, в которых основой построения игрового мира служат минимальные элементы - плитки, чаще всего квадратные с нанесенной на них текстурой.
К плиточным играм относятся и Сапёр и легендарный Марио и практически все стратегии на развитие территорий и пошаговые стратегии.
В основе построения мира лежит поле, составленное из отдельных квадратиков, каждый из которых может быть закрашен своим цветом или заполнен какой то текстурой.
Задача программиста нарисовать этот квадратный мир с помощью кода и научить персонажа в этом мире жить. Именно за простое создание игрового мира и отвечает модуль под названием TileTool.
TileTool - может всё или почти всё?
Устанавливаем модуль в командной строке
pip install tiletool
И через минуту мы готовы приступать.
У модуля на данный момент имеется три режима работы, то есть три варианта сборки мира.
Генератор прямоугольников
Создание мира из *.txt или *.csv файлов
Создание мира из файлов *.tmx, созданных в визуальном редакторе плиточных уровней Tiled (https://www.mapeditor.org/ - сайт редактора)
Для начала необходимо создать минимальный каркас проекта на Pygame, выглядит он так:
import pygame #Импортируем модуль Pygame pygame.init() #Инициализируем инструментарий Pygame window=pygame.display.set_mode((700, 640)) #Создаем окно шириной 700 и высотой 640 while True: #Бесконечный цикл, в котором будет "жить" игра for i in pygame.event.get(): #Перебираем все события, происходящие в окне if i.type == pygame.QUIT: #Если событие совпадаем с "закрытием окна", то есть нажатием на креcтикстик pygame.quit() #Выходим из Pygame pygame.display.update() #Обновляем экран
Данный код создает игровое окно и запускает цикл в котором будут происходить все события игры, а также делает возможным закрытие окна.
Теперь перейдем к созданию мира и сделаем это тремя разными способами.
Способ №1. Генератор.
TileTool позволяет создать прямоугольную область в виде контура или с заполнением клетками. Для этого до бесконечного цикла создаем объект на базе класса TileTool.
#Импортируем модуль TileTool import tiletool #Cоздаем первый объект TileTool с шириной в 7 и высотой 5 тайлов #в координатах x=120 и y=120 с включенной заливкой #Необязательный параметр fill принимая значение 1, заполняет весь создаваемый прямоугольник плитками,мии #значение fill=0, оставляет внутреннее пространство пустым Box=tiletool.TileTool((7,5),120,120,fill=1) #К созданному объекту Box применяем метод fill_dict, связывая таким образом #элементы созданной матрицы с графическими файлами тайлов Box.fill_dict((1,0),('platform2.png','Empty'))
Тут требуется некоторое пояснение. В результате создания объекта, формируется двумерная матрица или таблица, такого вида:

в ней "1" стоят там, где позднее будут подставлены плитки, а "0" там, где останется пустое пространство.
Метод fill_dict собственно и нужен для того, чтобы соединить графические файлы с матрицей, заменив единички на файл с картинкой, а вместо нуликов оставить пустое пространство.
Box.fill_dict((1,0),('platform2.png','Empty')) # (1, 0) - кортеж с возможными значениями матрицы # ('platform2.png','Empty') - соответствующий кортеж замен # цифра 1 из первого котрежа заменится на изображение из файла platform2.png, # цифра 0 заменится на "пустоту"
Всё, прямоугольник создан и заполнен тайлами. Осталось его отобразить в нашем окне.
while True: #Внутри бесконечного цикла к объекту Box применяем метод blit_tiles, в который передаем имя нашего окна Box.blit_tiles(window)
Одна из приятных фишек модуля заключается в том, что созданный прямоугольник, или, как многие уже догадались платформу, можно заставить двигаться буквально одной командой.
#Добавим в бесконечный цикл строчку: Box.moving_tile((300,300),1,500,'HORIZONTAL',window) #(300, 300) - левая или верхняя граница при движении платформы #1 - скорость движения #500 - значение правой или нижней границы движения платформы #'HORIZONTAL' или 'VERTICAL' - направление движения горизонтально или вертикально #window - имя окна #И наша платформа будет двигаться горизонталь��о от точки (300,300) до (500,300) с минимальной скоростью 1. #Если это слишком быстро, можно настроить частоту кадров с помощью метода tick модуля Pygame #А чтобы движущийся объект не оставлял за собой след, #над строкой с движением добавим заливку окна черным цветом window.fill((0,0,0))
В результате полный код примера с генератором платформ будет выглядеть так:
import pygame import tiletool pygame.init() window=pygame.display.set_mode((700, 640)) #creating the window(создаем окно) Box=tiletool.TileTool((7,5),120,120,fill=0) #Создаем платформу Box.fill_dict((1,0),('platform.png','Empty')) #Связываем графические файл и платформой. Файл platform.png должен содержать изображения тайла и лежать в папке с проектом while True: window.fill((0,0,0)) #Заливаем окно черным для очистки следа от движущегося объекта Box.blit_tiles(window) #Отображаем платформу на экране Box.moving_tile((300,300),1,500,'HORIZONTAL',window) #Заставляем платформу двигаться for i in pygame.event.get(): if i.type == pygame.QUIT: pygame.quit() pygame.display.update()
Всего четыре строчки кода, а у нас уже есть движущаяся платформа, которая ко всему прочему, является наследницей класса pygame.sprite.Sprite, что позволяет добавлять ее в группы и проверять коллизии.
Способ 2. Создание мира из *.txt или *.csv файлов
Многие визуальные редакторы миров умеют экспортировать созданные слои, карты в формат csv или txt. Некоторые разработчики пользуются Excel'ем для рисования уровней. О вкусах не спорят. Тем не менее, TileTool прекрасно работает с подобными форматами. Внешне нарисованный в txt мир выглядит как то так:

По сути своей это такая же матрица, что создавалась Генератором в первом примере. Отличие лишь в том, что задача модуля не создать, а прочитать готовую матрицу из файла и связать ее с текстурами тайлов.
#Импортируем модули import pygame import tiletool pygame.init() #Инициализируем инструментарий Pygame window=pygame.display.set_mode((800, 800)) #Создаем игровое окно #Cоздаем объект на основе csv-файла(файла с разделителем) в координатах(100,100) Box1=tiletool.TileTool('test.csv',100,100) #Связываем ключи из файла с картинками, для 0 передаем "empty" Box1.fill_dict((42,21,0),('platform.png','platform2.png','empty')) #Cоздаем объект на основе txt-файла с разделителем в координатах(0,0) Box2=tiletool.TileTool('temp.txt',0,0) #Cвязываем ключи из файла с картинками, для -1 передаем "empty" Box2.fill_dict((10,-1),('platform.png','empty')) while True: window.fill((0,0,0)) #Размещаем оба объекта в окне Box1.blit_tiles(window) Box2.blit_tiles(window) #Первый объект заставляем циклично двигаться Box1.moving_tile((300,500),1,500,'VERTICAL',window) for i in pygame.event.get(): if i.type == pygame.QUIT: pygame.quit() pygame.display.update()
Небольшое замечание по строчке
Box1.fill_dict((42,21,0),('platform.png','platform2.png','empty'))
В файлах txt и csv в открытом виде видны значения, используемые в построении миров. В методе fill_dict в первом кортеже необходимо перечислить все значения используемые в файлах, а во втором кортеже сопоставить значениям имена графических файлов, а чтобы оставить "пустоту", достаточно передать 'empty'.
Как и в первом примере - всего пара строк и из текстовых файлов получаются готовые платформы.
Способ 3. Создание мира из файлов *.tmx (редактор миров Tiled)
Не скрою, это мой любимый способ, поскольку визуальный редактор уровней Tiled в разы упрощает и ускоряет процесс создания больших и сложных уровней для двумерных игрушек. Умеет работать как с плоскими картами, так и с изометрией, что дает возможности создавать псевдо 3D игры.

Проект Tiled содержит в себе файлы *.tsx, *.tmx, графические файлы тайлов.
Одно из основных преимуществ редактора заключается в возможности создания многослойных уровней, что позволяет разделить фоновое изображение, передний, средний планы, добиваясь эффекта параллакса в движении, а кроме того, в одном файле нарисовать и статические элементы и на отдельных слоях платформы, которым уже в дальнейшем с помощью TileTool можно задать режимы движения.
И так, к практике:
import tiletool import pygame window=pygame.display.set_mode((800, 800)) #Создаем два объекта из одного файла desert1.tmx, #отличаться объекты будут номером слоя, за который будут отвечать. #Так, Box1 становится хозяином слоя №1 и мы может независимо управлять этим слоем #Box2 становится владельцем слоя №2, который мы позднее заставим двигаться. Box1=tiletool.TileTool('desert1.tmx',0,0,layer=1) Box2=tiletool.TileTool('desert1.tmx',0,0,layer=2) #Нам не нужно вызывать метод fill_dict, так как вся информация о изображениях, #привязанных к ключам, указана в .tsx файле, относящемуся к данному .tmx файлу while True: window.fill((0,0,0)) Box1.blit_tiles(window) Box2.blit_tiles(window) Box2.moving_tile((0,0),1,200,'HoriZONTAL',window) for i in pygame.event.get(): if i.type == pygame.QUIT: pygame.quit() pygame.display.update() #Oбновляем экран
И снова, всего три строчки и полчаса сэкономленного времени, которые можно потратить не на отрисовку игрового мира, а на работу с коллизиями и игровой механикой.
Способ 4 (бонусный):
У модуля TileTool есть еще один приятный метод, позволяющий в пару строк создавать персонажа и нацеливать на него виртуальную камеру, которая будет следить за ним, создавая иллюзию движения. Выглядит это так:
А реализуется примерно так:
import tiletool import pygame window=pygame.display.set_mode((700, 640)) #Cоздаем объект класса TileTool на основе первого слоя .tmx файла. b1=tiletool.TileTool('desert.tmx',0,0,layer=1) #Загружаем изображение и конвертируем его в подходящий формат, теперь в переменной filename находится файла с изображением нашего героя. filename=pygame.image.load('platform.png').convert_alpha() #Cоздаем объект класса platforms_objects в координатах x=100,y=200, #за ним будет следовать камера. Также передаем в метод изображение героя. f1=tiletool.platforms_objects(100,20,filename) #Cледующие 2 строчки ограничивают частоту кадров в секунду clock = pygame.time.Clock() fps = 300 #Kамера будет следить за объектом f1 в координатах (0-1000) по x и (0-1000) по y. #При выходе из этих координат объект выйдет за пределы экрана. camera_obj=tiletool.camera(1000,1000,f1) while True: window.fill((0,0,0)) #Заливаем окно черным для очистки следа от движущегося объекта. clock.tick(fps) #Настраиваем скорость обновления экрана. #Oтображаем объект в окне. Необходимо передать объект класса camera во все вызовы blit_tiles. b1.blit_tiles(window,camera_obj) #Oтображаем объект platforms_objects в окне. #Следует передать вызов метода drawCam, #как в примере вместо свойства rect объекта, #чтобы объект мог отображаться в координатах, нужных камере. window.blit(f1.image,camera_obj.drawCam(f1)) for i in pygame.event.get(): if i.type == pygame.QUIT: pygame.quit() # Эти сторочки отвечают за движение объекта, используйте стрелки для движения. keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: f1.rect.x-=1 elif keys[pygame.K_RIGHT]: f1.rect.x+=1 elif keys[pygame.K_DOWN]: f1.rect.y+=1 elif keys[pygame.K_UP]: f1.rect.y-=1 pygame.display.update() # Oбновляем экран.
Сравнительно недавно я в учебных целях реализовывал один уровень игры Марио, на что у меня ушло около 6 часов чистого времени. Познакомившись с модулем TileTool я решил переписать свой проект с использованием предоставившихся возможностей. Времени ушло 2.5 часа, а код сократился практически вдвое!
Автор модуля заявляет, что в следующих версиях появится поддержка изометрии, что даст возможность упрощать работу над псевдо 3D играми, что было бы очень кстати.
А в качестве заключения, хочу лишний раз подчеркнуть, что наличие огромного числа модулей для Python'a позволяет в разы сократить время прототипирования и разработки практически любых программ. И что тоже немаловажно, дает возможность не углубляться в изучение всего подряд, позволяя сосредоточится на одном узком направлении. На примере TileTool, я не хочу заморачиваться с "рисованием миров" и тратить на это свое время, мне интереснее физика движения, игровая механика, реалистичные коллизии, и на отработке этих элементов я хотел бы сосредоточиться. Те же, кому наоборот интереснее генерировать миры, могут сосредоточиться на изучении этой темы, а для физики движения использовать сторонние модули. Как говорится о вкусах не спорят!
Благодарю за терпение в данном лонгриде. Обзор получился довольно общим, и позволяет в общих чертах познакомиться с модулем, упрощающем жизнь создателей плиточных игрушек. Если стиль изложения и данный модуль вам понравились, с радостью поделюсь со всеми парой подробных и пошаговых статей-мануалов, написанных лично, о том как я с помощью модуля TileTool реализовал игры Марио и Сапёр. А также, если интересно, могу выложить мануал по редактору Tiled.
