Открытие и управление окном SFML

Введение

Этот урок рассказывает только о том, как открывать окно и как управлять им. Рисование объектов находится вне сферы действия модуля sfml-window: оно обрабатывается модулем sfml-graphics. Однако, управление окном остаётся точно таким же, поэтому прочитать этот урок в любом случае нужно.

Открытие окна

Окна в SFML определяются с помощью класса sf::Window. Окно может быть создано и открыто непосредственно в конструкторе:

#include <SFML/Window.hpp>

int main()
{
    sf::Window window(sf::VideoMode(800, 600), "My window");

    ...

    return 0;
}

Первый аргумент — видео режим (video mode), определяет размер окна (внутренний размер, без заголовка и границ). Здесь мы создаём окно 800 x 600 пикселей.
Класс sf::VideoMode имеет несколько интересных статических функций для получения разрешения рабочего стола или списка допустимых видео режимов для полноэкранного режима. Если интересно, то загляните в документацию.

Второй аргумент это просто заголовок окна.

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

sf::Style::None Без всяких декораций (полезно для заставок); этот стиль не может быть объединён с другими
sf::Style::Titlebar Окно с заголовком
sf::Style::Resize Размер окна можно изменять. Имеется кнопка максимизации окна
sf::Style::Close Окно с кнопкой закрытия окна
sf::Style::Fullscreen Окно в полноэкранном режиме; этот стиль не может быть объединён с другими
sf::Style::Default Стиль по умолчанию с ярлыками Titlebar | Resize | Close

В конструкторе может быть так же четвёртый необязательный аргумент, который определяет опции OpenGL.

Если вы хотите создать окно после создания объекта sf::Window или хотите заново создать его с другим видео режимом или заголовком, вы можете воспользоваться функцией create(). Она требует те же самые аргументы что и конструктор.

#include <SFML/Window.hpp>

int main()
{
    sf::Window window;
    window.create(sf::VideoMode(800, 600), "My window");

    ...

    return 0;
}

Оживление окна

Если вы попробуете выполнить тот код, что был представлен выше, ничего не написав при этом вместо «…», то едва ли вы что-нибудь увидите. Во-первых, потому что программа немедленно завершится, а во вторых, потому что нет обработки событий — так что даже если добавить туда бесконечный цикл, вы увидите «мёртвое» окно, которое нельзя подвинуть, изменить его размер или закрыть.

Добавим кое-что в код:

#include <SFML/Window.hpp>

int main()
{
    sf::Window window(sf::VideoMode(800, 600), "My window");

    // пусть программа работает до тех пор, пока открыто окно
    while (window.isOpen())
    {
        // проверить все события окна, которые были вызваны с последней итерации цикла
        sf::Event event;
        while (window.pollEvent(event))
        {
            // "запрос закрытия" событие: мы закрываем окно
            if (event.type == sf::Event::Closed)
                window.close();
        }
    }

    return 0;
}

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

Сначала мы добавили цикл, который гарантирует, что приложение будет обновляться до закрытия окна. Большинство программ на основе SFML будут иметь подобный цикл, который иногда называют главный цикл или игровой цикл.

Затем, первое что мы делаем внутри игрового цикла, это проверяем события которые были вызваны. Обратите внимание, что мы используем цикл while так, что обрабатываются все события, в случае если в очереди их было несколько. Функция pollEvent() возвращает истину, если в очереди были события, и возвращает ложь, если в очереди событий не оказалось.

Всякий раз при получении события, мы должны проверять его тип (закрыто окно? нажата клавиша? подвинута мышка? присоединён джойстик? …) и реагировать соответствующим образом, если это событие нас интересует. В нашем конкретном случае мы заботимся только о событии Event::Closed, которое срабатывает, когда пользователь хочет закрыть окно. На данный момент, окно всё ещё открыто и мы должны закрыть его явно с помощью функции close(). Это позволяет сделать что-нибудь до того, как окно закроется, например сохранить текущее состояние приложения или отобразить сообщение.

Частая ошибка, заключается в том, что люди забывают про цикл событий, потому как ещё не заботятся об обработке событий (используют входы в реальном времени). Но без цикла событий, окно не будет реагировать; действительно, цикл обработки событий играет две роли: в дополнение к обработке пользовательских событий, он даёт возможность окну обрабатывать и внутренние события, которые необходимы для реагирования на изменения размера окна и перемещения его.

