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

Chapter 7
Adding projectiles
Finally, time to add what makes a game fun. Shooting down stuff is essential for our game. The code to interact with the World class is already defined, thanks to the actions in Player and to the existing Entity base class. All that's left is to define the projectiles themselves.
We start with the Projectile class. We have normal machine gun bullets and homing missiles represented by the same class. This class inherits from the Entity class and is quite small, since it doesn't have anything special that differentiates it from other entities apart from collision tests, which we will talk about later.
class Projectile : public Entity
{
public:
enum Type
{
AlliedBullet, |
|
EnemyBullet, |
|
Missile, |
|
TypeCount |
|
}; |
|
public: |
|
|
Projectile(Type type, |
|
const TextureHolder& textures); |
void |
guideTowards(sf::Vector2f position); |
bool |
isGuided() const; |
virtual unsigned int |
getCategory() const; |
virtual sf::FloatRect getBoundingRect() const; |
|
float |
getMaxSpeed() const; |
int |
getDamage() const; |
private: |
|
virtual void |
updateCurrent(sf::Time dt, |
|
CommandQueue& commands); |
virtual void |
drawCurrent(sf::RenderTarget& target, |
|
sf::RenderStates states) const; |
[ 161 ]
www.it-ebooks.info

Warfare Unleashed – Implementing Gameplay
private: |
|
Type |
mType; |
sf::Sprite |
mSprite; |
sf::Vector2f |
mTargetDirection; |
};
Nothing fun or exciting here; we add some new helper functions such as the one to guide the missile towards a target. So let's have a quick look at the implementation. You might notice, we use the same data tables that we used in the Aircraft class to store data.
Projectile::Projectile(Type type, const TextureHolder& textures) : Entity(1)
,mType(type)
,mSprite(textures.get(Table[type].texture))
{
centerOrigin(mSprite);
}
The constructor simply creates a sprite with the texture we want for the projectile. We will check out the guide function when we actually implement the behavior of missiles. The rest of the functions don't hold anything particularly interesting. Draw the sprite and return a category for the commands and other data needed.
To get an overview of the class hierarchy in the scene graph, here is an inheritance diagram of the current scene node types. The data table structures which are directly related to their corresponding entities are shown at the bottom of the following diagram:
SceneNode
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SpriteNode |
|
Entity |
|
TextNode |
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Projectile |
|
Aircraft |
|
Pickup |
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ProjectileData |
|
AircraftData |
|
PickupData |
|||
|
|
|
|
|
|
|
|
[ 162 ]
www.it-ebooks.info

