Привет, друзья! Сегодня мы наконец-то доделаем нашего мариобоя. Вот только мы не будем изобретать свой велосипед в виде редактора уровней, а воспользуемся готовым мощным инструментом. За знакомство с которым я благодарен господам(товарищам) sourcerer и Tarvitz
Почему так?
На это есть несколько причин
- Удобный редактор уровней не пишется за 5 минут, лучше потратим это время на допиливание самой игры
- Более легкий способ добавления в игру разных на вид типов блоков
- Tiled map editor является универсальным инструментом для 2d игр, разобравшись с ним единожды, мы приобретаем навык генерации уровней для разных игр, написанных на разных языках и технологиях
Создаём неприятности и преграды нашему герою
Про работу с Tiled map editor можно почитать, например, тут.
Я же опишу основные моменты создания уровня именно для нашей игры.
Наша карта состоит из минимум 5-ти слоёв:
- BackGround — фон
- Platforms — блоки, по которым можно бегать
- DieBlocks -блоки, соприкосновение с которыми вызывает у героя моментальную смерть
- Monsters — слой объектов, тут наши монстрики, а так же, принцесса и сам герой
- 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(), мы её перепишем.
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
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, добавим немного нового и получим следующий вид:
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/
Добавить комментарий