
- •Credits
- •Foreword
- •About the Authors
- •About the Reviewers
- •www.PacktPub.com
- •Table of Contents
- •Preface
- •Introducing SFML
- •Downloading and installation
- •A minimal example
- •A few notes on C++
- •Developing the first game
- •The Game class
- •Game loops and frames
- •Input over several frames
- •Vector algebra
- •Frame-independent movement
- •Fixed time steps
- •Other techniques related to frame rates
- •Displaying sprites on the screen
- •File paths and working directories
- •Real-time rendering
- •Adapting the code
- •Summary
- •Defining resources
- •Resources in SFML
- •Textures
- •Images
- •Fonts
- •Shaders
- •Sound buffers
- •Music
- •A typical use case
- •Graphics
- •Audio
- •Acquiring, releasing, and accessing resources
- •An automated approach
- •Finding an appropriate container
- •Loading from files
- •Accessing the textures
- •Error handling
- •Boolean return values
- •Throwing exceptions
- •Assertions
- •Generalizing the approach
- •Compatibility with sf::Music
- •A special case – sf::Shader
- •Summary
- •Entities
- •Aircraft
- •Alternative entity designs
- •Rendering the scene
- •Relative coordinates
- •SFML and transforms
- •Scene graphs
- •Scene nodes
- •Node insertion and removal
- •Making scene nodes drawable
- •Drawing entities
- •Connecting entities with resources
- •Aligning the origin
- •Scene layers
- •Updating the scene
- •One step back – absolute transforms
- •The view
- •Viewport
- •View optimizations
- •Resolution and aspect ratio
- •View scrolling
- •Zoom and rotation
- •Landscape rendering
- •SpriteNode
- •Landscape texture
- •Texture repeating
- •Composing our world
- •World initialization
- •Loading the textures
- •Building the scene
- •Update and draw
- •Integrating the Game class
- •Summary
- •Polling events
- •Window events
- •Joystick events
- •Keyboard events
- •Mouse events
- •Getting the input state in real time
- •Events and real-time input – when to use which
- •Delta movement from the mouse
- •Playing nice with your application neighborhood
- •A command-based communication system
- •Introducing commands
- •Receiver categories
- •Command execution
- •Command queues
- •Handling player input
- •Commands in a nutshell
- •Implementing the game logic
- •A general-purpose communication mechanism
- •Customizing key bindings
- •Why a player is not an entity
- •Summary
- •Defining a state
- •The state stack
- •Adding states to StateStack
- •Handling updates, input, and drawing
- •Input
- •Update
- •Draw
- •Delayed pop/push operations
- •The state context
- •Integrating the stack in the Application class
- •Navigating between states
- •Creating the game state
- •The title screen
- •Main menu
- •Pausing the game
- •The loading screen – sample
- •Progress bar
- •ParallelTask
- •Thread
- •Concurrency
- •Task implementation
- •Summary
- •The GUI hierarchy, the Java way
- •Updating the menu
- •The promised key bindings
- •Summary
- •Equipping the entities
- •Introducing hitpoints
- •Storing entity attributes in data tables
- •Displaying text
- •Creating enemies
- •Movement patterns
- •Spawning enemies
- •Adding projectiles
- •Firing bullets and missiles
- •Homing missiles
- •Picking up some goodies
- •Collision detection and response
- •Finding the collision pairs
- •Reacting to collisions
- •An outlook on optimizations
- •An interacting world
- •Cleaning everything up
- •Out of view, out of the world
- •The final update
- •Victory and defeat
- •Summary
- •Defining texture atlases
- •Adapting the game code
- •Low-level rendering
- •OpenGL and graphics cards
- •Understanding render targets
- •Texture mapping
- •Vertex arrays
- •Particle systems
- •Particles and particle types
- •Particle nodes
- •Emitter nodes
- •Affectors
- •Embedding particles in the world
- •Animated sprites
- •The Eagle has rolled!
- •Post effects and shaders
- •Fullscreen post effects
- •Shaders
- •The bloom effect
- •Summary
- •Music themes
- •Loading and playing
- •Use case – In-game themes
- •Sound effects
- •Loading, inserting, and playing
- •Removing sounds
- •Use case – GUI sounds
- •Sounds in 3D space
- •The listener
- •Attenuation factor and minimum distance
- •Positioning the listener
- •Playing spatial sounds
- •Use case – In-game sound effects
- •Summary
- •Playing multiplayer games
- •Interacting with sockets
- •Socket selectors
- •Custom protocols
- •Data transport
- •Network architectures
- •Peer-to-peer
- •Client-server architecture
- •Authoritative servers
- •Creating the structure for multiplayer
- •Working with the Server
- •Server thread
- •Server loop
- •Peers and aircraft
- •Hot Seat
- •Accepting new clients
- •Handling disconnections
- •Incoming packets
- •Studying our protocol
- •Understanding the ticks and updates
- •Synchronization issues
- •Taking a peek in the other end – the client
- •Client packets
- •Transmitting game actions via network nodes
- •The new pause state
- •Settings
- •The new Player class
- •Latency
- •Latency versus bandwidth
- •View scrolling compensation
- •Aircraft interpolation
- •Cheating prevention
- •Summary
- •Index

