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

Пишем платформер на Python, применяя pygame

Anna | 16.06.2014 | нет комментариев
Сразу оговорюсь, что тут написано для самых маленькихначинающих.

Давным-давно хотел испробовать себя в качестве игродела, и незадолго вывалился случай исследовать Python и выполнить давнишнюю мечту.

Что такое платформер?

Платформер(platformer)— стиль компьютерных игр, в которых стержневой чертой игрового процесса является прыгание по платформам, лазанье по лестницам, собирание предметов, обыкновенно нужных для заключения яруса.
Вики

Одними из моих любимых игр данного стиля являются «Super Mario Brothers» и «Super Meat Boy». Давайте испробуем сделать что-то среднее между ними.

Самое — самое предисловие.

Вероятно, не только игры, да и все приложения, использующие pygame начинаются приблизительно так:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Импортируем библиотеку pygame
import pygame
from pygame import *

#Объявляем переменные
WIN_WIDTH = 800 #Ширина создаваемого окна
WIN_HEIGHT = 640 # Высота
DISPLAY = (WIN_WIDTH, WIN_HEIGHT) # Группируем ширину и высоту в одну переменную
BACKGROUND_COLOR = "#004400"

def main():
    pygame.init() # Инициация PyGame, непременная строчка 
    screen = pygame.display.set_mode(DISPLAY) # Создаем окошко
    pygame.display.set_caption("Super Mario Boy") # Пишем в шапку
    bg = Surface((WIN_WIDTH,WIN_HEIGHT)) # Создание видимой поверхности
                                         # будем применять как фон
    bg.fill(Color(BACKGROUND_COLOR))     # Заливаем поверхность сплошным цветом

    while 1: # Стержневой цикл программы
        for e in pygame.event.get(): # Обрабатываем события
            if e.type == QUIT:
                raise SystemExit, "QUIT"
        screen.blit(bg, (0,0))      # Всякую итерацию нужно всё перерисовывать 
        pygame.display.update()     # обновление и итог всех изменений на экран

if __name__ == "__main__":
    main()

Игра будет «вертеться» в цикле ( while 1), всякую итерацию нужно перерисовывать всё (фон, платформы, монстров, цифровые сообщения и т.д). Значимо подметить, что рисование идет ступенчато, т.е. если сперва нарисовать героя, а потом залить фон, то героя видно не будет, учтите это на грядущее.

Запустив данный код, мы увидим окно, залитое зелененьким цветом.


(Картинка кликабельна)

Ну что же, предисловие положено, идём дальше.

?рус.

А как без него? Под словом «ярус» будем подразумевать ограниченную область виртуального двумерного пространства, заполненную каждой — всячиной, и по которой будет передвигаться наш персонаж.

Для построения яруса сделаем двумерный массив m на n. Всякая ячейка (m,n) будет представлять из себя прямоугольник. Прямоугольник может в себе что-то содержать, а может и быть пустым. Мы в прямоугольниках будем рисовать платформы.

Добавим еще константы

PLATFORM_WIDTH = 32
PLATFORM_HEIGHT = 32
PLATFORM_COLOR = "#FF6262"

После этого добавим объявление яруса в функцию main

level = [
       "-------------------------",
       "-                       -",
       "-                       -",
       "-                       -",
       "-            --         -",
       "-                       -",
       "--                      -",
       "-                       -",
       "-                   --- -",
       "-                       -",
       "-                       -",
       "-      ---              -",
       "-                       -",
       "-   -----------        -",
       "-                       -",
       "-                -      -",
       "-                   --  -",
       "-                       -",
       "-                       -",
       "-------------------------"]

И в стержневой цикл добавим следующее:


  x=y=0 # координаты
        for row in level: # каждая строка
            for col in row: # всякий символ
                if col == "-":
                    #создаем блок, заливаем его цветом и рисеум его
                    pf = Surface((PLATFORM_WIDTH,PLATFORM_HEIGHT))
                    pf.fill(Color(PLATFORM_COLOR)) 
                    screen.blit(pf,(x,y))

                x  = PLATFORM_WIDTH #блоки платформы ставятся на ширине блоков
            y  = PLATFORM_HEIGHT    #то же самое и с высотой
            x = 0                   #на всякой новой строчке начинаем с нуля

Т.е. Мы перебираем двумерный массив level, и, если находим символ «-», то по координатам (x * PLATFORM_WIDTH, y * PLATFORM_HEIGHT), где x,y — индекс в массиве level

Запустив, мы увидим следующее:

Персонаж

Легко кубики на фоне — это дюже уныло. Нам необходим наш персонаж, тот, что будет бегать и прыгать по платформам.

Создаём класс нашего героя.

Для комфорта, будем удерживать нашего персонажа в отдельном файле player.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pygame import *

