Менеджер ресурсов и динамический текст на 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;
}