- •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
We create the projectile with an offset from the player and a velocity required by the projectile type. Also, depending on if this projectile is shot by an enemy or the player, we will have different directions. We do not want the enemy bullets to go upwards like the player's bullets or the other way around.
Implementing gunfire for enemies is now a tiny step; instead of calling fire() when keys are pressed, we just call it always. We do this by adding the following code to the beginning of the checkProjectileLaunch() function:
if (!isAllied()) fire();
Now we have bullets that fly and split the sky.
Homing missiles
What would a modern aircraft be if it hadn't got an arsenal of homing missiles?
This is where we start to add intelligence to our missiles; they should be capable of seeking enemies autonomously.
Let's first look at what we need to implement on the projectile site. For homing missiles, the functions guideTowards() and isGuided(), as well as the variable mTargetDirection are important. Their implementation looks as follows:
bool Projectile::isGuided() const
{
return mType == Missile;
}
void Projectile::guideTowards(sf::Vector2f position)
{
assert(isGuided());
mTargetDirection = unitVector(position - getWorldPosition());
}
The function unitVector() is a helper we have written. It divides a vector by its length, thus, always returns a vector of length one. The target direction is therefore a unit vector headed towards the target.
In the function updateCurrent(), we steer our missile. We change the current missile's velocity by adding small contributions of the target direction vector to it. By doing so, the velocity vector continuously approaches the target direction, having the effect that the missile flies along a curve towards the target.
[ 167 ]
www.it-ebooks.info
Warfare Unleashed – Implementing Gameplay
approachRate is a constant that determines, to what extent the target direction contributes to the velocity. newVelocity, which is the weighted sum of the two vectors, is scaled to the maximum speed of the missile. It is assigned to the missile's velocity, and its angle is assigned to the missile's rotation. We use +90 here, because the missile texture points upwards (instead of right).
void Projectile::updateCurrent(sf::Time dt, CommandQueue& commands)
{
if (isGuided())
{
const float approachRate = 200.f;
sf::Vector2f newVelocity = unitVector(approachRate
* dt.asSeconds() * mTargetDirection + getVelocity());
newVelocity *= getMaxSpeed();
float angle = std::atan2(newVelocity.y, newVelocity.x);
setRotation(toDegree(angle) + 90.f); setVelocity(newVelocity);
}
Entity::updateCurrent(dt, commands);
}
Note that there are many possibilities to guide a missile. Steering behaviors define a whole field of AI; they incorporate advanced mechanisms such as evasion, interception, and group behavior.
Don't hesitate to search on the internet if you're interested.
Now, we have guided the missile to a certain position, but how to retrieve that position?
We want our missile to pursuit the closest enemy. For this, we switch from Projectile to the World class, where we write a new function. First, we store all currently active (that is, already spawned and not yet destroyed) enemies in the member variable mActiveEnemies. With the command facility, this task is almost trivial:
void World::guideMissiles()
{
Command enemyCollector;
enemyCollector.category = Category::EnemyAircraft; enemyCollector.action = derivedAction<Aircraft>( [this] (Aircraft& enemy, sf::Time)
[ 168 ]
www.it-ebooks.info
Chapter 7
{
if (!enemy.isDestroyed()) mActiveEnemies.push_back(&enemy);
});
Next, we have to find the nearest enemy for each missile. We set up another command, now for projectiles, that iterates through the active enemies to find the closest one. Here, distance() is a helper function that returns the distance between the centers of two scene nodes.
Command missileGuider;
missileGuider.category = Category::AlliedProjectile; missileGuider.action = derivedAction<Projectile>( [this] (Projectile& missile, sf::Time)
{
// Ignore unguided bullets if (!missile.isGuided())
return;
float minDistance = std::numeric_limits<float>::max(); Aircraft* closestEnemy = nullptr;
FOREACH(Aircraft* enemy, mActiveEnemies)
{
float enemyDistance = distance(missile, *enemy);
if (enemyDistance < minDistance)
{
closestEnemy = enemy; minDistance = enemyDistance;
}
}
In case we found a closest enemy, we let the missile chase it.
if (closestEnemy) missile.guideTowards( closestEnemy->getWorldPosition());
});
After defining the second command, we push both to our queue, and reset the container of active enemies. Remember that the commands are not yet executed, they wait in the queue until they are invoked on the scene graph in World::update().
mCommandQueue.push(enemyCollector);
mCommandQueue.push(missileGuider);
[ 169 ]
www.it-ebooks.info