Chapter 7
Firing bullets and missiles
So let's try and shoot some bullets in the game. We start with adding two new actions in the Player class: Fire and LaunchMissile. We define the default key bindings for these to be the Space bar and M keys.
Player::Player()
{
//Set initial key bindings mKeyBinding[sf::Keyboard::Left] = MoveLeft; mKeyBinding[sf::Keyboard::Right] = MoveRight; mKeyBinding[sf::Keyboard::Up] = MoveUp; mKeyBinding[sf::Keyboard::Down] = MoveDown; mKeyBinding[sf::Keyboard::Space] = Fire; mKeyBinding[sf::Keyboard::M] = LaunchMissile;
//...
}
void Player::initializeActions()
{
// ...
mActionBinding[Fire].action = derivedAction<Aircraft>( std::bind(&Aircraft::fire, _1)); mActionBinding[LaunchMissile].action =derivedAction<Aircraft>( std::bind(&Aircraft::launchMissile, _1));
}
So when we press the keys bound to those two actions, a command will be fired which calls the aircraft's fire() and launchMissile() functions. However, we cannot put the actual code that fires the bullet or missile in those two functions. The reason is, because if we could, we would have no concept of how much time has elapsed. We don't want to fire a projectile for every frame. We want there to be some cool down until the next time we fire a bullet, to accomplish that we need to use the delta time passed in the aircraft's update() function.
Instead, we mark what we want to fire by setting the Boolean flags mIsFiring
or mIsLaunchingMissile to true in the Aircraft::fire() and the
Aircraft::launchMissile() functions, respectively. Then we perform the actual logic in the update() function using commands. In order to make the code clearer to read, we have extracted it to its own function.
void Aircraft::checkProjectileLaunch(sf::Time dt, CommandQueue& commands)
{
if (mIsFiring && mFireCountdown <= sf::Time::Zero)
[ 163 ]
www.it-ebooks.info

Warfare Unleashed – Implementing Gameplay
{
commands.push(mFireCommand);
mFireCountdown += sf::seconds(1.f / (mFireRateLevel+1)); mIsFiring = false;
}
else if (mFireCountdown > sf::Time::Zero)
{
mFireCountdown -= dt;
}
if (mIsLaunchingMissile)
{
commands.push(mMissileCommand); mIsLaunchingMissile = false;
}
}
We have a cool down for the bullets. When enough time has elapsed since the last bullet was fired, we can fire another bullet. The actual creation of the bullet is done using a command which we will look at later. After we spawn the bullet, we reset the countdown. Here, we use += instead of =; with a simple assignment, we would discard a little time remainder in each frame, generating a bigger error as time goes by. The time of the countdown is calculated using a member variable
mFireCountdown in Aircraft. Like that, we can improve the aircraft's fire rate easily. So if the fire rate level is one, then we can fire a bullet every half a second, increase it to level two, and we get every third of a second. We also have to remember to keep ticking down the countdown member, even if the user is not trying to fire.
Otherwise, the countdown would get stuck when the user released the Space bar.
Next is the missile launch. We don't need a countdown here, because in the Player class, we made the input an event-based (not real-time based) input.
bool Player::isRealtimeAction(Action action)
{
switch (action)
{
case MoveLeft: case MoveRight: case MoveDown: case MoveUp: case Fire:
return true;
[ 164 ]
www.it-ebooks.info

Chapter 7
default:
return false;
}
}
Since the switch statement does not identify LaunchMissile as a real-time input, the user has to release the M key before he can shoot another missile. The user wants to save his missiles for the moment he needs them.
So, let's look at the commands that we perform, in order to actually shoot the projectiles. We define them in the constructor in order to have access to the texture holder. This shows one of the strengths of lambda expressions in C++11.
Aircraft::Aircraft(Type type, const TextureHolder& textures)
{
mFireCommand.category = Category::SceneAirLayer; mFireCommand.action =
[this, &textures] (SceneNode& node, sf::Time)
{
createBullets(node, textures);
};
mMissileCommand.category = Category::SceneAirLayer; mMissileCommand.action =
[this, &textures] (SceneNode& node, sf::Time)
{
createProjectile(node, Projectile::Missile, 0.f, 0.5f, textures);
};
}
Now, we can pass the texture holder to the projectiles without any extra difficulty, and we don't even have to keep an explicit reference to the resources. This makes the Aircraft class and our code a lot simpler, since the reference does not need to exist in the update() function.
The commands are sent to the air layer in the scene graph. This is the node where we want to create our projectiles. The missile is a bit simpler to create than bullets, that's why we call directly Aircraft::createProjectile(). So how do we create bullets then?
void Aircraft::createBullets(SceneNode& node, const TextureHolder& textures) const
{
Projectile::Type type = isAllied()
? Projectile::AlliedBullet : Projectile::EnemyBullet;
[ 165 ]
www.it-ebooks.info

Warfare Unleashed – Implementing Gameplay
switch (mSpreadLevel)
{
case 1:
createProjectile(node, type, 0.0f, 0.5f, textures); break;
case 2:
createProjectile(node, type, -0.33f, 0.33f, textures); createProjectile(node, type, +0.33f, 0.33f, textures); break;
case 3:
createProjectile(node, type, -0.5f, 0.33f, textures); createProjectile(node, type, 0.0f, 0.5f, textures); createProjectile(node, type, +0.5f, 0.33f, textures); break;
}
}
For projectiles, we provide different levels of fire spread in order to make the game more interesting. The player can feel that progress is made, and that his aircraft becomes more powerful as he is playing. The function calls createProjectile() just as it was done for the missile.
So how do we actually create the projectile and attach it to the scene graph?
void Aircraft::createProjectile(SceneNode& node, Projectile::Type type, float xOffset, float yOffset, const TextureHolder& textures) const
{
std::unique_ptr<Projectile> projectile( new Projectile(type, textures));
sf::Vector2f offset(
xOffset * mSprite.getGlobalBounds().width, yOffset * mSprite.getGlobalBounds().height);
sf::Vector2f velocity(0, projectile->getMaxSpeed());
float sign = isAllied() ? -1.f : +1.f; projectile->setPosition(getWorldPosition() + offset * sign); projectile->setVelocity(velocity * sign); node.attachChild(std::move(projectile));
}
[ 166 ]
www.it-ebooks.info