MOVE_SPEED = 7
WIDTH = 22
HEIGHT = 32
COLOR =  "#888888"

class Player(sprite.Sprite):
    def __init__(self, x, y):
        sprite.Sprite.__init__(self)
        self.xvel = 0   #скорость перемещения. 0 - стоять на месте
        self.startX = x # Исходная позиция Х, сгодится когда будем переигрывать ярус
        self.startY = y
        self.image = Surface((WIDTH,HEIGHT))
        self.image.fill(Color(COLOR))
        self.rect = Rect(x, y, WIDTH, HEIGHT) # прямоугольный объект

    def update(self,  left, right):
        if left:
            self.xvel = -MOVE_SPEED # Лево = x- n

        if right:
            self.xvel = MOVE_SPEED # Право = x   n

        if not(left or right): # стоим, когда нет указаний идти
            self.xvel = 0

        self.rect.x  = self.xvel # переносим свои расположение на xvel 

    def draw(self, screen): # Выводим себя на экран
        screen.blit(self.image, (self.rect.x,self.rect.y))

Что здесь увлекательного?
Начнём с того, что мы создаём новейший класс, наследуясь от класса pygame.sprite.Sprite, тем самым наследую все колляции спрайта.
Cпрайт — это движущееся растровое изображение. Имеет ряд пригодных способов и свойств.

self.rect = Rect(x, y, WIDTH, HEIGHT), в этой строчке мы создаем фактические границы нашего персонажа, прямоугольник, по которому мы будем не только перемещать героя, но и проверять его на соударения. Но об этом чуть ниже.

Способ update(self, left, right)) применяется для изложения поведения объекта. Переопределяет родительский update(*args) ок слишком стремительно перемещается, добавим лимитация в числе кадров в секунду. Для этого позже определения яруса добавим таймер

timer = pygame.time.Clock()

И в предисловие основного цикла добавим следующее:

timer.tick(60)

Завис в воздухе

Да, наш герой в безысходном расположении, он завис в воздухе.
Добавим гравитации и вероятности прыгать.

И так, трудимся в файле player.py

Добавим еще констант

JUMP_POWER = 10
GRAVITY = 0.35 # Сила, которая будет тащить нас вниз

В способ _init_ добавляем строки:

 self.yvel = 0 # скорость вертикального перемещения
 self.onGround = False # На земле ли я?

Добавляем входной довод в способ update
def update(self, left, right, up):
И в предисловие способа добавляем:

if up:
   if self.onGround: # прыгаем, только когда можем оттолкнуться от земли
       self.yvel = -JUMP_POWER

И перед строчкой self.rect.x = self.xvel 
Добавляем

if not self.onGround:
    self.yvel  =  GRAVITY

self.onGround = False; # Мы не знаем, когда мы на земле((   
self.rect.y  = self.yvel

И добавим в основную часть программы:
Позже строчки left = right = False 
Добавим переменную up

up = false

В проверку событий добавим

if e.type == KEYDOWN and e.key == K_UP:
       up = True

if e.type == KEYUP and e.key == K_UP:
      up = False

И изменим вызов способа update, добавив новейший довод up:
hero.update(left, right)
на

hero.update(left, right, up)  

Тут мы сделали силу гравитации, которая будет тащить нас вниз, непрерывно наращивая скорость, если мы не стоим на земле, и прыгать в полете мы не умеем. А мы пока не можем твердо встать на что-то, следственно на дальнейшей анимации наш герой падает вдалеке за границы видимости.
image

Встань обеими ногами на землю свою.

Как узнать, что мы на земле либо иной твердой поверхности? Результат явствен — применять проверку на пересечение, но для этого изменим создание платформ.

Сотворим еще один файл blocks.py, и перенесем в него изложение платформы.

PLATFORM_WIDTH = 32
PLATFORM_HEIGHT = 32
PLATFORM_COLOR = "#FF6262"

Дальше сделаем класс, наследуясь от pygame.sprite.Sprite

class Platform(sprite.Sprite):
    def __init__(self, x, y):
        sprite.Sprite.__init__(self)
        self.image = Surface((PLATFORM_WIDTH, PLATFORM_HEIGHT))
        self.image.fill(Color(PLATFORM_COLOR))
        self.rect = Rect(x, y, PLATFORM_WIDTH, PLATFORM_HEIGHT)

Здесь нет ни чего нам теснее не приятеля, идём дальше.

В стержневой файле произведем метаморфозы, перед изложением массива level добавим

entities = pygame.sprite.Group() # Все объекты
platforms = [] # то, во что мы будем врезаться либо опираться
entities.add(hero)

Группа спрайтов entities будем применять для отображения всех элементов этой группы.
Массив platforms будем применять для проверки на пересечение с платформой.

