html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

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



Привет, друзья!

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

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. Уровень, созданный выше, вполне проходимый, дерзайте.
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

13

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции