Пишем платформер на python, используя pygame. Часть 2 подчасть 2. Редактор уровней

от автора


Привет, друзья! Сегодня мы наконец-то доделаем нашего мариобоя. Вот только мы не будем изобретать свой велосипед в виде редактора уровней, а воспользуемся готовым мощным инструментом. За знакомство с которым я благодарен господам(товарищам) sourcerer и Tarvitz

Почему так?

На это есть несколько причин

  • Удобный редактор уровней не пишется за 5 минут, лучше потратим это время на допиливание самой игры
  • Более легкий способ добавления в игру разных на вид типов блоков
  • Tiled map editor является универсальным инструментом для 2d игр, разобравшись с ним единожды, мы приобретаем навык генерации уровней для разных игр, написанных на разных языках и технологиях

Создаём неприятности и преграды нашему герою

Про работу с Tiled map editor можно почитать, например, тут.
Я же опишу основные моменты создания уровня именно для нашей игры.
Наша карта состоит из минимум 5-ти слоёв:

  1. BackGround — фон
  2. Platforms — блоки, по которым можно бегать
  3. DieBlocks -блоки, соприкосновение с которыми вызывает у героя моментальную смерть
  4. Monsters — слой объектов, тут наши монстрики, а так же, принцесса и сам герой
  5. Teleports -слой объектов, для чего — понятно по названию

Фон

Тут можете рисовать что угодно и как угодно, тайлы с этого слоя ни как не влияют на героя или игровой процесс, разве что на эстетический вид игры 🙂

Блоки, по которым можно бегать

На этом слое располагаются тайлы, которые в игре создают объекты класса Platform

Смертельно опасные блоки

Все тайлы, независимо от внешнего вида, будь то шипы или кирпичная стена, создают в игре объекты класса BlockDie

Монстры

Это слой объектов, а значит, он не отображает в игре тайлы и каждый объект, добавленный на него, должен обладать какими — нибудь свойствами.
Объекты класса Monster, чей конструктор имеет следующий вид

class Monster(sprite.Sprite):     def __init__(self, x, y, left, up, maxLengthLeft,maxLengthUp): 

Обязательно должны иметь такие свойства, как: left, maxLeft, up, maxUp — заполняемые вручную и x, y — передающиеся по расположению объекта.

Объект персонажа должен иметь имя Player

Объект принцессы должен иметь имя Princess

Вид слоя:

Телепорты

Объекты этого слоя должны иметь свойства конечного назначения перемещения героя: goX и goY

Слой:

Как узнать конечные координаты?

Легко! Навести курсор мыши на то место, куда хотите, чтобы герой телепортировался и посмотреть слева снизу координаты места

Карту в игру

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

import tmxreader # Может загружать tmx файлы import helperspygame # Преобразует tmx карты в формат  спрайтов pygame 

Далее, очищаем процедуру loadLevel(), мы её перепишем.

loadlevel

def loadLevel(name):     global playerX, playerY # объявляем глобальные переменные, это координаты героя     global total_level_height, total_level_width     global sprite_layers # все слои карты      world_map = tmxreader.TileMapParser().parse_decode('%s/%s.tmx' % (FILE_DIR, name)) # загружаем карту     resources = helperspygame.ResourceLoaderPygame() # инициируем преобразователь карты      resources.load(world_map) # и преобразуем карту в понятный pygame формат          sprite_layers = helperspygame.get_layers_from_map(resources) # получаем все слои карты          # берем слои по порядку 0 - слой фона, 1- слой блоков, 2 - слой смертельных блоков     # 3 - слой объектов монстров, 4 - слой объектов телепортов     platforms_layer = sprite_layers[1]      dieBlocks_layer = sprite_layers[2]      for row in range(0, platforms_layer.num_tiles_x): # перебираем все координаты тайлов         for col in range(0, platforms_layer.num_tiles_y):             if platforms_layer.content2D[col][row] is not None:                 pf = Platform(row * PLATFORM_WIDTH, col * PLATFORM_WIDTH)# как и прежде создаем объкты класса Platform                 platforms.append(pf)             if dieBlocks_layer.content2D[col][row] is not None:                 bd = BlockDie(row * PLATFORM_WIDTH, col * PLATFORM_WIDTH)                 platforms.append(bd)      teleports_layer = sprite_layers[4]     for teleport in teleports_layer.objects:         try: # если произойдет ошибка на слое телепортов             goX = int(teleport.properties["goX"]) * PLATFORM_WIDTH             goY = int (teleport.properties["goY"]) * PLATFORM_HEIGHT             x = teleport.x             y = teleport.y - PLATFORM_HEIGHT             tp = BlockTeleport(x, y, goX, goY)             entities.add(tp)             platforms.append(tp)             animatedEntities.add(tp)         except: # то игра не вылетает, а просто выводит сообщение о неудаче             print(u"Ошибка на слое телепортов")      monsters_layer = sprite_layers[3]     for monster in monsters_layer.objects:         try:             x = monster.x             y = monster.y             if monster.name == "Player":                 playerX = x                 playerY = y - PLATFORM_HEIGHT             elif monster.name == "Princess":                 pr = Princess(x, y - PLATFORM_HEIGHT)                 platforms.append(pr)                 entities.add(pr)                 animatedEntities.add(pr)             else:                 up = int(monster.properties["up"])                 maxUp = int(monster.properties["maxUp"])                 left = int(monster.properties["left"])                 maxLeft = int(monster.properties["maxLeft"])                 mn = Monster(x, y - PLATFORM_HEIGHT, left, up, maxLeft, maxUp)                 entities.add(mn)                 platforms.append(mn)                 monsters.add(mn)         except:             print(u"Ошибка на слое монстров")      total_level_width = platforms_layer.num_tiles_x * PLATFORM_WIDTH # Высчитываем фактическую ширину уровня     total_level_height = platforms_layer.num_tiles_y * PLATFORM_HEIGHT   # высоту  

Что мы тут видим?

Начнем с того, что теперь процедура принимает входной параметр name, который используется для загрузки карты уровня. Это сделали для того, чтобы сделать переход между уровнями.
Далее идёт загрузка и преобразование карты, и по тому же принципу, что мы парсили массив с картой, парсим слои с тайлами. Обратите внимание, что теперь созданные объекты классов Platform и BlockDie не помещаются в группу entities, а значит, мы их не будет отображать т.е. они будут существовать, но не отображаться. Вместо них мы будет отображать тайлы со слоёв карты.

Продолжим

Теперь займемся процедурой main
Добавим визуализатор(рендерер) слоёв карты

renderer = helperspygame.RendererPygame() # визуализатор 

Для чего он — увидим чуть ниже

Изменим вызов процедуры loadLevel

for lvl in range(1,4):         loadLevel("map_%s" % lvl) 

И далее, весь код будет в этом цикле

В блоке вывода изображений на экран добавим работу визуализатора

for sprite_layer in sprite_layers: # перебираем все слои       if not sprite_layer.is_object_group: # и если это не слой объектов            renderer.render_layer(screen, sprite_layer) # отображаем его ***  center_offset = camera.reverse(CENTER_OF_SCREEN) # получаем координаты внутри длинного уровня  renderer.set_camera_position_and_size(center_offset[0], center_offset[1], \                                                   WIN_WIDTH, WIN_HEIGHT, "center") 

Обратите внимание, что renderer выводит свои изображения по центру экрана, внутри передвигающегося фокуса камеры, для этого нам нужно было добавить процедуру в класс Camera

Camera

class Camera(object):     def __init__(self, camera_func, width, height):         self.camera_func = camera_func         self.state = Rect(0, 0, width, height)      def apply(self, target):         return target.rect.move(self.state.topleft)      def update(self, target):         self.state = self.camera_func(self.state, target.rect)      def reverse(self, pos):# получение внутренних координат из глобальных         return pos[0] - self.state.left, pos[1] - self.state.top 

Уберем то, что перенесли в процедуру loadlevel, добавим немного нового и получим следующий вид:

main

def main():     pygame.init() # Инициация PyGame, обязательная строчка     screen = pygame.display.set_mode(DISPLAY) # Создаем окошко     pygame.display.set_caption("Super Mario Boy") # Пишем в шапку     bg = Surface((WIN_WIDTH, WIN_HEIGHT)) # Создание видимой поверхности     # будем использовать как фон      renderer = helperspygame.RendererPygame() # визуализатор     for lvl in range(1,4):         loadLevel("levels/map_%s" % lvl)         bg.fill(Color(BACKGROUND_COLOR))     # Заливаем поверхность сплошным цветом          left = right = False # по умолчанию - стоим         up = False         running = False         try:             hero = Player(playerX, playerY) # создаем героя по (x,y) координатам             entities.add(hero)         except:             print (u"Не удалось на карте найти героя, взяты координаты по-умолчанию")             hero = Player(65, 65)         entities.add(hero)          timer = pygame.time.Clock()          camera = Camera(camera_configure, total_level_width, total_level_height)          while not hero.winner: # Основной цикл программы             timer.tick(60)             for e in pygame.event.get(): # Обрабатываем события                 if e.type == QUIT:                     raise SystemExit, "QUIT"                 if e.type == KEYDOWN and e.key == K_UP:                     up = True                 if e.type == KEYDOWN and e.key == K_LEFT:                     left = True                 if e.type == KEYDOWN and e.key == K_RIGHT:                     right = True                 if e.type == KEYDOWN and e.key == K_LSHIFT:                     running = True                  if e.type == KEYUP and e.key == K_UP:                     up = False                 if e.type == KEYUP and e.key == K_RIGHT:                     right = False                 if e.type == KEYUP and e.key == K_LEFT:                     left = False                 if e.type == KEYUP and e.key == K_LSHIFT:                     running = False             for sprite_layer in sprite_layers: # перебираем все слои                 if not sprite_layer.is_object_group: # и если это не слой объектов                    renderer.render_layer(screen, sprite_layer) # отображаем его              for e in entities:                 screen.blit(e.image, camera.apply(e))             animatedEntities.update() # показываеaм анимацию             monsters.update(platforms) # передвигаем всех монстров             camera.update(hero) # центризируем камеру относительно персонаж             center_offset = camera.reverse(CENTER_OF_SCREEN)             renderer.set_camera_position_and_size(center_offset[0], center_offset[1], \                                                   WIN_WIDTH, WIN_HEIGHT, "center")             hero.update(left, right, up, running, platforms) # передвижение             pygame.display.update()     # обновление и вывод всех изменений на экран             screen.blit(bg, (0, 0))      # Каждую итерацию необходимо всё перерисовывать         for sprite_layer in sprite_layers:             if not sprite_layer.is_object_group:                 renderer.render_layer(screen, sprite_layer)         # когда заканчиваем уровень         for e in entities:             screen.blit(e.image, camera.apply(e)) # еще раз все перерисовываем         font=pygame.font.Font(None,38)          text=font.render(("Thank you MarioBoy! but our princess is in another level!"), 1,(255,255,255))# выводим надпись         screen.blit(text, (10,100))         pygame.display.update()         time.wait(10000) # ждем 10 секунд и после - переходим на следующий уровень 

Что тут интересного?

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

Вот и всё. Вот так, легко и быстро мы переделали игру для загрузки уровней из tmx файлов.

ссылка на оригинал статьи http://habrahabr.ru/post/204112/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *