08 ноября 2009

PyGame: Окно открывается и закрывается...

Доброго времени суток!

В этом уроке мы с вами научимся создавать окно, и закрывать его. Зачем? Ну надо же с чего-нибудь начать, и мы начнем как-раз с этого. Мы создадим окно, покажем логотип, и закроем окно. Ну и раз на то пошло, мы установим заголовок и иконку для окна. Вот такие мы молодцы.

И так приступим. Вот мое лого , его я и буду показывать при открытие окна. Вы можете легко нарисовать свое лого в gimp или еще где. Постарайтесь сделать размер изображения 64х16, это конечно не принципиально, но позволит вам взять код примеров без изменений.

Так же нам потребуется иконка, мы возьмем вот эту , опять же можете нарисовать свою, иконка у меня размером 32х32, что опять же не принципиально, если конечно не считать что это иконка и размером она должна быть с иконку.

Давайте сначала договоримся о том где будут располагаться ресурсы игры. Пока это выглядит так:

data
`-- image
    |-- icon.png
    `-- logo.png

Теперь давайте создадим окно и установим ему заголовок и иконку.

Создадим файл main.py в корне нашего проекта и дадим файлу права на выполнение, это делается командой chmod u+x main.py.

Вот содержание файла main.py:

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

import os, time, pygame
from pygame.locals import *

# Этот класс отвечает за загрузку ресурсов.
class ResManager:
    # При инициализации класса мы указываем где у нас что находится.
    def __init__(self,
                 data_dir = 'data',
                 image_dir = 'image',
                 sound_dir = 'sound',
                 music_dir = 'music'):
        # Это корневой каталог ресурсов
        self.data_dir = data_dir
        # Это каталог с изображениями
        self.image_dir = image_dir
        # Это каталог со звуками
        self.sound_dir = sound_dir
        # Это каталог с музыкой
        self.music_dir = music_dir

    # Этот метод загружает файл по имени.
    def get_image(self, name):
        # Получаем имя нужного нам файла вместе с путями к нему.
        fullname = os.path.join(self.data_dir,
                                os.path.join(self.image_dir, name))

        try:
            # Пробуем загрузить изображение
            image = pygame.image.load(fullname)
        except pygame.error, message:
            # Если это не удалось сообщаем об этом и кидаем исключение
            # на выход, так как отсутствие нужно изображения,
            # критичная ошибка.
            print('Cannot load image: {0}'.format(name))

            raise SystemExit, message
        else:
            # Мы используем изображения с поддержкой альфа канала,
            # потому и конвертируем изображение в формат удобный pygame c
            # учетом этого самого альфа канала.
            image = image.convert_alpha()

            return image

if __name__ == '__main__':
    # Инициализируем pygame.
    pygame.init()

    # Устанавливаем разрешение экрана.
    pygame.display.set_mode((640,480))

    # Создаем менеджер ресурсов, мы используем значения по умолчанию
    # для функции __init__, так как они нас устраивают.
    manager = ResManager()

    # Устанавливаем иконку.
    pygame.display.set_icon(manager.get_image('icon.png'))
    # Устанавливаем заголовок.
    pygame.display.set_caption("Plambir")

    # А это что бы окно не закрылось сразу.
    time.sleep(10)

Нам этого мало, давайте наконец покажем наш логотип. Конечно просто показать его мало, давайте сделаем его появление плавным, как и исчезновение. Зачем? Просто что бы все усложнить. Ах, да, фон мы тоже поменяем.

Но с начало мы переместим класс ResManager в отдельный файл. Назовем его ResManager.py и положим в ту же папку что и main.py. После чего перенесем в него сам класс ResManage и добавим перед клвассом импорт нужных модулей import os, pygame.

Фух. Вот и все приготовления. Теперь сосредоточимся на задаче. Нам потребуется залить фон белым цветом и сделать анимацию логотипа. Как это сделать? Ну проблем с заливкой фона у нас поверьте не возникнет. Это достаточно просто.

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

import time, pygame
from ResManager import *

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

    # Получили поверхность которая и отображается в окне.
    display = pygame.display.set_mode((640,480))

    manager = ResManager()

    pygame.display.set_icon(manager.get_image('icon.png'))
    pygame.display.set_caption("Plambir")

    # Залили поверхность сплошным цветом
    display.fill((255,255,255))

    # Обновили окно, что бы было видно белый цвет.
    pygame.display.flip()

    time.sleep(10)

Я же говорил ничего сложного нет. Все достаточно просто. С плавным появлением логотипа дела обстоят чуть иначе. Давайте подумаем, как это можно сделать.

Что же такое анимация? Вы можете посмотреть определения этого слово в википедии, но я бы не советовал вам этого делать. Не надо. Давайте договоримся что мы будем понимать под анимацией смену заранее подготовленных картинок по времени. Как вы наверное понимаете что такой вид анимации как плавное исчезновение/появление можно получить не рисуя ее заранее.

И так, по порядку. Начнем с отрисовки изображений. Давайте просто загрузим изображение нашего логотипа и нарисуем его в середине экрана.

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

import time, pygame
from ResManager import *

def get_center(surface, sprite):
    # Координаты точки находятся просто, ширина и высота surface
    # делится на 2, тоже самое происходит и с sprite, после чего из
    # первого вычитается второе, так мы и получаем заветный центр.
    return (surface.w/2 - sprite.w/2,
            surface.h/2 - sprite.h/2)

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

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

    manager = ResManager()

    pygame.display.set_icon(manager.get_image('icon.png'))
    pygame.display.set_caption("Plambir")

    # Получили изображения логотипа.
    plambir = manager.get_image('logo.png')

    display.fill((255,255,255))

    # Рисуем наш логотип в середине экрана.
    # Функция get_center возвращает объект типа (x, y)
    # это координаты точки в которой рисуется логотип.
    # /-------------display
    # |
    # |   +-----\plambir
    # |   |     |
    # |   \-----/
    # |
    # '+' - и есть та самая точка, она находится не совсем
    # в центре, потому что иначе бы нам казалось что логотип смещен
    # вправо и вниз.
    display.blit(plambir, get_center(display.get_rect(), plambir.get_rect()))

    pygame.display.flip()

    time.sleep(10)

Вот собственно и все наш логотип появляется в середине экрана. Но где обещанная анимация? Еще секунду, сейчас мы этим и займемся.

С начало давайте увеличим наш логотип, а то уж больно он маленький.

    ...
    plambir = manager.get_image('logo.png')
    # Вот так мы увеличим наш логотип в 5 раз.
    plambir = pygame.transform.scale(plambir, (plambir.get_rect().w * 5,
                                               plambir.get_rect().h * 5))
    ...

Все, теперь давайте к анимации. Для того чтобы сделать анимацию плавной, нам понадобится такая удобная вещь, как класс pygame.time.Clock. Этот класс позволит нам синхронизировать нашу анимацию по времени, а так же ограничить fps (количество кадров в секунду). Зачем? А вы хотите видеть красивую анимацию? Конечно хотите. Для этого нужно знать сколько времени прошло после того как мы нарисовали предыдущий кадр, что и позволяет сделать класс pygame.time.Clock, а ограничение количество кадров в секунду позволит нам не мучить лишний раз компьютер.

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

import time, pygame
from ResManager import *

# Это класс который будет создавать анимированную прозрачность
class Transparent:
    # Нам нужен спрайт
    # Время за которое появляется спрайт или исчезает
    # Показывать или скрывать спрайт
    def __init__(self, sprite = None, time = 2000, show = True):
        # Получаем копию спрайта, в процессе анимации мы изменяем его
        # и чтобы не изменить оригинал мы получаем копию
        self.sprite = sprite.copy()

        # Устанавливаем флаг для Surface.fill
        if show:
            # Добавление цвета к Surface
            self.flag = pygame.BLEND_RGBA_ADD
        else:
            # Вычитания цвета из Surface
            self.flag = pygame.BLEND_RGBA_SUB

        # Если требуется показывать, то тогда устанавливаем
        # абсолютную прозрачность то есть в 0
        if show:
            self.sprite.fill((0,0,0,255),None,pygame.BLEND_RGBA_SUB)

        # Через какое время требуется изменить прозрачность
        self.time = float(255)/float(time)
        # Это нужно что бы знать запущена ли анимация
        self.run = False
        # Сколько нужно добавить или вычесть альфы из Surface
        self.add = float(0)
        # Нужно что бы посчитать сколько раз мы изменяли альфа канал
        self.count = 0

    # Принимает время с прошлого кадра
    def update(self, dt):
        # Если запущена анимация
        if self.run:
            # Считаем на сколько нужно изменить значения альфа канала
            self.add += float(dt) * self.time
            # Так как значение цвета число целое, мы должны его ждать
            if int(self.add) > 0:
                # Изменяет альфа канал спрайта
                self.sprite.fill((0,0,0,int(self.add)),None,self.flag)
                # Подсчитываем на сколько он уже изменился
                self.count += int(self.add)
                # Вычитаем целое чтобы не потерять еще не примененные
                # изменения значения прозрачности
                self.add = self.add - int(self.add)
                # Если мы достигли максима изменений (255)
                if self.count > 255:
                    # Сбросим счетчик
                    self.count = 0
                    # Останавливаем анимацию
                    self.run = False

    # Запускаем анимацию
    def start(self):
        self.run = True

    # Проверяем запущена ли анимация
    def is_start(self):
        return self.run

    # Меняем флаг для Surface.fill
    def flag_toggle(self):
        if self.flag == pygame.BLEND_RGBA_ADD:
            self.flag = pygame.BLEND_RGBA_SUB
        else:
            self.flag = pygame.BLEND_RGBA_ADD

def get_center(surface, sprite):
    return (surface.w/2 - sprite.w/2,
            surface.h/2 - sprite.h/2)

# Главный цикл
def loop(display,plambir):
    # Создаем класс Clock
    clock = pygame.time.Clock()
    # В этой переменной будет сохранятся время прошедшее
    # между рисованием кадров.
    dt = 0
    # Цикл будет работать пока анимация не закончится.
    while plambir.is_start():
        # Это обработка событий, об этом как-нибудь потом.
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                plambir.stop()

        display.fill((255,255,255))
        # Обновляем анимацию.
        plambir.update(dt)
        # Рисуем
        display.blit(plambir.sprite, get_center(display.get_rect(), plambir.sprite.get_rect()))
        pygame.display.flip()

        # Здесь указывается максимальное количество кадров в секунду
        # возвращается время прошедшее за время ожидания.
        dt = clock.tick(40)

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

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

    manager = ResManager()

    pygame.display.set_icon(manager.get_image('icon.png'))
    pygame.display.set_caption("Plambir")

    sprite = manager.get_image('logo.png')
    sprite = pygame.transform.scale(sprite, (sprite.get_rect().w * 5,
                                             sprite.get_rect().h * 5))

    # Создаем анимацию которая будет проигрываться 3 секунды
    # Время в миллисекундах.
    plambir = Transparent(sprite, 3000)
    plambir.start()

    # Передаем в игровой цикл экран, и анимацию
    loop(display, plambir)

    plambir.flag_toggle()
    plambir.start()

    loop(display, plambir)

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

Пока можете подумать почему такой подход к анимации прозрачности не совсем хорош (а скорее даже плох). Да и вообще. Думайте. Полезно.