Последнее время из каждого чайника напористо лезет реклама обучения языку Python. И это не удивительно, поскольку Python уверенно занял лидирующее положение в качестве первого языка для изучения программирования. А его простота, обилие вспомогательных модулей и скорость написания кода сделали его лидером для обучения детей программированию, полностью вытеснив, популярные лет 20 назад QBasic и Pascal.

Обучаем играючи...

Последние тренды в обучении детей сводятся к игровым механикам. Чтобы заинтересовать ребенка программированием - преврати обучение в игру или предложи научиться игры создавать. Из-за этой логики одним из самых популярных модулей для Python, используемых в обучении стал Pygame, позволяющий создавать простые и не очень игры, без глубокого погружения в тонкости реализации, что очень удобно для детей в процессе начального освоения программирования.

О чем статья?

В этой статье я хотел бы поделиться с хабравцеми и маленькими хабрятами одним очень удобным дополнением для Pygame, найденным на просторах интернета буквально пару дней назад, но покорившем мое сердце. А все от того, что модуль, о котором пойдет речь, упрощает процесс создания 2D игрушек, делая его простым, наглядным, быстрым и увлекательным.

Маленький ликбез о плиточных играх

Огромное количество плоских 2D игрушек относятся к так называемым "плиточным" играм, в которых основой построения игрового мира служат минимальные элементы - плитки, чаще всего квадратные с нанесенной на них текстурой.

К плиточным играм относятся и Сапёр и легендарный Марио и практически все стратегии на развитие территорий и пошаговые стратегии.

В основе построения мира лежит поле, составленное из отдельных квадратиков, каждый из которых может быть закрашен своим цветом или заполнен какой то текстурой.

Задача программиста нарисовать этот квадратный мир с помощью кода и научить персонажа в этом мире жить. Именно за простое создание игрового мира и отвечает модуль под названием TileTool.

TileTool - может всё или почти всё?

Устанавливаем модуль в командной строке

pip install tiletool

И через минуту мы готовы приступать.

У модуля на данный момент имеется три режима работы, то есть три варианта сборки мира.

  1. Генератор прямоугольников

  2. Создание мира из *.txt или *.csv файлов

  3. Создание мира из файлов *.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
Редактор "плиточных" уровней Tiled

Проект 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.

Комментарии (7)


  1. pfr46
    11.05.2022 00:43
    +4

    Ох, не нравится мне, что последнее время так много python'а. Не вышло бы как с С++ - изучили детки один язык и всё. Потом пихали везде где можно, а особенно где ему совсем не место. Плакали, кололись, но впихивали.


    1. HemulGM
      11.05.2022 10:02
      +2

      Самое страшное, действительно, в том что не показывают альтернатив, а если показывают, то крайне кратко. А ведь на питоне не всё делается проще и тем более эффективнее, чем на многих других языках.

      То же создание приложений с GUI. Если покажут на Питоне и детки будут думать, что вот оно везде так, да ещё и скорее всего сложнее, чем на питоне, ведь "питон проще". А если показать им C# или Delphi, где такие приложения делаются буквально за секунды и без какого-либо лишнего кода - я уверен, будет удивление.

      А ведь всё, что мы имеем в питоне - это уже кем-то когда-то написанное и упрощенное до библиотек. Что, как и почему работает - ни кому не понятно и не видно. Да и, зачастую просто не интересно, потому что "сложно".


      1. LostAngel
        13.05.2022 16:53

        А самое интересное, что эти библиотеки написаны как раз на C, откомпилированы и подхватываются питоном.


        1. HemulGM
          13.05.2022 17:08

          Да, об это тоже часто "забывают" рассказать новичкам


  1. 1eternal
    11.05.2022 09:07
    +2

    побурчу немного.

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

    а детям ваш текст вообще не интересен.

    сплошное графоманство у программистов


  1. AcckiyGerman
    11.05.2022 10:06
    +1

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


  1. kanyenorth
    11.05.2022 12:40

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