Привет, друзья!
Продолжаем разбираться с нашим МариоБоем. Начало тут. В этой подчасти второй части мы сделаем приготовление для создания редактора уровней, а именно: добавим турбо режим бега герою, смертельно опасные платформы, движущиеся монстры, телепортеры, принцессу и парсер уровней, дабы во второй подчасти не отвлекаться на всё это.
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. Уровень, созданный выше, вполне проходимый, дерзайте:-)
Автор: Velese