Менеджер ресурсов и динамический текст на SFML
Когда Вы только начинаете изучать игровую разработку на C++ с использованием SFML, перед многими из Вас встаёт вопрос: как правильно организовать хранение и загрузку игровых ресурсов? Сначала всё кажется просто - загружаешь текстуру здесь, шрифт там, звук где-то ещё. Но когда Ваш проект начинает расти, Вы сталкиваетесь с реальными проблемами.
Одна и та же текстурная карта может загружаться в память трижды - для главного меню, для уровня и для редактора. В этом случае память расходуется неэффективно, а код превращается в спагетти из вызовов loadFromFile, разбросанных по всей кодовой базе. Тогда Вы начинаете понимать, что нужен системный подход.
Какие варианты есть, это: хранение ресурсов в отдельных классах, глобальные переменные (да, я знаю, что это плохо), система с ручным управлением жизненного цикла.
Каждый из этих подходов имеет свои недостатки. Либо ресурсы живут слишком долго, либо их освобождение происходит не в тот момент, либо доступ к ним неудобный.
Самый оптимальный вариант, это создание класса сингелтона, для управления ресурсами.
Но почему бы не упаковать все ресурсы прямо в исполняемый файл?
Мы создадим менеджер ресурсов, который использует встроенные возможности Windows.
Стоит сразу оговориться — это решение работает исключительно под Windows. Оно использует системные API-функции для доступа к ресурсам, встроенным в EXE-файл.
Главное преимущество этой системы — все ресурсы действительно хранятся внутри EXE-файла. Вы не представляете, как приятно, когда твоя игра представляет собой всего один файл, который можно скопировать куда угодно, и он просто работает. Никаких "где-то потерялась текстура", "не найден шрифт" и прочих проблем с путями.
Конечно, у этого подхода есть свои ограничения:
1. Размер EXE-файла увеличивается (но современные компьютеры с этим легко справляются)
2. Невозможно изменить ресурсы без перекомпиляции
3. Работает только под Windows
Но для многих сценариев разработки игр эти ограничения не критичны. В моём случае плюсы перевешивают:
1. Простота распространения (один файл!)
2. Защита ресурсов от случайного изменения
3. Гарантия, что все нужные файлы на месте
Если вам нужна кроссплатформенность, этот подход не подойдёт. Но если вы, как и я, разрабатываете игры исключительно под Windows и цените простоту распространения, то встроенные ресурсы Windows — отличное решение. В следующих разделах я подробно расскажу, как реализовать эту систему с нуля.
Структура проекта в Visual Studio 2022
├── 📄 main.cpp # Главный файл с основным циклом
├── 📄 ResourceManager.h # Заголовочный файл менеджера ресурсов
├── 📄 ResourceManager.cpp # Реализация менеджера ресурсов
├── 📄 ColorfulText.h # Заголовочный файл анимированного текста
├── 📄 ColorfulText.cpp # Реализация анимированного текста
├── 📄 resource.h # Идентификаторы ресурсов
└── 📄 resources.rc # Скрипт компиляции ресурсов
Файлы ресурсов
Ожидаемый результат от программы
Начинаем творить чудеса
Сначала создаём в ручную файлы ресурсов используя редактор кода С++ нашей IDE. Надеюсь Вы уже справились с установкой библиотеки SFML, если нет почитайте статьи выше.
resource.h
#pragma once #define IDR_FONT1 201 // Шрифт #define IDR_FONT2 202 // Шрифт #define IDR_TEXTURE1 301 // Текстура
resource.rc
#include "resource.h" // ресурсы по умолчанию IDR_TEXTURE1 RCDATA "assets/puzzle.png" IDR_FONT1 RCDATA "assets/arial.ttf" IDR_FONT2 RCDATA "assets/glv.ttf" IDR_ICON1 ICON "assets/puzzle.ico"
В нашей программе все ресурсы находятся в отдельной папке assets, которая в свою очередь находится в корневом каталоге проекта.
Теперь создадим класс управления ресурсами.
ResourceManager.h
#pragma once #include <SFML/Graphics.hpp> #include <SFML/Audio.hpp> #include <string> #include <unordered_map> #include <memory> #include <optional> #include <source_location> #include <windows.h> // Класс для управления ресурсами (шрифты, текстуры, звуки, музыка) class ResourceManager { public: // Получение единственного экземпляра (паттерн Singleton) static ResourceManager& getInstance() noexcept; // Загрузка шрифта по идентификатору ресурса [[nodiscard]] bool loadFont(std::string_view id, int resourceId, const std::source_location& loc = std::source_l ocation::current()) noexcept; // Загрузка звукового буфера по идентификатору ресурса [[nodiscard]] bool loadSound(std::string_view id, int resourceId, const std::source_location& loc = std::sour ce_location::current()) noexcept; // Загрузка текстуры по идентификатору ресурса [[nodiscard]] bool loadTexture(std::string_view id, int resourceId, const std::source_location& loc = std::s ource_location::current()) noexcept; // Загрузка музыки по идентификатору ресурса [[nodiscard]] bool loadMusic(std::string_view id, int resourceId, const std::source_location& loc = s td::source_location::current()) noexcept; // Получение шрифта по идентификатору (возвращаем указатель) [[nodiscard]] std::optional<sf::Font*> getFont(std::string_view id, const std::source_location& loc = std::source_location::current()) noexcept; // Получение звукового буфера по идентификатору (возвращаем указатель) [[nodiscard]] std::opti onal<sf::SoundBuffer*> getSoundBuffer(std::string_view id, const std::source_location& loc = std::source_location::current()) noexcept; // Получение текстуры по идентификатору (возвращаем указатель) [[nodiscard]] std::optional< sf::Texture*> getTexture(std::string_view id, const std::source_locat ion& loc = std::source_location::current()) noexcept; // Получение изображения из текстуры по идентификатору [[nodiscard]] std::optional<sf::Image> getImage(std::string_view id, const std::source_l ocation& loc = std::source_location::current()) noexcept; //Получение музыкального потока по идентификатору (возвращаем указатель) [[nodiscard]] std::optional<sf::Music*> getMusic(std::string_view id, const std::sourc e_location& loc = std::source_location::current()) noexcept; // Запрещаем копирование и перемещение ResourceManager(const ResourceManager&) = delete; ResourceManager& operator=(const ResourceManager&) = delete; ResourceManager(ResourceManager&&) = delete; ResourceManager& operator=(ResourceManager&&) = delete; private: ResourceManager() = default; // Приватный конструктор для Singleton // Вспомогательный класс для RAII-управления ресурсами Windows API class ResourceHandle { public: ResourceHandle(HRSRC resource, HGLOBAL handle) noexcept : resource_(resource), handle_(handle) {} ~ResourceHandle() { if (handle_) FreeResource(handle_); } [[nodiscard]] const void* data() const noexcept { return LockResource(handle_); } [[nodiscard]] DWORD size() const noexcept { return SizeofResource(NULL, resource_); } private: HRSRC resource_; HGLOBAL handle_; }; // Шаблонный метод для загрузки ресурса template<typename T> [[nodiscard]] bool loadResource(int resourceId, T& target, const std::source_location& loc = std::source_location::current()) noexcept; // Хранилища ресурсов std::unordered_map<std::string, sf::Font> fonts_; // Шрифты // Звуковые буферы std::unordered_map<std::string, sf::SoundBuffer> buffers_; std::unordered_map<std::string, sf::Texture> textures_; // Текстуры // Музыкальные потоки std::unordered_map<std::string, std::unique_ptr<sf::Music>> musicStreams_; // Кэш изображений std::unordered_map<std::string, sf::Image> imageCache_; };
ResourceManager.cpp
#include "ResourceManager.h" #include <iostream> // Получение единственного экземпляра менеджера ресурсов (синглтон) ResourceManager& ResourceManager::getInstance() noexcept { static ResourceManager instance; return instance; } // Шаблонный метод для загрузки ресурса любого типа template<typename T> bool ResourceManager::loadResource(int resourceId, T& target, const std::source_location& loc) noexcept { // Поиск ресурса в исполняемом файле HRSRC resource = FindResource(NULL, MAKEINTRESOURCE( resourceId), RT_RCDATA); if (!resource) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Не удалось найти ресурс с ID " << resourceId << '\n'; return false; } // Загрузка ресурса с использованием RAII HGLOBAL handle = LoadResource(NULL, resource); if (!handle) { std::cerr << "Ошибка в " << loc.function _name() << " (строка " << loc.line() << "): Не удалось загрузить ресу рс с ID " << resourceId << '\n'; return false; } // Использование RAII-обертки для управления ресурсом ResourceHandle resourceHandle(resource, handle); const void* data = resourceHandle.data(); if (!data) { std::cerr << "Ошибка в " << loc .function_name() << " (строка " << loc.line() << "): Не удалось заблокиро вать ресурс с ID " << resourceId << '\n'; return false; } // Получение размера ресурса и загрузка в целевой объект DWORD size = resourceHandle.size(); return target.loadFromMemory(data, size); } // Загрузка шрифта из ресурсов bool ResourceManager::loa dFont(std::string_view id, int resourceId, const std::source_location& loc) noexcept { sf::Font font; if (!loadResource(resourceId, font, loc)) { std::cerr << "Оши бка в " << loc.function_name() << " (строка " << loc.line() << "): Не удалось загрузить шрифт '" << id << "'\n"; return false; } // Сохранение шрифта в словаре под указанным идентификатором fonts_[std::string(id)] = std::move(font); return true; } // Загрузка звукового буфера из ресурсов bool Resource Manager::loadSound(std::string_view id, int resourceId, const std::source_location& loc) noexcept { sf::SoundBuffer buffer; if (!loadResource(resourceId, buffer, loc)) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Не удалось загрузить звук '" << id << "'\n"; return false; } // Сохранение звукового буфера в словаре buffers_[std::string(id)] = std::move(buffer); return true; } // Загрузка текстуры из ресурсов bool ResourceManager::loadTexture(std::string_view id, int resourceId, const std::source_location& loc) noexcept { sf::Texture texture; if (!loadResource(resourceId, texture, loc)) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Не удалось загрузить текстуру '" << id << "'\n"; return false; } // Сохранение текстуры в словаре textures_[std::string(id)] = std::move(texture); return true; } // Загрузка музыки из ресурсов (особый случай, так как Music в SFML //работает с потоками) bool ResourceManager::loadMusic(std::string_view id, int resourceId, const std::source_location& loc) noexcept { // Поиск музыкального ресурса HRSRC resource = FindResource(NULL, MAKEINTRESOURCE(resourceId), RT_RCDATA); if (!resource) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Не удалось найти музыкальный ресурс '" << id << "'\n"; return false; } // Загрузка ресурса HGLOBAL handle = LoadResource(NULL, resource); if (!handle) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Не удалось загрузить музыкальный ресурс '" << id << "'\n"; return false; } // Использование RAII-обертки ResourceHandle resourceHandle(resource, handle); const void* data = resourceHandle.data(); if (!data) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Не удалось заблокировать музыкальный ресурс '" << id << "'\n"; return false; } // Создание музыкального потока из данных в памяти DWORD size = resourceHandle.size(); auto music = std::make_unique<sf::Music>(); if (!music->openFromMemory(data, size)) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Не удалось открыть музыкальный поток '" << id << "' из памяти\n"; return false; } // Сохранение музыкального потока в словаре musicStreams_[std::string(id)] = std::move(music); return true; } // Получение шрифта по идентификатору std::optional<sf::Font*> ResourceManager::getFont(std::string_view id, const std::source_location& loc) noexcept { auto it = fonts_.find(std::string(id)); if (it == fonts_.end()) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Шрифт '" << id << "' не найден\n"; return std::nullopt; } return &(it->second); } // Получение звукового буфера по идентификатору std::optional<sf::SoundBuffer*> ResourceManager::getSoundBuffer( std::string_view id, const std::source_location& loc) noexcept { auto it = buffers_.find(std::string(id)); if (it == buffers_.end()) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Звуковой буфер '" << id << "' не найден\n"; return std::nullopt; } return &(it->second); } // Получение текстуры по идентификатору std::optional<sf::Texture*> ResourceManager::getTexture( std::string_view id, const std::source_location& loc) noexcept { auto it = textures_.find(std::string(id)); if (it == textures_.end()) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Текстура '" << id << "' не найдена\n"; return std::nullopt; } return &(it->second); } // Получение изображения по идентификатору (с кэшированием) std::optional<sf::Image> ResourceManager::getImage(std::string_view id, const std::source_location& loc) noexcept { // Проверяем, есть ли изображение в кэше auto cacheIt = imageCache_.find(std::string(id)); if (cacheIt != imageCache_.end()) { return cacheIt->second; } // Если нет в кэше, загружаем из текстуры auto texIt = textures_.find(std::string(id)); if (texIt == textures_.end()) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Текстура для изображения '" << id << "' не найдена\n"; return std::nullopt; } // Конвертируем текстуру в изображение и кэшируем результат sf::Image image = texIt->second.copyToImage(); imageCache_[std::string(id)] = image; // Кэшируем изображение return image; } // Получение музыкального потока по идентификатору std::optional<sf::Music*> ResourceManager::getMusic(std::string_view id, const std::source_location& loc) noexcept { auto it = musicStreams_.find(std::string(id)); if (it == musicStreams_.end()) { std::cerr << "Ошибка в " << loc.function_name() << " (строка " << loc.line() << "): Музыкальный поток '" << id << "' не найден\n"; return std::nullopt; } return it->second.get(); }
Теперь мы можем создать класс для создания феерического текста
ColorfulText.h
#pragma once #include <SFML/Graphics.hpp> #include <vector> #include <string_view> #include <random> #include <memory> #include <optional> #include <ranges> #include <concepts> #include <source_location> class ResourceManager; // Предполагаем, что определен где-то еще class ColorfulText : public sf::Drawable { public: // Перечисление эффектов с scoped enum enum class Effect { RandomColors, // Случайные цвета для каждой буквы Wave, // Волна цветов, бегущая по тексту Blink, // Мигание букв с изменением яркости Rainbow, // Радужный переход цветов Pulse, // Пульсация размера букв Fade, // Затухание и появление цветов Twinkle // Мерцание случайных букв }; // Статический метод создания с поддержкой юникода [[nodiscard]] static std::optional<ColorfulText> create( std::string_view fon tId, std::wstring_view text, unsigned int charSize, const sf::Vector2f& position, const sf::RenderWindow& window, bool centerHorizontally = false, bool centerVertically = false, Effect effect = Effect::RandomColors, const std::source_locat ion& loc = std::source_location::current()); // Обновление состояния текста void update(float deltaTime) noexcept; // Установка эффекта с использованием концепций C++20 template<typename T> requires std::same_as<T, Effect> void setEffect(T newEffect) noexcept { setEffectImpl(newEffect); } // Установка позиции текста void setPosition(const sf::Vector2f& position, bool centerHorizontally = false, bool centerVertically = false) noexcept; // Запрещаем копирование, разрешаем перемещение ColorfulText(const ColorfulText&) = delete; ColorfulText& operator=(const ColorfulText&) = delete; ColorfulText(ColorfulText&&) = default; ColorfulText& operator=(ColorfulText&&) = default; private: // Приватный конструктор для внутреннего использования в create ColorfulText() = default; // Переопределение метода отрисовки из sf::Drawable void draw(sf::RenderTarget& target, sf::RenderStates states) const override; // Методы обновления эффектов void updateRandomColors(); // Случайные цвета void updateWave(float deltaTime); // Волна цветов void updateBlink(float deltaTime); // Мигание букв void updateRainbow(float deltaTime); // Радужный переход void updatePulse(float deltaTime); // Пульсация размера void updateFade(float deltaTime); // Затухание цветов void updateTwinkle(float deltaTime); // Мерцание случайных букв // Внутренняя реализия смены эффекта void setEffectImpl(Effect newEffect) noexcept; // Данные класса // Указатель на шрифт (хранится в ResourceManager) sf::Font* font_ = nullptr; std::vector<sf::Text> letters_; // Вектор букв текста float colorTimer_ = 0.0f; // Таймер для эффектов Effect currentEffect_; // Текущий эффект float waveOffset_ = 0.0f; // Смещение для плавных эффектов std::vector<float> twinkleTimers_; // Таймеры для эффекта мерцания // Константы // Интервал смены эффектов static constexpr float COLOR_CHANGE_INTERVAL = 0.5f; static constexpr float WAVE_SPEED = 1.0f; // Скорость волны static constexpr float PULSE_AMPLITUDE = 0.2f; // Амплитуда пульсации static constexpr float TWINKLE_CHANCE = 0.1f; // Шанс мерцания };
ColorfulText.cpp
#include "ColorfulText.h" #include "ResourceManager.h" #include <cmath> #include <algorithm> #include <iostream> std::optional<ColorfulText> ColorfulText::create( std::string_view fontId, std::wstring_view text, unsigned int charSize, const sf::Vector2f& position, const sf::RenderWindow& window, bool centerHorizontally, bool centerVertically, Effect effect, const std::source_location& loc) { // Создаем объект с помощью приватного конструктора ColorfulText result; ResourceManager& rm = ResourceManager::getInstance(); auto fontOpt = rm.getFont(fontId, loc); if (!fontOpt.has_value()) { return std::nullopt; // Ошибка уже выведена в getFont } result.font_ = fontOpt.value(); // Извлекаем указатель на шрифт // Расчет позиции текста float totalWidth = text.size() * charSize * 0.8f; float xPos = centerHorizontally ? (window.get Size().x - totalWidth) / 2.0f : position.x; float yPos = centerVertically ? (window. getSize().y - charSize) / 2.0f : position.y; // Резервируем память result.letters_.reserve(text.size()); result.twinkleTimers_.resize(text.size(), 0.0f); // Инициализация букв с использованием std::ranges (C++20) std::ranges::for_each(std::views::i ota(size_t{ 0 }, text.size()), [&](size_t i) { sf::Text letter; letter.setFont(*result.font_); // Устанавливаем символ юникода letter.setCharacterSize(charSize); letter.setString(std::wstring(1, text[i])); letter.setFillColor(sf::Color::Magenta); letter.setPosition(xPos + i * charSize * 0.8f, yPos); result.letters_.push_back(std::move(letter)); }); // Инициализация остальных полей result.currentEffect_ = effect; result.colorTimer_ = 0.0f; result.waveOffset_ = 0.0f; return result; } void ColorfulText::update(float deltaTime) noexcept { colorTimer_ += deltaTime; waveOffset_ += deltaTime * WAVE_SPEED; // Обновление в зависимости от текущего эффекта switch (currentEffect_) { case Effect::RandomColors: if (colorTimer_ >= COLOR_CHANGE_INTERVAL) { colorTimer_ = 0.0f; updateRandomColors(); } break; case Effect::Wave: updateWave(deltaTime); break; case Effect::Blink: updateBlink(deltaTime); break; case Effect::Rainbow: updateRainbow(deltaTime); break; case Effect::Pulse: updatePulse(deltaTime); break; case Effect::Fade: updateFade(deltaTime); break; case Effect::Twinkle: updateTwinkle(deltaTime); break; } } void ColorfulText::setEffectImpl(Effect newEffect) noexcept { currentEffect_ = newEffect; colorTimer_ = 0.0f; waveOffset_ = 0.0f; std::ranges::fill(twinkleTimers_, 0.0f); // Сброс таймеров мерцания update(0.0f); // Немедленное обновление состояния } void ColorfulText::updateRandomColors() { // Генерация случайных цветов для каждой буквы thread_local std::mt19937 gen{ std::random_device{}() }; std::uniform_int_distribution<int> dis(0, 255); std::ranges::for_each(letters_, [&](auto& letter) { letter.setFillColor(sf::Color( static_cast<sf::Uint8>(dis(gen)), static_cast<sf::Uint8>(dis(gen)), static_cast<sf::Uint8>(dis(gen)))); }); } void ColorfulText::updateWave(float deltaTime) { // Эффект волны цветов std::ranges::for_each(std::views::iota(size_t{ 0 }, letters_.size()), [&](size_t i) { float phase = waveOffset_ + i * 0.2f; sf::Uint8 value = static_cast<sf::Uint8>(127.5f + 127.5f * std::sin(phase)); letters_[i].setFillColor(sf::Color(value, 255 - value, value / 2)); }); } void ColorfulText::updateBlink(float deltaTime) { // Мигание букв через изменение прозрачности float alpha = 127.5f + 127.5f * std::sin(colorTimer_ * 5.0f); std::ranges::for_each(letters_, [alpha](auto& letter) { sf::Color color = letter.getFillColor(); color.a = static_cast<sf::Uint8>(alpha); letter.setFillColor(color); }); } void ColorfulText::updateRainbow(float deltaTime) { // Радужный эффект с плавным переходом std::ranges::for_each(std::views::iota(size_t{ 0 }, letters_.size()), [&](size_t i) { float phase = waveOffset_ + i * 0.1f; sf::Uint8 r = static_cast<sf::Uint8>(127.5f + 127.5f * std::sin(phase)); sf::Uint8 g = static_cast<sf::Uint8>(127.5f + 127.5f * std::sin(phase + 2.0f)); sf::Uint8 b = static_cast<sf::Uint8>(127.5f + 127.5f * std::sin(phase + 4.0f)); letters_[i].setFillColor(sf::Color(r, g, b)); }); } void ColorfulText::updatePulse(float deltaTime) { // Пульсация размера букв float scale = 1.0f + PULSE_AMPLITUDE * std::sin(colorTimer_ * 3.0f); std::ranges::for_each(letters_, [scale](auto& letter) { letter.setScale(scale, scale); }); } void ColorfulText::updateFade(float deltaTime) { // Затухание и появление цветов float alpha = 127.5f + 127.5f * std::sin(colorTimer_ * 2.0f); std::ranges::for_each(letters_, [alpha](auto& letter) { letter.setFillColor(sf::Color(255, 255, 255, static_cast<sf::Uint8>(alpha))); }); } void ColorfulText::updateTwinkle(float deltaTime) { // Мерцание случайных букв thread_local std::mt19937 gen{ std::random_device{}() }; std::uniform_real_distribution<float> dis(0.0f, 1.0f); std::ranges::for_each(std::views::iota(size_t{ 0 }, letters_.size()), [&](size_t i) { twinkleTimers_[i] += deltaTime; if (twinkleTimers_[i] >= COLOR_CHANGE_INTERVAL) { if (dis(gen) < TWINKLE_CHANCE) { letters_[i].setFillColor(sf::Color::White); twinkleTimers_[i] = 0.0f; } else { letters_[i].setFillColor(sf::Color(100, 100, 100)); } } }); } void ColorfulText::draw(sf::RenderTarget& target, sf::RenderStates states) const { // Отрисовка всех букв for (const auto& letter : letters_) { target.draw(letter, states); } } void ColorfulText::setPosition(const sf::Vector2f& position, bool centerHorizontally, bool centerVertically) noexcept { // Установка новой позиции текста float totalWidth = letters_.size() * letters_[0].getCharacterSize() * 0.8f; float xPos = centerHorizontally ? position.x - totalWidth / 2.0f : position.x; float yPos = centerVertically ? position.y - letters_[0].getCharacterSize() / 2.0f : position.y; std::ranges::for_each(std::views::iota(size_t{ 0 }, letters_.size()), [&](size_t i) { letters_[i].setPosition(xPos + i * letters_[0].getCharacterSize() * 0.8f, yPos); }); }
А теперь все объединим в исполняем файле.
Мигающие_буквы.cpp
#include <SFML/Graphics.hpp> #include "ColorfulText.h" #include "ResourceManager.h" #include "resource.h" #include <iostream> // Инициализация ресурсов void initializeResources() { ResourceManager& rm = ResourceManager::getInstance(); if (!rm.loadTexture("puzzle", IDR_TEXTURE1) || !rm.loadFont("font", IDR_FONT1)) { throw std::runtime_error("Не удалось загрузить ресурсы"); } } int main() { initializeResources(); sf::RenderWindow window(sf::VideoMode(1280, 720), L"Весёлый текст", sf::Style::Default); window.setFramerateLimit(60); // Установка иконки окна ResourceManager& rm = ResourceManager::getInstance(); auto iconOpt = rm.getImage("puzzle"); if (!iconOpt) { std::cerr << "Не удалось загрузить иконку 'puzzle'\n"; return 1; } sf::Image icon = *iconOpt; // Извлекаем значение из std::optional if (icon.getSize().x == 0 || icon.getSize().y == 0) { std::cerr << "Иконка 'puzzle' пуста\n"; return 1; } window.setIcon(icon.getSize().x, icon.getSize().y, icon.getPixelsPtr()); // Создание текста с юникодом auto text = ColorfulText::create("font", L"Привет, мир!", 100, sf::Vector2f(0, 0), window, true, true, ColorfulText::Effect::Pulse); if (!text) { std::cerr << "Не удалось создать ColorfulText\n"; return 1; } sf::Clock clock; while (window.isOpen()) { sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) { window.close(); } // Переключение эффектов по нажатию клавиш (только 7 эффектов) if (event.type == sf::Event::KeyPressed) { switch (event.key.code) { case sf::Keyboard::F1: text->setEffect( ColorfulText::Effect::RandomColors); break; case sf::Keyboard::F2: text->setEffect( ColorfulText::Effect::Wave); break; case sf::Keyboard::F3: text->setEffect( ColorfulText::Effect::Blink); break; case sf::Keyboard::F4: text->setEffect( ColorfulText::Effect::Rainbow); break; case sf::Keyboard::F5: text->setEffect( ColorfulText::Effect::Pulse); break; case sf::Keyboard::F6: text->setEffect( ColorfulText::Effect::Fade); break; case sf::Keyboard::F7: text->setEffect( ColorfulText::Effect::Twinkle); break; } } } // Обновление и отрисовка текста float deltaTime = clock.restart().asSeconds(); text->update(deltaTime); window.clear(sf::Color::Blue); window.draw(*text); window.display(); } return 0; }