- SFML Game Development
- Artur Moreira Henrik Vogelius Hansson Jan Haller
- 984字
- 2021-08-13 17:11:12
Generalizing the approach
We have implemented everything we need for textures, but we would like to handle other resources such as fonts and sound buffers too. As the implementation looks extremely similar for them, it would be a bad idea to write new classes FontHolder
and SoundBufferHolder
with exactly the same functionality. Instead, we write a class template, which we instantiate for different resource classes.
We call our template ResourceHolder
and equip it with two template parameters:
- Resource: The type of resource, for example,
sf::Texture
. We design the class template to work the SFML classes, but if you have your own resource class which conforms to the required interface (providingloadFromFile()
methods), nothing keeps you from using it together withResourceHolder
. - Identifier: The ID type for resource access, for example,
Textures::ID
. This will usually be an enum, but the type is not restricted to enumerations. Any type that supports anoperator<
can be used as identifier, for example,std::string
.
The transition from TextureHolder
to ResourceHolder<Resource, Identifier>
is straightforward. We replace the used types with the generic template parameters: sf::Texture
becomes Resource
, and Textures::ID
becomes Identifier
. Furthermore, we rename some variables to reflect the fact that we are talking about resources in general, not only textures. We also adapt the member functions accordingly.
Note
One thing we have to note when using templates is that the complete implementation needs to be in the header. We cannot use .cpp
files for the method definitions anymore, but we would still like to separate interface and implementation. That is why we use a file ResourceHolder.hpp
for the class definition, and a file ResourceHolder.inl
for the method definitions. At the end of the .hpp
file, we include the .inl
file containing the implementation. .inl
is a common file extension for inline template implementations.
The generalized class definition has the following interface:
template <typename Resource, typename Identifier> class ResourceHolder { public: void load(Identifier id,const std::string& filename); Resource& get(Identifier id); const Resource& get(Identifier id) const; private: std::map<Identifier,std::unique_ptr<Resource>> mResourceMap; };
The load()
method can be written in the following way. Like before, we attempt to load the resource from a file, and then we insert the unique pointer into the map and make sure the insertion was successful:
template <typename Resource, typename Identifier> void ResourceHolder<Resource, Identifier>::load(Identifier id, const std::string& filename) { std::unique_ptr<Resource> resource(new Resource()); if (!resource->loadFromFile(filename)) throw std::runtime_error("ResourceHolder::load - Failed to load " + filename); auto inserted = mResourceMap.insert( std::make_pair(id, std::move(resource))); assert(inserted.second); }
The two overloaded get()
member functions are generalized using the same principle, that is why they are not listed here. You can refer to the online code base for a complete implementation.
Compatibility with sf::Music
As initially mentioned, the class sf::Music
has semantics that are very different from other resource types. This begins already with its openFromFile()
method that is not compatible to the loadFromFile()
call in our implementation. Because of its streaming nature, a music object cannot be shared between multiple clients—each object represents one point in time of a certain music theme. No object contains the whole music data at once.
Instead of forcing sf::Music
into a concept it does not fit, we decide to not store it inside ResourceHolder
instances. This does not imply that there will be no encapsulation for music; we are rather going to cover music handling in-depth during Chapter 9, Cranking Up the Bass – Music and Sound Effects.
A special case – sf::Shader
There is one resource type we ave yet to cover: shaders. Since a SFML shader object can consist of a fragment and/or a vertex shader, the interface of sf::Shader
deviates slightly from the other resource classes in SFML. sf::Shader
provides two methods to load from a file:
bool loadFromFile(const std::string& filename, sf::Shader::Type type); bool loadFromFile(const std::string& vertexShaderFilename, const std::string& fragmentShaderFilename);
The first function loads either a fragment or a vertex shader (which one is specified by the second parameter). The second function loads both a vertex and a fragment shader.
This interface is an issue for our generic implementation, because ResourceHolder::load()
contains the following expression:
resource->loadFromFile(filename)
Which assumes that loadFromFile()
is invoked with one argument. For sf::Shader
, we have to specify two instead.
The solution is simple: we write an overloaded ResourceHolder::load()
function that takes an additional parameter and forwards it directly as the second argument to sf::Shader::loadFromFile()
. This parameter can have the type sf::Shader::Type
or const std::string&
. In order to cope with both types, we add a function template parameter. Our new load()
function has the following declaration:
template <typename Parameter> void load(Identifier id, const std::string& filename, const Parameter& secondParam);
The function definition is listed in the following code. Do not confuse yourself by the two template parameter lists; the first one is required for the class template ResourceHolder
and the second one for the function template ResourceHolder::load()
. In the function body, only the loadFromFile()
call is different from before.
template <typename Resource, typename Identifier> template <typename Parameter> void ResourceHolder<Resource, Identifier>::load(Identifier id, const std::string& filename, const Parameter& secondParam) { std::unique_ptr<Resource> resource(new Resource()); if (!resource->loadFromFile(filename, secondParam)) throw ...; ... // insertion like before }
A nice side effect of this additional overload is that it enables other argument combinations for loadFromFile()
too. The method of sf::Texture
actually looks as shown in the following line of code:
bool loadFromFile(const std::string& filename, const IntRect& area = IntRect())
The default parameter can be used if we want the texture to load only a rectangular area of the image. Usually, we do not specify it and load the whole file, but thanks to our second load()
overload, we have now the possibility to use this parameter.
In our complete implementation, we have written a separate function that inserts resources into the map, in order to reduce code duplication.
Using the new ResourceHolder
class template, we can visualize a possible in-game situation in the following diagram. On the left you see two resource holders, one for textures and one for fonts. Each one contains a map of enumerators to resources. The player's Aircraft
class on the right stores a sprite that points to a texture, as well as a text that points to a font.

- UNIX編程藝術
- 零基礎搭建量化投資系統:以Python為工具
- Spring 5.0 By Example
- Microsoft Dynamics 365 Extensions Cookbook
- Reactive Programming with Swift
- Java入門很輕松(微課超值版)
- Mastering Python Scripting for System Administrators
- 薛定宇教授大講堂(卷Ⅳ):MATLAB最優化計算
- JSP開發案例教程
- Android底層接口與驅動開發技術詳解
- 軟件測試技術指南
- Web Development with MongoDB and Node(Third Edition)
- Getting Started with Greenplum for Big Data Analytics
- AppInventor實踐教程:Android智能應用開發前傳
- Apache Kafka Quick Start Guide