Часть 2 "Менеджер ресурсов"

Предыдущая тема

Следующая тема

Пчела на работе

Пчела на работе – простая компьютерная игра, написанная на С++ используя мультимедийную библиотеку SFML.

Желающие ознакомиться с игрою могут:

скачать игру “Пчела на работе” с Telegram канала;

клонировать репозиторий игры “Пчела на работе” с GitHub.

Управление ресурсами

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

Начнем с простого факта - объекты в стеке уничтожаются, когда их вытаскивают.
В некоторых языках программирования все классы размещаются в куче (где память управляется автоматически сборщиком мусора). Однако в C++, если мы хотим хранить объекты в стеке, что значительно упрощает управление памятью, мы должны быть уверены, что объекты не будут уничтожены пока программа их использует.

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

sf::Sprite createSprite(std::string const& filename)
{
sf::Texture texture;
texture.loadFromFile(filename);

return sf::Sprite(texture);
}

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

 Код заголовочного файл AssetManager.h

#pragma once
#include<SFML/Audio.hpp>
#include <SFML/Graphics.hpp>
#include <assert.h>
#include<map>

class AssetManager
{

public:
	AssetManager();

	static sf::Texture& GetTexture(std::string const& filename);
	static sf::SoundBuffer& GetSoundBuffer(std::string const& filename);
	static sf::Font& GetFont(std::string const& filename);
private:

	std::map<std::string, sf::Texture> m_Textures;
	std::map<std::string, sf::SoundBuffer> m_SoundBuffer;
	std::map<std::string, sf::Font> m_Fonts;

	static AssetManager* sInstance;
};

Класс AssetManager является синглтоном, т. е. допускается существование только одного экземпляра данного класса, именно поэтому он имеет статический указатель на себя. На самом деле, все статические функции класса AssetManager будут использовать этот указатель как способ ссылки на экземпляр этого класса. Создание менеджера ресурсов по такому принципу очень удобно для написания кода, потому что мы можем вызвать статическую функцию данного класс (AssetManager::GetTexture()) из любого места программы, не имея ссылки на объект класса AssetManager. Возможно, для небольших проектов этот вариант может показаться бессмысленным, однако разрабатывая крупные игровые проекты, такой способ в написании класса по управлению ресурсами избавит Вас от головной боли с передачею ссылок на объект данного класса. Помимо указателя, класс содержит массивы контейнера map, в которых находятся игровые ресурсы: Texture (текстуры), SoundBuffer (звуковые эффекты), Font (шрифты). Получение доступа к этим ресурсам, а также наполнение их в массивах контейнеров map происходит с помощью статических функций GetTexture(), GetSoundBuffer(), GetFont().

map — стандартный шаблонный класс библиотеки языка программирования C++, используется для хранения и извлечения данных из коллекции, в которой каждый элемент является парой, обладающей одновременно значением данных и уникальным ключом сортировки. Значение элемента в сопоставлении можно изменить напрямую. Значение ключа является константой и не может быть изменено. Вместо этого значения ключей, связанные со старыми элементами, необходимо удалить и вставить новые значения ключей для новых элементов. В нашем случае ключи содержат имена файлов ресурсов, а значения — объекты: текстуры, звуковые эффекты, шрифты. Перебирая ключи ассоциативного массива map, мы можем легко проверить, существует ли имя файла ресурса в данном массиве и если нет добавить его.

Теперь давайте посмотрим на файл AssetManager.cpp

#include "AssetManager.h"

AssetManager* AssetManager::sInstance = nullptr;

AssetManager::AssetManager()
{
	// Разрешить существование только одного AssetManager
	// В противном случае сгенерировать исключение
	assert(sInstance == nullptr);
	sInstance = this;
}

В первой строке после подключения заголовочного файла мы инициализируем статический указатель значением nullptr, это значит, что указатель равен нулю. Установив значение указателя на nullptr, мы в дальнейшем сможем проверить истину этого значения.

Проверку указателя на равенство нулю мы проводим внутри конструктора класса AssetManager, используя для этого макрос assert (оператор проверочного утверждения), который обрабатывает условное выражение во время выполнения. Если указатель равен нулю, программа продолжит свою работу, если нет макрос вызовет прерывание, которое завершит программу.

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

Далее рассмотрим реализацию статической функции GetTexture().

sf::Texture& AssetManager::GetTexture(std::string const& filename)
{
	auto& texMap = sInstance->m_Textures;

	auto pairFound = texMap.find(filename);

	if (pairFound != texMap.end())
	{
		return pairFound->second;
	}
	else
	{
		auto& texture = texMap[filename];
		texture.loadFromFile(filename);
		return texture;
	}

}

Функция GetTexture() является статической, а значит мы можем получить массив map через статический указатель экземпляра класса, sInstance. В параметрах функции GetTexture() получаем путь и название файла с картинкой текстуры через константу filename. Используем данную константу в качестве параметра для функции find(), которая проверяет, была ли загружена в ассоциативный массив запрошенная текстура ранее. Функция find() возвращает итератор, указывающий на найденную пару в массиве контейнера map, после чего, функция GetTexture() возвратит найденную текстуру. Если пара не найдена, функция find() укажет на конец массива, в таком случаи мы создадим слот в контейнере map для этой текстуры и загрузим файл с ненайденной текстурой в ассоциативный массив. Далее функция GetTexture() снова возвратит загруженную текстуру.

Подобную функцию создаём для ресурсов звуковых эффектов и шрифтов.

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

#pragma once

#include<SFML/Graphics.hpp>
#include<memory>
#include"AssetManager.h"

class Engine
{
	AssetManager manager;      // Менеджер ресурсов
	
  // Код класса
};

Подключаем заголовочный файл для работы с менеджером ресурсов AssetManager.h внутри класса Engine.h. В закрытой области класса Engine, создаём объект менеджер ресурсов manager.

Engine::Engine()
{
	// Получение ссылки на текстуру для прямоугольника 
	background.setTexture(&AssetManager::GetTexture("image/background.jpg"));
}

В конструкторе загружаем текстуру фона игры используя функцию менеджера ресурсов GetTexture().

Подведём итог: класс менеджер ресурсов содержит массив игровых ассетов и интерфейс доступа к ним, что облегчает разработку крупных игровых проектов.

Продолжение следует...

Более подробную инструкцию вы можете получить, посмотрев видео «Игра на SFML C++ Пчела на работе часть 2 Менеджер ресурсов»

Телеграмм канал "Программирование игр С++/С#

Предыдущая тема

Следующая тема

Комментарии (4)


  1. Oska2
    00.00.0000 00:00
    +1

    "Менеджер ресурсо" на первой же иллюстрации. Это так задумано для кликбейта или очепятка?)


    1. Troyy Автор
      00.00.0000 00:00

      опечатка


  1. sp1cklerboy
    00.00.0000 00:00
    +1

    Информативно и полезно. Единственное к чему у меня претензии это к репозиторий на GitHub. В репозитории не должны храниться бинарные файлы и директория Build.


    1. jmdorian
      00.00.0000 00:00
      +1

      Я бы еще отметил что Engen наверное все-таки Engine, ну и реализация синглтона вызывает некоторые вопросы. Есть же вполне известный и понятный синглтон Майерса, а местный как-то попахивает.