
- •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

Command and Control – Input Handling
The default constructor initializes the category to Category::None. By assigning a different value to it, we can specify exactly who receives the command. If we want a command to be executed for all airplanes except the player's one, the category can be set accordingly:
Command command; command.action = ...;
command.category = Category::AlliedAircraft | Category::EnemyAircraft;
Command execution
We have discussed how to construct commands with a function and a receiver category. In order to execute them, the function must be invoked on the receivers.
In our world, commands are passed to the scene graph, inside which they are distributed to all scene nodes with the corresponding game objects. Each scene node is responsible for forwarding a command to its children.
We write a non-virtual method SceneNode::onCommand() which is called every time a command is passed to the scene graph. First, we check if the current scene node is a receiver of the command, that is, if it is listed in the command's receiver category. The check is performed using the bitwise AND operator. If a bit is set in both the command's and the current node's category, then we know that the node receives the command. In this case, we can execute the command by invoking the action member of type std::function on the current node, and with the current frame time. The second part of onCommand() forwards the command to all the child nodes:
void SceneNode::onCommand(const Command& command, sf::Time dt)
{
if (command.category & getCategory()) command.action(*this, dt);
FOREACH(Ptr& child, mChildren) child->onCommand(command, dt);
}
[ 100 ]
www.it-ebooks.info

Chapter 4
Command queues
Now that the interface to distribute a command inside the scene graph is ready, we need a way to transport commands to the world and the scene graph. For this purpose, we write a new class CommandQueue. This class is a very thin wrapper around a queue of commands. A queue is a FIFO (first in, first out) data structure that ensures that elements, which are inserted first, are also removed first. Only the front element can be accessed. The standard library already provides the container adapter std::queue, which implements a queue interface on top of a full-featured STL container such as std::deque.
Our class looks similar to the following:
class CommandQueue |
|
{ |
|
public: |
|
void |
push(const Command& command); |
Command |
pop(); |
bool |
isEmpty() const; |
private:
std::queue<Command> mQueue;
};
It only provides three methods, which directly forward their calls to the underlying std::queue. Their definitions are straightforward, and hence they are omitted here.
The World class holds an instance of CommandQueue. In the World::update() function, all commands that have been triggered since the last frame are forwarded to the scene graph:
void World::update(sf::Time dt)
{
...
//Forward commands to the scene graph while (!mCommandQueue.isEmpty())
mSceneGraph.onCommand(mCommandQueue.pop(), dt);
//Regular update step
mSceneGraph.update(dt);
}
[ 101 ]
www.it-ebooks.info

Command and Control – Input Handling
As explained earlier, SceneNode::onCommand() distributes a command across all scene nodes. We also provide a getter function to access the command queue from outside the world:
CommandQueue& World::getCommandQueue()
{
return mCommandQueue;
}
Handling player input
Since this chapter is about input, it would be interesting to see how the commands can be exploited to react to the SFML events and real-time input. Up to now, player input has been handled in the Game class. But it deserves an own class, we call it
Player.
The Player class contains two methods to react to the SFML events and real-time input, respectively:
class Player |
|
{ |
|
public: |
|
void |
handleEvent(const sf::Event& event, |
|
CommandQueue& commands); |
void |
handleRealtimeInput(CommandQueue& commands); |
}; |
|
These methods are invoked from the Game class, inside the processInput() member function. Only the sf::Event::Closed event is still handled inside Game, all other events are delegated to the Player class:
void Game::processInput()
{
CommandQueue& commands = mWorld.getCommandQueue();
sf::Event event;
while (mWindow.pollEvent(event))
{
mPlayer.handleEvent(event, commands);
if (event.type == sf::Event::Closed) mWindow.close();
}
mPlayer.handleRealtimeInput(commands);
}
[ 102 ]
www.it-ebooks.info

