Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Пишем платформер на Python. Часть 2. Подчасть 1, подготовка к созданию редактора ярусов

Anna | 16.06.2014 | нет комментариев
Здравствуй, друзья!

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

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 способа в класс персонажа. 1-й способ — поведение при гибели, 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 доводов: х— координаты, 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. ?рус, сделанный выше, абсолютно проходимый, дерзайте.

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB