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

Warfare Unleashed –
Implementing Gameplay
In Chapter 5, Diverting the Game Flow – State Stack and Chapter 6, Waiting and Maintenance Area – Menus, you have seen how to handle menus and states, now it is time to return to the actual game. Up till now, we have built a world that can
contain various entities, and implemented the basic interaction mechanisms through updates, drawing, and commands. However, this is not particularly interesting as long as the world is empty.
In this chapter, we are going to populate the world, and implement the core part of the game; the actual gameplay with enemies, weapons, battles, and goodies. We are going to cover the following topics:
•Enemy aircraft controlled by a simple artificial intelligence
•Projectiles such as a machine gun or missiles
•Pickups that improve the player's equipment
•Collision detection and response between entities in the scene graph
•The world's update cycle and automatic removal of entities
Equipping the entities
You have heard about entities for the first time in Chapter 3, Forge of the Gods – Shaping Our World, where we built the World class and the scene graph. As a quick reminder, the SceneNode base class was inherited by the Entity class. Entities are the central part of this chapter. It's all about the interaction between entities of
different kinds. Before starting to implement all those interactions, it is reasonable to think about crucial properties our entities need to have.
www.it-ebooks.info

Warfare Unleashed – Implementing Gameplay
Introducing hitpoints
Since, we are preparing our airplanes for the battlefield, we need to provide them with new specific attributes. To our class definition of Entity, we add a new member variable that memorizes the current hitpoints. Hitpoints (HP) are a measure for the hull integrity of an entity; the entity is destroyed as soon as the hitpoints reach or fall below zero.
In addition to the member variable, we provide member functions that allow the modification of the hitpoints. We do not provide direct write access, however, the hitpoints can be decreased (the plane is damaged) or increased (the plane is repaired). Also, a destroy() function instantly destroys the entity.
class Entity : public SceneNode
{
public: |
|
explicit |
Entity(int hitpoints); |
void |
repair(int points); |
void |
damage(int points); |
void |
destroy(); |
int |
getHitpoints() const; |
bool |
isDestroyed() const; |
... |
|
private: |
|
int |
mHitpoints; |
... |
|
};
The implementation is as expected: repair() adds the specified hitpoints, damage() subtracts them, and destroy() sets them to zero.
Storing entity attributes in data tables
In our game, there are already two different airplanes with different attributes. For this chapter, we introduce a third one to make the game more interesting. With an increasing amount of new aircraft types, attributes such as speed, hitpoints, used texture, or fire rate may vary strongly among them. We need to think of a way to store those properties in a central place, allowing easy access to them.
What we clearly want to avoid are case differentiations in every Aircraft method, since this makes the local logic code less readable, and spreads the attributes across different functions. Instead of if/else cascades or switch statements, we can store the attributes in a central table, and just access the table every time we need an attribute.
[ 152 ]
www.it-ebooks.info

Chapter 7
Let's define the type of such a table entry in the case of an airplane. We choose the simplest way, and have a structure AircraftData with all members public. This type is defined in the file DataTables.hpp.
struct AircraftData
{
int |
hitpoints; |
float |
speed; |
Textures::ID |
texture; |
};
While AircraftData is a single table entry, the whole table is represented as a sequence of entries, namely std::vector<AircraftData>.
Next, we write a function that initializes the table for different aircraft types.
We begin to define a vector of the correct size (Aircraft::TypeCount is the last enumerator of the enum Aircraft::Type, it contains the number of different aircraft types). Since the enumerators are consecutive and begin at zero, we can use them
as indices in our STL container. We thus initialize all the attributes for different airplanes, and eventually return the filled table.
std::vector<AircraftData> initializeAircraftData()
{
std::vector<AircraftData> data(Aircraft::TypeCount);
data[Aircraft::Eagle].hitpoints = 100; data[Aircraft::Eagle].speed = 200.f; data[Aircraft::Eagle].texture = Textures::Eagle;
data[Aircraft::Raptor].hitpoints = 20; data[Aircraft::Raptor].speed = 80.f; data[Aircraft::Raptor].texture = Textures::Raptor;
...
return data;
}
The global function initializeAircraftData() is declared in DataTables.hpp and defined in DataTables.cpp. It is used inside Aircraft.cpp, to initialize a global constant Table. This constant is declared locally in the .cpp file, so only the
Aircraft internals can access it. In order to avoid name collisions in other files, we use an anonymous namespace.
namespace
{
const std::vector<AircraftData> Table = initializeAircraftData();
}
[ 153 ]
www.it-ebooks.info

