Привет, друзья!
Продолжаем разбираться с нашим МариоБоем. Начало тут. В этой подчасти второй части мы сделаем приготовление для создания редактора уровней, а именно: добавим турбо режим бега герою, смертельно опасные платформы, движущиеся монстры, телепортеры, принцессу и парсер уровней, дабы во второй подчасти не отвлекаться на всё это.
Upgrade героя
Добавим нашему герою возможность ускоряться. Для этого немного изменим код метода update.
Для начала, добавим констант
MOVE_EXTRA_SPEED = 2.5 # Ускорение JUMP_EXTRA_POWER = 1 # дополнительная сила прыжка ANIMATION_SUPER_SPEED_DELAY = 0.05 # скорость смены кадров при ускорении
Далее, добавим анимации движения влево — вправо в ускоренном режиме. Мы вставим те же картинки, но с другой скоростью смены кадров
# Анимация движения вправо boltAnim = [] boltAnimSuperSpeed = [] for anim in ANIMATION_RIGHT: boltAnim.append((anim, ANIMATION_DELAY)) boltAnimSuperSpeed.append((anim, ANIMATION_SUPER_SPEED_DELAY)) self.boltAnimRight = pyganim.PygAnimation(boltAnim) self.boltAnimRight.play() self.boltAnimRightSuperSpeed = pyganim.PygAnimation(boltAnimSuperSpeed) self.boltAnimRightSuperSpeed.play() # Анимация движения влево boltAnim = [] boltAnimSuperSpeed = [] for anim in ANIMATION_LEFT: boltAnim.append((anim, ANIMATION_DELAY)) boltAnimSuperSpeed.append((anim, ANIMATION_SUPER_SPEED_DELAY)) self.boltAnimLeft = pyganim.PygAnimation(boltAnim) self.boltAnimLeft.play() self.boltAnimLeftSuperSpeed = pyganim.PygAnimation(boltAnimSuperSpeed) self.boltAnimLeftSuperSpeed.play()
Добавили 2 набора анимаций при ускорении self.boltAnimRightSuperSpeed , self.boltAnimLeftSuperSpeed , отображать будем их чуть ниже
Теперь займемся самим методом update
Добавим входной параметр running
def update(self, left, right, up, running, platforms):
Изменим обработку движений персонажа, добавив поведение при ускорении.
if up: if self.onGround: # прыгаем, только когда можем оттолкнуться от земли self.yvel = -JUMP_POWER if running and (left or right): # если есть ускорение и мы движемся self.yvel -= JUMP_EXTRA_POWER # то прыгаем выше self.image.fill(Color(COLOR)) self.boltAnimJump.blit(self.image, (0, 0)) if left: self.xvel = -MOVE_SPEED # Лево = x- n self.image.fill(Color(COLOR)) if running: # если ускорение self.xvel-=MOVE_EXTRA_SPEED # то передвигаемся быстрее if not up: # и если не прыгаем self.boltAnimLeftSuperSpeed.blit(self.image, (0, 0)) # то отображаем быструю анимацию else: # если не бежим if not up: # и не прыгаем self.boltAnimLeft.blit(self.image, (0, 0)) # отображаем анимацию движения if up: # если же прыгаем self.boltAnimJumpLeft.blit(self.image, (0, 0)) # отображаем анимацию прыжка if right: self.xvel = MOVE_SPEED # Право = x + n self.image.fill(Color(COLOR)) if running: self.xvel+=MOVE_EXTRA_SPEED if not up: self.boltAnimRightSuperSpeed.blit(self.image, (0, 0)) else: if not up: self.boltAnimRight.blit(self.image, (0, 0)) if up: self.boltAnimJumpRight.blit(self.image, (0, 0))
И в основном файле добавим обработку события нажатия левого шифта.
running = False *** if e.type == KEYDOWN and e.key == K_LSHIFT: running = True *** if e.type == KEYUP and e.key == K_LSHIFT: running = False
Все коды клавиш тут
И не забываем добавить аргументы при вызове метода hero.update()
hero.update(left, right, up, running, platforms)
Смотрим результаты ( я изменил цвет фона на черный, брутальный цвет для брутального МариоБоя)
Без ускорения
Прыжок с ускорением
Смертельные шипы
Настоящему герою — настоящая опасность. Создадим новый вид блоков, при соприкосновении с которыми будет происходить моментальная смерть.
Создаем класс, наследующийся от Platform.
class BlockDie(Platform): def __init__(self, x, y): Platform.__init__(self, x, y) self.image = image.load("%s/blocks/dieBlock.png" % ICON_DIR)
Далее, добавим поведение героя при соприкосновении с ним. Для этого, добавим 2 метода в класс персонажа. Первый метод — поведение при смерти, второй — перемещение по указанным координатам(который пригодится нам еще раз чуть ниже)
def die(self): time.wait(500) self.teleporting(self.startX, self.startY) # перемещаемся в начальные координаты def teleporting(self, goX, go Y): self.rect.x = goX self.rect.y = goY
Т.е. когда мы умираем, игра замирает на некоторое время, затем мы перемещаемся в начало уровня и играем дальше.
Ну и описываем само поведение при пересечении с блоком смерти в методе collide()
*** if isinstance(p, blocks.BlockDie): # если пересакаемый блок - blocks.BlockDie self.die()# умираем ***
Теперь, в основном классе изменим уровень
level = [ "----------------------------------", "- -", "- -- -", "- * -", "- -", "- -- -", "-- -", "- -", "- ---- --- -", "- -", "-- -", "- * -", "- --- -", "- -", "- -", "- * --- * -", "- -", "- ------- ---- -", "- -", "- - -", "- -- -", "- *** -", "- -", "----------------------------------"]
И добавим создание блока смерти, если в уровне есть символ "*"
if col == "*": bd = BlockDie(x,y) entities.add(bd) platforms.append(bd)
Результат:
Порталы
Какой современный сантехник обходится без телепорта? Так давайте и нашего героя не будем делать белой вороной.
Создаём новый тип блока. Работаем в файле blocks.py
Cперва добавляем константы
ANIMATION_BLOCKTELEPORT = [ ('%s/blocks/portal2.png' % ICON_DIR), ('%s/blocks/portal1.png' % ICON_DIR)]
Затем создаем новый класс.
class BlockTeleport(Platform): def __init__(self, x, y, goX,goY): Platform.__init__(self, x, y) self.goX = goX # координаты назначения перемещения self.goY = goY # координаты назначения перемещения boltAnim = [] for anim in ANIMATION_BLOCKTELEPORT: boltAnim.append((anim, 0.3)) self.boltAnim = pyganim.PygAnimation(boltAnim) self.boltAnim.play() def update(self): self.image.fill(Color(PLATFORM_COLOR)) self.boltAnim.blit(self.image, (0, 0))
Тут нет ни чего нового. При создании задаются не только координаты расположения портала, но и координаты перемещения героя, при попадании в телепортер.
Далее, добавим нашему герою поведение при соприкосновении с порталом
*** elif isinstance(p, blocks.BlockTeleport): self.teleporting(p.goX, p.goY) ***
И добавим один портал на карту. Только теперь будем описывать координаты вручную. Когда сделаем редактор уровней — будет легче.
Добавим еще одну группу спрайтов, которая будет содержать анимированные блоки
animatedEntities = pygame.sprite.Group() # все анимированные объекты, за исключением героя
И создаем телепортер.
tp = BlockTeleport(128,512,800,64) entities.add(tp) platforms.append(tp) animatedEntities.add(tp)
В конце, добавим вызов метода update() у всех анимированных спрайтов
animatedEntities.update() # показываем анимацию
Как-то так
Монстры
Страшные, передвигающиеся, смертельно опасные огоньки.
Отличие монстров от смертельных блоков в том, что монстры могут двигаться.
Начнем, пожалуй.
Будем работать в новом файле, дабы не запутаться. Назовем его очень оригинально — monsters.py
Создадим новый класс Monster. В нём нет ничего такого, чего мы не применяли ранее.
Содержимое всего файла
#!/usr/bin/env python # -*- coding: utf-8 -*- from pygame import * import pyganim import os MONSTER_WIDTH = 32 MONSTER_HEIGHT = 32 MONSTER_COLOR = "#2110FF" ICON_DIR = os.path.dirname(__file__) # Полный путь к каталогу с файлами ANIMATION_MONSTERHORYSONTAL = [('%s/monsters/fire1.png' % ICON_DIR), ('%s/monsters/fire2.png' % ICON_DIR )] class Monster(sprite.Sprite): def __init__(self, x, y, left, up, maxLengthLeft,maxLengthUp): sprite.Sprite.__init__(self) self.image = Surface((MONSTER_WIDTH, MONSTER_HEIGHT)) self.image.fill(Color(MONSTER_COLOR)) self.rect = Rect(x, y, MONSTER_WIDTH, MONSTER_HEIGHT) self.image.set_colorkey(Color(MONSTER_COLOR)) self.startX = x # начальные координаты self.startY = y self.maxLengthLeft = maxLengthLeft # максимальное расстояние, которое может пройти в одну сторону self.maxLengthUp= maxLengthUp # максимальное расстояние, которое может пройти в одну сторону, вертикаль self.xvel = left # cкорость передвижения по горизонтали, 0 - стоит на месте self.yvel = up # скорость движения по вертикали, 0 - не двигается boltAnim = [] for anim in ANIMATION_MONSTERHORYSONTAL: boltAnim.append((anim, 0.3)) self.boltAnim = pyganim.PygAnimation(boltAnim) self.boltAnim.play() def update(self, platforms): # по принципу героя self.image.fill(Color(MONSTER_COLOR)) self.boltAnim.blit(self.image, (0, 0)) self.rect.y += self.yvel self.rect.x += self.xvel self.collide(platforms) if (abs(self.startX - self.rect.x) > self.maxLengthLeft): self.xvel =-self.xvel # если прошли максимальное растояние, то идеи в обратную сторону if (abs(self.startY - self.rect.y) > self.maxLengthUp): self.yvel = -self.yvel # если прошли максимальное растояние, то идеи в обратную сторону, вертикаль def collide(self, platforms): for p in platforms: if sprite.collide_rect(self, p) and self != p: # если с чем-то или кем-то столкнулись self.xvel = - self.xvel # то поворачиваем в обратную сторону self.yvel = - self.yvel
При создании монстра необходимо указать 6 аргументов: х, y — координаты, left — скорость перемещения по горизонтали, up — скорость перемещения по вертикали, maxLengthLeft — максимальное расстояние в одну сторону, которое может пройти монстр, maxLengthUp — аналогично предыдущему, но по вертикали.
Теперь добавим смерть герою от соприкосновения с огнем.
Заменим строки
if isinstance(p, blocks.BlockDie): # если пересакаемый блок - blocks.BlockDie self.die()# умираем
На
if isinstance(p, blocks.BlockDie) or isinstance(p, monsters.Monster): # если пересакаемый блок- blocks.BlockDie или Monster self.die()# умираем
И не забываем добавить импорт с файла monsters.py
И, конечно же, добавим создание монстра в основной файл.
Создадим еще одну группу спрайтов, в которую будем помещать наших монстриков.
monsters = pygame.sprite.Group() # Все передвигающиеся объекты
Вопрос: Для чего нам еще одна группа? Почему не хватило предыдущей? Ведь в группе спрайтов animatedEntities мы вызываем метод update()
Ответ: В предыдущей группе мы вызываем метод update()без аргументов, а в группе monsters этот метод будет вызывать с аргументом.
Создаем самого монстра.
mn = Monster(190,200,2,3,150,15) entities.add(mn) platforms.append(mn) monsters.add(mn)
И двигаем его
monsters.update(platforms) # передвигаем всех монстров
Смотрим на результат.
Принцесса
Дело чести любого сантехника — спасти принцессу.
Класс принцессы не содержит что-либо нам интересное, поэтому код его показывать не буду. Кто заинтересуется — искать в файле blocks.py
Нашему персонажу добавим свойство winner, по которому будем судить, что пора завершать уровень.
self.winner = False
И внесем изменения в метод collide()
elif isinstance(p, blocks.Princess): # если коснулись принцессы self.winner = True # победили!!!
И далее, напишем код создания принцессы
if col == "P": pr = Princess(x,y) entities.add(pr) platforms.append(pr) animatedEntities.add(pr)
Не забыв вставить символ «P» в уровень.
Смотрим
Уровень
Наконец-то мы добрались до парсинга уровня. Их мы будем держать в каталоге levels. Привожу пример уровня из файла 1.txt
[ ----------------------------------| - * -| - * *P -| ---- *--** --| - -- -| - -| - -| - -| -- ---- | - -| -- -| - ** -| - --- -| - -| - -| - --- -| - -| - -------- * ---- -| - -| - - -| - ** -- -| - * -| - ** -| --------------- *** -- -| - -| - -| ----------------------------------| ] player 55 44 portal 128 512 900 35 portal 170 512 700 64 monster 190 250 2 1 150 10 monster 190 400 2 3 150 150 monster 150 200 1 2 150 100 /
Что мы тут видим? Ни чего такого, чего бы не рассматривали в этом посте (включая первую часть). Сперва генерирум статические платформы, посредствам символов "[","-", "*","]","|"
Где "[" — показывает парсеру начало уровня
"]" — соответсвенно, конец уровня
"|" — конец строки
"-" — обычная платформа
"*" — шипованная платформа
Затем, в строчке «player 55 44» мы указываем начальные координаты нашего героя
«portal 128 512 900 35» — первые два числа — координаты портала, вторые — координаты перемещения
«monster 150 200 1 2 150 100» — первые два числа, аналогично, координаты монстра, затем, вторые два — скорость горизонтальная и вертикальная, и последние — максимальное расстояние в одну сторону по горизонтали и вертикали.
Как вы уже заметили, как порталов, так и монстров может быть столько, сколько вам захочется.
Символ "/" означает конец файла. Все данные, после него, считаны не будут.
Теперь, давайте, напишем сам парсер.
Работаем в основном файле.
Для начала, перенесем все массивы и группы из функции main() в тело основной программы
*** level = [] entities = pygame.sprite.Group() # Все объекты animatedEntities = pygame.sprite.Group() # все анимированные объекты, за исключением героя monsters = pygame.sprite.Group() # Все передвигающиеся объекты platforms = [] # то, во что мы будем врезаться или опираться if __name__ == "__main__": main()
Затем, убираем уровень, переменная должна быть пустой. Так же, удаляем создание монстров и порталов.
И добавляем новую функцию
def loadLevel(): global playerX, playerY # объявляем глобальные переменные, это координаты героя levelFile = open('%s/levels/1.txt' % FILE_DIR) line = " " commands = [] while line[0] != "/": # пока не нашли символ завершения файла line = levelFile.readline() #считываем построчно if line[0] == "[": # если нашли символ начала уровня while line[0] != "]": # то, пока не нашли символ конца уровня line = levelFile.readline() # считываем построчно уровень if line[0] != "]": # и если нет символа конца уровня endLine = line.find("|") # то ищем символ конца строки level.append(line[0: endLine]) # и добавляем в уровень строку от начала до символа "|" if line[0] != "": # если строка не пустая commands = line.split() # разбиваем ее на отдельные команды if len(commands) > 1: # если количество команд > 1, то ищем эти команды if commands[0] == "player": # если первая команда - player playerX= int(commands[1]) # то записываем координаты героя playerY = int(commands[2]) if commands[0] == "portal": # если первая команда portal, то создаем портал tp = BlockTeleport(int(commands[1]),int(commands[2]),int(commands[3]),int(commands[4])) entities.add(tp) platforms.append(tp) animatedEntities.add(tp) if commands[0] == "monster": # если первая команда monster, то создаем монстра mn = Monster(int(commands[1]),int(commands[2]),int(commands[3]),int(commands[4]),int(commands[5]),int(commands[6])) entities.add(mn) platforms.append(mn) monsters.add(mn)
Не забываем вызвать эту функцию и указать переменные startX и startY как стартовые координаты нашему герою.
def main(): loadLevel() *** hero = Player(playerX,playerY) # создаем героя по (x,y) координатам ***
Скачать результат
Сейчас не очень интересно редактировать файл уровня руками, поэтому, в следующей подчасти мы напишем сам редактор уровней.
P.S. Уровень, созданный выше, вполне проходимый, дерзайте:-)
ссылка на оригинал статьи http://habrahabr.ru/post/196432/
Добавить комментарий