Keeping Track of Your Textures – Resource Management
The implementation stays the same, so it is not listed again. Our class now looks as follows:
class TextureHolder
{
public: |
|
void |
load(Textures::ID id, |
|
const std::string& filename); |
sf::Texture& |
get(Textures::ID id); |
const sf::Texture& |
get(Textures::ID id) const; |
private: |
|
std::map<Textures::ID, std::unique_ptr<sf::Texture>> mTextureMap;
};
Now the get() method is easy to use and can directly be invoked when a texture is requested:
TextureHolder textures;
textures.load(Textures::Airplane, "Media/Textures/Airplane.png");
sf::Sprite playerPlane; playerPlane.setTexture(textures.get(Textures::Airplane));
Error handling
The basic steps are done, the main functionality is implemented. However, there may be errors which we have to recognize and handle meaningfully. The first error can occur during the loading of the texture. For example, the specified file might not exist, or the file might have an invalid image format, or be too big for the video memory of the graphics card. To handle such errors, the method sf::Texture::loadFromFile() returns a Boolean value that is true in case of success, and false in case of failure.
There are several strategies to react to resource loading errors. In our case, we have to consider that the texture is later needed by sprites that are rendered on the screen—if such a sprite requests the texture, we must give something back. One possibility would be to provide a default texture (for example, plain white), so the sprites are just drawn as a white rectangle. However, we do not want the player of our game to fiddle around with rectangles; he should either have a proper airplane or nothing. But how can we implement "nothing"? We have to notify the caller of our load() method that something did not work. A possibility to implement these notifications is shown in the next sections.
[ 40 ]
www.it-ebooks.info

Chapter 2
Boolean return values
We could follow SFML's philosophy and return a Boolean value denoting success or failure. This approach has some disadvantages. We cannot use the return type for something else. Additionally, the caller has to check the returned value every time he calls load(). This is easily overlooked, and if it is not, it leads to messy usage code that is full of error checks. That is not what we want, as initially stated our objective consists of performing as much work as possible in the TextureHolder, to relieve the user from writing boilerplate code.
Throwing exceptions
Another approach to react to a loading failure is to throw an exception. We choose the standard exception type std::runtime_error. To its constructor, we pass an error message describing the problem as clearly as possible, including the filename:
if (!texture->loadFromFile(filename))
throw std::runtime_error("TextureHolder::load - Failed to load " + filename);
Exceptions have the big advantage that user code can be kept clean of error handling. Clients can now have the following code:
TextureHolder textures;
textures.load(Textures::Landscape, "Media/Textures/Desert.png"); textures.load(Textures::Airplane, "Media/Textures/Airplane.png"); textures.load(Textures::Missile, "Media/Textures/Missile.png");
We do not need to check every single call. If an error occurs, an exception will be thrown until a try-catch block catches it and reacts meaningfully. It is possible that the exception passes several functions before it is eventually handled.
Once the resource is loaded, we insert it into the map. Here, we have to be aware of possible error sources too. When the given ID is already stored, the map will refuse to insert our ID-resource pair, as it cannot contain duplicate keys. The member function std::map::insert() returns a pair with an iterator to the inserted element and a Boolean value which is true if inserting was successful. We store this returned pair and check its second member (the Boolean value). Instead of writing std::pa
ir<std::map<Textures::ID, std::unique_ptr<sf::Texture>>::iterator, bool>, we can use C++11 type inference:
auto inserted = mTextureMap.insert(std::make_pair(id, std::move(resource)));
[ 41 ]
www.it-ebooks.info

Keeping Track of Your Textures – Resource Management
Now, inserted is our pair containing an iterator and a Boolean value, inserted. second is the Boolean value denoting the success of the insertion. If it is false, we know that the ID is already stored in the map. How do we react to this situation?
We could throw a std::runtime_error exception again. However, in contrast to a loading failure, double insertion is not a runtime error. The attempt to insert the same ID twice in the map is a logic error, meaning, there is a mistake in the application logic—in other words, a bug. A well-formed program would not attempt to load
the same resource twice. In comparison, runtime errors occur in correctly written programs too, for example, if the user renames or moves the resource files. For logic errors, the standard library provides the exception class std::logic_error.
This raises already the next question: how do you handle such exceptions? It is not that once you have thrown an exception, you can forget about it and the world is in harmony. Somebody has to catch those exceptions, and it had better be you and not the operating system (unless you like crashing applications). In the case of a loading failure, we can tell the player that the files were not found, and prevent him from starting the game. But what in our double insertion case? Are we supposed to tell the player that the programmer accidentally called load() twice? Certainly not. This bug must not occur in the final application. There is no way to recover from it—continuing the application is dangerous, because its logic is broken, and we risk upsetting even more if we ignore the error. What if the two load() calls are passed the same ID, but different filenames? We do not know with which resource the ID is associated. If we later want to access a resource by its ID, we might get the wrong resource, and thus display a wrong image on the screen. In this manner, errors can propagate further and further, sometimes remaining for a long time before being noticed. In case of a logic error, we would like the program to interrupt immediately.
Assertions
Clearly, a mechanism apart from exceptions is appropriate, which shows us directly and inevitably when something goes wrong. This is where assertions come into play. The macro assert evaluates its expression; if it is false in debug mode, a breakpoint is triggered, halting the program execution and directly pointing to the source
of the error. In release mode, assertions are optimized away, so we do not waste any performance to check for errors that cannot occur. The assert expression is completely removed in release mode, so make sure you only use it for error checks, and not to implement actual functionality with possible side effects.
We have to insert a single line, we expect that the Boolean member of the pair returned by std::map::insert() is true:
assert(inserted.second);
[ 42 ]
www.it-ebooks.info