Дальше, блок

if col == "-":
   #создаем блок, заливаем его цветом и рисеум его
   pf = Surface((PLATFORM_WIDTH,PLATFORM_HEIGHT))
   pf.fill(Color(PLATFORM_COLOR)) 
   screen.blit(pf,(x,y))

Заменим на

if col == "-":
   pf = Platform(x,y)
   entities.add(pf)
   platforms.append(pf)

Т.е. создаём экземплр класса Platform, добавляем его в группу спрайтов entities и массив platforms. Вentities, Дабы для всякого блока не писать логику отображения. В platforms добавили, Дабы потом проверить массив блоков на пересечение с игроком.

Дальше, каждый код генерации яруса переносим из цикла.

И так же строчку
hero.draw(screen) # отображение
Заменим на

entities.draw(screen) # отображение каждого

Запустив, мы увидим, что ни чего не изменилось. Правильно. чай мы не проверяем нашего героя на соударения. Начнём это исправлять.

Трудимся в файле player.py

Удаляем способ draw, он нам огромнее не необходим. И добавляем новейший способ collide

def collide(self, xvel, yvel, platforms):
        for p in platforms:
            if sprite.collide_rect(self, p): # если есть пересечение платформы с игроком

                if xvel > 0:                      # если движется вправо
                    self.rect.right = p.rect.left # то не движется вправо

                if xvel < 0:                      # если движется налево
                    self.rect.left = p.rect.right # то не движется налево

                if yvel > 0:                      # если падает вниз
                    self.rect.bottom = p.rect.top # то не падает вниз
                    self.onGround = True          # и становится на что-то твердое
                    self.yvel = 0                 # и энергия падения исчезает

                if yvel < 0:                      # если движется вверх
                    self.rect.top = p.rect.bottom # то не движется вверх
                    self.yvel = 0                 # и энергия прыжка исчезает


В этом способе происходит проверка на пересечение координат героя и платформ, если таковое имеется, то выше описанной логике происходит действие.

Ну, и для того, что бы это всё происходило, нужно вызывать данный способ.
Изменим число доводов для способа update, сейчас он выглядит так:

update(self, left, right, up, platforms)

И не позабудьте изменить его вызов в основном файле.

И строчки

self.rect.y  = self.yvel
self.rect.x  = self.xvel # переносим свои расположение на xvel

Заменям на:

self.rect.y  = self.yvel
self.collide(0, self.yvel, platforms)

self.rect.x  = self.xvel # переносим свои расположение на xvel
self.collide(self.xvel, 0, platforms)

Т.е. передвинули героя вертикально, проверили на пересечение по вертикали, передвинули горизонтально, вновь проверили на пересечение по горизонтали.

Вот, что получится, когда запустим.

image

Фу[у]! Движущийся прямоугольник — не прекрасно!

Давайте немножко приукрасим нашего МариоБоя.

Начнем с платформ. Для этого в файле blocks.py сделаем небольшие метаморфозы.

Заменим заливку цветом на картинку, для этого строчку
self.image.fill(Color(PLATFORM_COLOR))
Заменим на

self.image = image.load("blocks/platform.png")

Мы загружаем картинку взамен сплошного цвета. Разумеется, файл «platform.png» должен находиться в папке «blocks», которая должна располагаться в каталоге с начальными кодами.

Вот, что получилось

Сейчас переходим к нашему герою. Для движущегося объекта необходима не статическая картинка, а анимация. чтобы облегчить себе эту задачу, воспользуемся восхитительной библиотекой pyganim . Приступим.

Сперва добавим в блок констант.

ANIMATION_DELAY = 0.1 # скорость смены кадров
ANIMATION_RIGHT = [('mario/r1.png'),
            ('mario/r2.png'),
            ('mario/r3.png'),
            ('mario/r4.png'),
            ('mario/r5.png')]
ANIMATION_LEFT = [('mario/l1.png'),
            ('mario/l2.png'),
            ('mario/l3.png'),
            ('mario/l4.png'),
            ('mario/l5.png')]
ANIMATION_JUMP_LEFT = [('mario/jl.png', 0.1)]
ANIMATION_JUMP_RIGHT = [('mario/jr.png', 0.1)]
ANIMATION_JUMP = [('mario/j.png', 0.1)]
ANIMATION_STAY = [('mario/0.png', 0.1)]

Здесь, думаю, ясно, анимация различных действий героя.

Сейчас добавим следующее в способ __init__

self.image.set_colorkey(Color(COLOR)) # делаем фон прозрачным
#        Анимация движения вправо
boltAnim = []
for anim in ANIMATION_RIGHT:
   boltAnim.append((anim, ANIMATION_DELAY))