После закрытия окна, главный цикл останавливается и программа завершает работу.

На данный момент, мы пока не рассматриваем рисование объектов в окне (об этом уже говорилось в ведении к уроку). Если хотите нарисовать спрайты, текст, фигуры, то всё это описано в уроках о модуле sfml-graphics (перевод их появится немного позже).

Для рисования объектов можно использовать и OpenGL, при это совсем не задействовав модуль sfml-graphics. Об этом тоже будет рассказано, но чуть позже.

Так что не ожидайте пока увидеть в окне что-то интересное :) Скорее всего это будет окно залитое чёрным или белым цветом.

Управление окном

Конечно, SFML позволяет немного поиграться с вашими окнами. Поддерживаются такие базовые операции как изменение размера окна, позиции, заголовка, иконки..но в отличие от специализированных GUI библиотек (Qt, wxWidgets), SFML не обеспечивает расширенные функции. Окна SFML предназначены только для обеспечения основы для рисования с использованием OpenGL или SFML.

// изменение позиции окна (относительно экрана)
window.setPosition(sf::Vector2i(10, 50));

// изменение размера окна
window.setSize(sf::Vector2u(640, 480));

// изменение заголовка окна
window.setTitle("SFML window");

// получение размера окна
sf::Vector2u size = window.getSize();
unsigned int width = size.x;
unsigned int height = size.y;

...

Вы можете посмотреть в API документации sf::Window полный список функций.

В том случае если вам необходимо использовать расширенные возможности для вашего окна (или вам хочется создать полноценный GUI), то это можно осуществить с помощью другой библиотеки (например Qt) и встроить SFML уже непосредственно внутрь. Что бы сделать это, вы можете использовать другой конструктор или написать функцию для sf::Window которая будет брать управление существующим окном, специфическое для данной ОС. В этом случае SFML создаст контекст рисования внутри данного окна и будет отлавливать все события без нарушения управления первоначальным окном.

sf::WindowHandle handle = /* зависит от используемой библиотеки и того что вы делаете */;
sf::Window window(handle);

Если вы просто хотите использовать некое дополнение, какую-то очень специфическую особенность, вы можете сделать и наоборот: создать окно SFML и получить для него управление, характерное для ОС, для реализации той вещи которая не поддерживается SFML.

sf::Window window(sf::VideoMode(800, 600), "SFML window");
sf::WindowHandle handle = window.getSystemHandle();

// вы можете использовать handle вместе с функциями характерными для ОС

Мы не будем  рассматривать интеграцию SFML с конкретными библиотеками (хотя для Qt я позже напишу отдельную статью).

Управление частотой кадров

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

Решение этого заключается в включении вертикальной синхронизации. Она автоматически управляется видеокартой и может быть легко включена или выключена функцией setVerticalSyncEnabled():

window.setVerticalSyncEnabled(true); // вызывается лишь однажды, после создания окна

После этого ваше приложение будет работать с той же частотой что и ваш монитор, примерно 60 кадров в секунду.

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

Вы можете сами задать нужную частоту кадров, вызвав функцию setFramerateLimit():

window.setFramerateLimit(30); // вызывается лишь однажды, после создания окна

В отличии от setVerticalSyncEnabled(), эта возможность реализована самим SFML на основе sf::Clock and sf::sleep. Важным следствием этого, является то, что эта функция не на 100% точна (особенно при высокой частоте кадров). Всё зависит от ОС и различия могут быть в 10-15 миллисекунд. Не стоит полагаться на эту возможность если важна высокая точность.

Никогда не используйте setVerticalSyncEnabled() и setFramerateLimit() в одно и тоже время!

Что нужно знать об окнах

Вот краткий список того, что вы можете и не можете делать с окнами SFML.

Вы можете создать несколько окон.

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

Пока что нет корректной поддержки нескольких мониторов

SFML не способна явно управлять несколькими мониторами. Как следствие, вы не можете выбрать на каком из мониторов появится окно и не сможете создавать более одного полноэкранного окна.

События должны опрашиваться в потоке окна

Это важное ограничение большинства ОС: цикл обработки событий (точнее функции pollEvent() или waitEvent()) должен быть вызван в том же потоке, в котором создавалось окно. Это означает, что если вы хотите создать специальный поток для обработки событий, вы должны иметь ввиду, что окно должно создаваться в нём. Если вы хотите разделить задачи между потоками, то удобнее всего держать обработку событий в главном потоке, а всё остальное (рендеринг, физика, логика) в другом. Это будет возможно и с учётом ограничения описанного ниже.

В OS X окна и события должны обрабатываться в главном потоке

Да, это правда. Mac OS X просто не позволит создать окно или обрабатывать события не в главном потоке.

В Windows окно которое больше чем экран, будет вести себя не корректно.

По некоторым причинам Windows не любит окна размер которых превышает размеры экрана. Если создаётся окно с видео режимом VideoMode::getDesktopMode() например точно равным размеру экрана, то при добавлении декораций в виде заголовка и рамок, в конечном итоге может получится окно, большее чем размер экрана.

18 Комментарии

  1. Егор

    Добрый день, а может быть и вечер короче не буду тянуть кота за хвост и перейду сразу к делу. У меня вопрос к тем людям которые используют SFML: Как сделать в SFML свет (освещение) желательно с примером. ПРОШУ ПОМОГИТЕ ТРИ ДНЯ ДОЛБАЮСЬ!!!

    1. samuel_unknown (Автор записи)

      С примером пока что не помогу, сам я это не реализовывал.
      На хабре есть парочка статей по поводу реализации освещения (например http://habrahabr.ru/post/183534/). Там всё через шейдеры делается, так что тебе надо почитать про их использование в SFML вот тут http://www.sfml-dev.org/tutorials/2.1/graphics-shader.php (статья на английском, руки всё не доходят написать перевод).

  2. maksis9n

    А не подскажете как сделать так,чтобы размер экрана задавался в программе и больше не изменялся? т.е. был постоянным

    1. samuel_unknown (Автор записи)
      sf::Window renderWindow;
      renderWindow.create(sf::VideoMode(800, 600), "Game!", sf::Style::Close);
      

      Так его даже мышкой не получится растянуть или сжать :)

      1. maksis9n

        спасибо огромное) я это и имел ввиду)

        1. samuel_unknown (Автор записи)

          Рад был помочь. :)

          1. maksis9n

            если не сложно то не могли бы вы дать какие-нибудь данные чтобы с вами связаться,хотел по sfml пару вопросов спросить

          2. samuel_unknown (Автор записи)

            Вы натолкнули меня на мысль добавить на сайт форму обратной связи :)
            Написать мне можно вот на этой странице http://progressor-blog.ru/svyazatsya-so-mnoj/

  3. dmitri_burov

    Неправильный перевод фразы «On OS X, windows and events must be managed in the main thread» —
    «В OS X и Windows события должны обрабатываться в главном потоке». Правильно так «В OS X окна и события должны обрабатываться в главном потоке». Т.е. в винде такого ограничения нет

    1. samuel_unknown (Автор записи)

      Спасибо! Исправил :)

  4. Лалыч

    При вводе русских букв в имя заголовка выводятся каракули.
    Можно ли русифицировать заголовок окна? Если можно, то как это сделать?

    1. samuel_unknown (Автор записи)

      Попробуй так:

      sf::Window window(sf::VideoMode(800, 600), L"Моя игра");
      
      1. Лалыч

        Огромное спасибо.)

  5. programer

    Огромное спасибо. :))

  6. Readix

    Подскажите, пожалуйста , а можно ли изменять стиль окна после инициализации, то есть, например, создать окно 800х600, а потом уже сделать его Fullscreen ?

    1. samuel_unknown (Автор записи)

      Если правильно помню, то нельзя, но могу и ошибаться, сто лет с SFML не работал)

    2. Веталь

      Я сталкивался с такой проблемой. Просто удали окно и создай новое на весь экран

  7. Zilord

    Вопрос, есть разница между FullScreen и манипуляции через VideMode::DesktopMode(ну и коллекцию Style::None)?

Добавить комментарий для samuel_unknown Отменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Капча * Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.