Warfare Unleashed – Implementing Gameplay
Inside the Aircraft methods, we can access a constant attribute of the own plane type using the member variable mType as index. For example, Table[mType]. hitpoints denotes the maximal hitpoints of the current aircraft.
Data tables are only the first step of storing gameplay constants. For more flexibility, and to avoid recompiling the application, you can also store these constants externally, for example, in a simple text file or using a specific file format. The application initially loads these files, parses the values, and fills the data tables accordingly.
Nowadays, it is very common to load gameplay information from external resources. There are text-based formats such as YAML or XML, as well as, many application-specific text and binary formats. There are also well-known C++ libraries such as Boost.Serialize (www.boost. org) that help with loading and saving data structures from C++.
One possibility that has recently gained popularity consists of using script languages, most notably Lua (www.lua.org), in addition to C++. This has the advantage that not only constant data, but dynamic functionality can be outsourced and loaded during runtime.
Displaying text
We would like to add some text on the display, for example, to show the hitpoints or ammunition of different entities. Since this text information is supposed to be shown next to the entity, it stands to reason to attach it to the corresponding scene node.
We therefore, create a TextNode class which inherits SceneNode as shown in the following code:
class TextNode : public SceneNode
{
public: |
|
explicit |
TextNode(const FontHolder& fonts, |
|
const std::string& text); |
void |
setString(const std::string& text); |
private: |
|
virtual void |
drawCurrent(sf::RenderTarget& target, |
|
sf::RenderStates states) const; |
private: |
|
sf::Text |
mText; |
};
The implementation of the functions is not complicated. The SFML class sf::Text provides most of what we need. In the TextNode constructor, we retrieve the font from the resource holder and assign it to the text.
[ 154 ]
www.it-ebooks.info

Chapter 7
TextNode::TextNode(const FontHolder& fonts, const std::string& text)
{
mText.setFont(fonts.get(Fonts::Main));
mText.setCharacterSize(20);
setString(text);
}
The function to draw the text nodes just forwards the call to the SFML render target, as you know it from sprites.
void TextNode::drawCurrent(sf::RenderTarget& target, sf::RenderStates states) const
{
target.draw(mText, states);
}
For the interface, mainly the following method is interesting. It assigns a new string to the text node, and automatically adapts to its size. centerOrigin() is a utility function we wrote; it sets the object's origin to its center, which simplifies positioning a lot.
void TextNode::setString(const std::string& text)
{
mText.setString(text);
centerOrigin(mText);
}
In the Aircraft constructor, we create a text node and attach it to the aircraft itself. We keep a pointer mHealthDisplay as a member variable and let it point to the attached node.
std::unique_ptr<TextNode> healthDisplay(new TextNode(fonts, "")); mHealthDisplay = healthDisplay.get(); attachChild(std::move(healthDisplay));
In the method Aircraft::update(), we check for the current hitpoints, and convert them to a string, using our custom toString() function. The text node's string and relative position are set. Additionally, we set the text node's rotation to the negative aircraft rotation, which compensates the rotation in total. We do this in order to have the text always upright, independent of the aircraft's orientation.
mHealthDisplay->setString(toString(getHitpoints()) + " HP"); mHealthDisplay->setPosition(0.f, 50.f); mHealthDisplay->setRotation(-getRotation());
[ 155 ]
www.it-ebooks.info