self.boltAnimRight = pyganim.PygAnimation(boltAnim)
self.boltAnimRight.play()
#        Анимация движения налево        
boltAnim = []
for anim in ANIMATION_LEFT:
   boltAnim.append((anim, ANIMATION_DELAY))
self.boltAnimLeft = pyganim.PygAnimation(boltAnim)
self.boltAnimLeft.play()

self.boltAnimStay = pyganim.PygAnimation(ANIMATION_STAY)
self.boltAnimStay.play()
self.boltAnimStay.blit(self.image, (0, 0)) # По-умолчанию, стоим

self.boltAnimJumpLeft= pyganim.PygAnimation(ANIMATION_JUMP_LEFT)
self.boltAnimJumpLeft.play()

self.boltAnimJumpRight= pyganim.PygAnimation(ANIMATION_JUMP_RIGHT)
self.boltAnimJumpRight.play()

self.boltAnimJump= pyganim.PygAnimation(ANIMATION_JUMP)
self.boltAnimJump.play()

Тут для всякого действия мы создаем комплект анимаций, и включаем их(т.е. Включаем смену кадров).

for anim in ANIMATION_LEFT:
            boltAnim.append((anim, ANIMATION_DELAY

))
Всякий кадр имеет картинку и время показа.

Осталось в необходимый момент показать необходимую анимацию.

Добавим смену анимаций в способ update.

if up:
    if self.onGround: # прыгаем, только когда можем оттолкнуться от земли
       self.yvel = -JUMP_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 up: # для прыжка налево есть отдельная анимация
      self.boltAnimJumpLeft.blit(self.image, (0, 0))
   else:
      self.boltAnimLeft.blit(self.image, (0, 0))

if right:
   self.xvel = MOVE_SPEED # Право = x   n
   self.image.fill(Color(COLOR))
      if up:
         self.boltAnimJumpRight.blit(self.image, (0, 0))
      else:
         self.boltAnimRight.blit(self.image, (0, 0))

if not(left or right): # стоим, когда нет указаний идти
   self.xvel = 0
   if not up:
      self.image.fill(Color(COLOR))
      self.boltAnimStay.blit(self.image, (0, 0))
      


Вуаля!
image

Огромнее, необходимо огромнее места


Лимитация в размере окна мы одолеем созданием динамической камеры.

Для этого сотворим класс 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 camera_configure(camera, target_rect):
    l, t, _, _ = target_rect
    _, _, w, h = camera
    l, t = -l WIN_WIDTH / 2, -t WIN_HEIGHT / 2

    l = min(0, l)                           # Не движемся дальше левой границы
    l = max(-(camera.width-WIN_WIDTH), l)   # Не движемся дальше правой границы
    t = max(-(camera.height-WIN_HEIGHT), t) # Не движемся дальше нижней границы
    t = min(0, t)                           # Не движемся дальше верхней границы

    return Rect(l, t, w, h)      

Сотворим экземпляр камеры, добавим перед основным циклом:

total_level_width  = len(level[0])*PLATFORM_WIDTH # Высчитываем фактическую ширину яруса
total_level_height = len(level)*PLATFORM_HEIGHT   # высоту

camera = Camera(camera_configure, total_level_width, total_level_height) 

Что мы сделали?

Мы сотворили внутри большого прямоугольника, размеры которого вычисляются так:

total_level_width  = len(level[0])*PLATFORM_WIDTH # Высчитываем фактическую ширину яруса
total_level_height = len(level)*PLATFORM_HEIGHT   # высоту

меньший прямоугольник, размером, одинаковым размеру окна.

Меньший прямоугольник центрируется касательно основного персонажа(способ update), и все объекты рисуются в меньшем прямоугольнике (способ apply), за счет чего создаётся ощущение движения камеры.

Для работы вышеописанного, необходимо изменить рисование объектов.

Заменим строчку
entities.draw(screen) # отображение
На

for e in entities:
   screen.blit(e.image, camera.apply(e))

И перед ней добавим

camera.update(hero) # центризируем камеру касательно персонажа

Сейчас можем изменить ярус.

level = [
       "----------------------------------",
       "-                                -",
       "-                       --       -",
       "-                                -",
       "-            --                  -",
       "-                                -",
       "--                               -",
       "-                                -",
       "-                   ----     --- -",
       "-                                -",
       "--                               -",
       "-                                -",
       "-                            --- -",
       "-                                -",
       "-                                -",
       "-      ---                       -",
       "-                                -",
       "-   -------         ----         -",
       "-                                -",
       "-                         -      -",
       "-                            --  -",
       "-                                -",
       "-                                -",
       "----------------------------------"]


Вот, собственно, и итог
image

Итог дозволено скачать

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

 

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

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