Chapter 4
Now let's see how input is handled inside the Player class. We treat the example of the arrow keys and real-time input with sf::Keyboard. What we want to do is change the aircraft's velocity if an arrow key is pressed. For our command, we need
an action function, we design it as a function object (functor) similar to the following:
struct AircraftMover
{
AircraftMover(float vx, float vy) : velocity(vx, vy)
{
}
void operator() (SceneNode& node, sf::Time) const
{
Aircraft& aircraft = static_cast<Aircraft&>(node); aircraft.accelerate(velocity);
}
sf::Vector2f velocity;
};
When the functor is invoked, operator() is called, which adds (vx, vy) to the current aircraft velocity. aircraft.accelerate(velocity) is a utility function that acts equivalently to aircraft.setVelocity(aircraft.getVelocity() + velocity). In other words, the variable velocity is added to the aircraft's current velocity. The downcast is required because the command stores a function which is invoked on SceneNode&, but we need Aircraft&. It is safe as long as we guarantee with the receiver category that only correct types receive the command. We can now construct a command as follows:
Command moveLeft;
moveLeft.category = Category::PlayerAircraft; moveLeft.action = AircraftMover(-playerSpeed, 0.f);
[ 103 ]
www.it-ebooks.info

Command and Control – Input Handling
Since we often work on entities that are classes derived from SceneNode, the constant need for downcasts is annoying. It would be much more user friendly if we could directly create a function with the signature void(Aircraft& aircraft, sf::Time dt) instead. This is possible, if we provide a small adapter derivedAction() that takes a function on a derived class such as Aircraft and converts it to a function on the SceneNode base class. We create a lambda expression, inside which we invoke the original function fn on the derived class, passing a downcast argument to it. An additional assertion checks in the debug mode that the conversion is safe, which
is extremely helpful to avoid bugs. The lambda expression uses a [=] capture list, meaning that variables referenced from its body (such as the variable fn) are copied from the surrounding scope:
template <typename GameObject, typename Function> std::function<void(SceneNode&, sf::Time)>
derivedAction(Function fn)
{
return [=] (SceneNode& node, sf::Time dt)
{
// Check if cast is safe assert(dynamic_cast<GameObject*>(&node) != nullptr);
// Downcast node and invoke function on it fn(static_cast<GameObject&>(node), dt);
};
}
Given this adapter, we can change our AircraftMover to take Aircraft& instead of
SceneNode&:
struct AircraftMover
{
...
void operator() (Aircraft& aircraft, sf::Time) const
{
aircraft.accelerate(velocity);
}
};
A command would then be constructed as follows:
Command moveLeft;
moveLeft.category = Category::PlayerAircraft; moveLeft.action
= derivedAction<Aircraft>(AircraftMover(-playerSpeed, 0));
[ 104 ]
www.it-ebooks.info

Chapter 4
To be honest, our adapter does not have the simplest implementation, but it should be worth the advantage that after writing it once, we can create actions in a much cleaner way, without the need to downcast again and again.
Let's get back to the interesting part. Let's finally define
Player::handleRealtimeInput(), which creates a command every frame an arrow key is held down:
void Player::handleRealtimeInput(CommandQueue& commands)
{
const float playerSpeed = 30.f;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
{
Command moveLeft;
moveLeft.category = Category::PlayerAircraft; moveLeft.action = derivedAction<Aircraft>(
AircraftMover(-playerSpeed, 0.f)); commands.push(moveLeft);
}
}
For one-time events, the handling is quite similar. As a simple example, we write a lambda expression that outputs the position of the player's aircraft every time the user presses the P key:
void Player::handleEvent(const sf::Event& event, CommandQueue& commands)
{
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::P)
{
Command output;
output.category = Category::PlayerAircraft; output.action = [] (SceneNode& s, sf::Time)
{
std::cout << s.getPosition().x << "," << s.getPosition().y << "\n";
};
commands.push(output);
}
}
[ 105 ]
www.it-ebooks.info