12 февраля 2010

PyGame: Управление и столкновения...

Здравствуйте. Прошло достаточно времени, и я смогу открыть вам самую страшную тайну. Без этой тайны сделать игру было бы очень сложно. Что это за тайна? Это определение столкновений и управление.

Начнем с управления. Как же оно происходит? Все дело в событиях любое действие игрока по сути событие, будь то движение мыши, нажатие кнопки или еще чего. В pygame все это делается легко и просто. Рассмотрим следующий код:

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

import pygame

if __name__ == '__main__':
    pygame.init()

    display = pygame.display.set_mode((640,480))

    clock = pygame.time.Clock()

    hero = pygame.sprite.Sprite()
    hero.image = pygame.Surface((16,16))
    hero.image.fill((0,0,0))
    hero.rect = hero.image.get_rect()
    hero.rect.move_ip(312,232)

    done = False
    dt = 0
    while not done:
        # Обратите пристальное внимание на код в цикле.
        # pygame.event.get() возвращает список событий.
        for e in pygame.event.get():
            # Тип события pygame.QUIT посылается нашей
            # игре если игрок решил закрыть окно с ней
            if e.type == pygame.QUIT:
                done = True
                continue
            # А вот и то что нас интересует.
            # pygame.KEYDOWN и pygame.KEYUP
            # это нажатие и отпускание кнопки на клавиатуре
            # соответственно.
            if e.type == pygame.KEYDOWN:
                hero.image.fill((255,0,0))
                # По мимо кода самой клавиши 'e' для pygame.KEYDOWN
                # будет содержать поле unicode и mod
                # первое, это код клавиши в юникоде (что из названия понятно)
                # второе, это модификатор (shift, ctrl).
                pygame.display.set_caption(\
                    u"key: {0}({1})".format(\
                        pygame.key.name(e.key),\
                        e.key))
                continue
            if e.type == pygame.KEYUP:
                # Для pygame.KEYUP у 'e' не будет поля unicode.
                hero.image.fill((0,0,0))
                continue

        display.fill((255,255,255))
        display.blit(hero.image, hero.rect)
        pygame.display.flip()

        dt = clock.tick(40)

Вот видите, ничего сложного нет. Так что если вы захотите что бы ваша игра закрывалась по нажатие клавиши esc, то можете добавить в цикл обработки событий такие строки:

#...
if __name__ == '__main__':
#...
        for e in pygame.event.get():
            #...
            if e.type == pygame.KEYDOWN \
               and e.key == pygame.K_ESCAPE:
                done = True
                continue
            #...

Посмотреть имена клавиш и соответствующие им константы вы можете в документации к pygame, прямая ссылка на соответствующий участок документации прилагается.

Теперь давайте заставим наш квадрат двигаться.

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

import pygame

speed = 2

up = pygame.K_w
down = pygame.K_s
left = pygame.K_a
right = pygame.K_d

if __name__ == '__main__':
    pygame.init()

    display = pygame.display.set_mode((640,480))

    clock = pygame.time.Clock()

    hero = pygame.sprite.Sprite()
    hero.image = pygame.Surface((16,16))
    hero.image.fill((0,0,0))
    hero.rect = hero.image.get_rect()
    hero.rect.move_ip(312,232)

    done = False
    dt = 0
    while not done:
        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                done = True
                continue
            if e.type == pygame.KEYDOWN \
               and e.key == pygame.K_ESCAPE:
                done = True
                continue

        # Вот таким не хитрым образом мы получаем список
        # нажатых клавишь. Если клавиша нажата, то ей
        # соответствует значение True, если не нажата то
        # False.
        keys = pygame.key.get_pressed()
        if keys[up]:
            hero.rect.move_ip(0, -speed)
        if keys[down]:
            hero.rect.move_ip(0, speed)
        if keys[left]:
            hero.rect.move_ip(-speed,0)
        if keys[right]:
            hero.rect.move_ip(speed, 0)

        display.fill((255,255,255))
        display.blit(hero.image, hero.rect)
        pygame.display.flip()

        dt = clock.tick(40)

Вот так все просто. Теперь давайте я объясню вам как можно средствами pygame обнаружить столкновение. А что такое столкновение? Это когда один игровой объект влезает в личную зону другого игрового объекта. А что такое игровой объект? Да что угодно: ракета, гоблин, танк, аптечка, дверь.

Рассмотрим это на примере:

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

import pygame

speed = 2

up = pygame.K_w
down = pygame.K_s
left = pygame.K_a
right = pygame.K_d

# Это плитка на полу, она постепенно превращается в белую.
class Tiles(pygame.sprite.Sprite):
    def __init__(self, x=0, y=0, group=None):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((16,16))
        self.rect = self.image.get_rect()
        self.rect.move_ip(x, y)
        # это значения зеленого канала в RGB
        self.color = 255
        self.image.fill((255, self.color, 255))
        # Добавляем спрайт в группу.
        self.add(group)

    # Здесь мы постепенно обесцвечиваем квадрат,
    # увеличиваем значение зеленого канала.
    def update(self, dt):
        fill =  dt / 2.0
        self.color += int(fill)
        if self.color >= 255:
            self.color = 255
        self.image.fill((255, self.color, 255))

if __name__ == '__main__':
    pygame.init()

    display = pygame.display.set_mode((640,480))

    clock = pygame.time.Clock()

    hero = pygame.sprite.Sprite()
    hero.image = pygame.Surface((16,16))
    hero.image.fill((0,0,0))
    hero.rect = hero.image.get_rect()
    hero.rect.move_ip(16*18,16*10)
    # Это группа спрайтов.
    group = pygame.sprite.Group()
    for x in range(640/16):
        for y in range(480/16):
            Tiles(x*16, y*16, group)

    done = False
    dt = 0
    while not done:
        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                done = True
                continue
            if e.type == pygame.KEYDOWN \
               and e.key == pygame.K_ESCAPE:
                done = True
                continue

        keys = pygame.key.get_pressed()
        if keys[up]:
            hero.rect.move_ip(0, -speed)
        if keys[down]:
            hero.rect.move_ip(0, speed)
        if keys[left]:
            hero.rect.move_ip(-speed,0)
        if keys[right]:
            hero.rect.move_ip(speed, 0)

        # `pygame.sprite.spritecollide` находит все спрайты
        # из группы у которых есть столкновение со спрайтом,
        # в данном случае все плитки которых касается `hero`.
        for obj in pygame.sprite.spritecollide(hero, group, None):
            obj.color = 0

        # У всех спрайтах входящих в группу вызывается метод `update`.
        group.update(dt)

        display.fill((255,255,255))
        # Рисуем все спрайты из группы, используется поля спрайта
        # `image` и `rect`.
        group.draw(display)
        display.blit(hero.image, hero.rect)
        pygame.display.flip()

        dt = clock.tick(40)

Что такое pygame.sprite.Group? Это контейнер. Стоит использовать группы когда необходимо объединить ряд спрайтов по какому либо признаку. Например мины определить в одну группу, а мартышек в другую. Зачем? Помимо того что мы с помощью групп сможем быстро нарисовать и обновить спрайты, мы можем найти столкновения между ними. Подробнее о возможностях групп можете узнать из документации.

На этом пожалуй все, но только на этот раз. Пока.