- •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
Forge of the Gods – Shaping Our World
Scene layers
In the game, we often have different scene nodes that must be rendered in a certain order. Nodes with objects that are located "above" others (closer to the sky) must be drawn after them. For example, we might first draw a desert background, then an oasis and some buildings, above which we draw the planes, and eventually some health bars located in front of them. This is rather cumbersome to handle when we insert node by node to the scene graph, because we have to ensure the order manually.
Luckily, we can easily automate the ordering, even using the scene graph's current capabilities. We call a group of scene nodes that are rendered together a layer. Inside a layer, the rendering order is irrelevant—we just make sure we render the different layers in the right order. We represent a layer with an empty scene node, directly under the graph's root node. A layer node itself contains no graphics; it is only supposed to render its children. Since the scene graph is traversed node by node, we know that all children of layer one will be rendered before any children of layer two. We can assign a node to a certain layer by attaching it as a child to the corresponding layer node. As a result, we have an automatic ordering of different layers, without the need to manually sort objects.
Let us introduce two layers for now: one for the background, and one for entities in the air. We use an enum to have an appropriate type. The last enumerator
LayerCount is not used to refer to a layer; instead it stores the total amount of layers.
enum Layer
{
Background,
Air, LayerCount
};
Updating the scene
In each frame, we update our world with all the entities inside. During an update, the whole game logic is computed: entities move and interact with each other, collisions are checked, and missiles are launched. Updating changes the state of our world and makes it progress over time, while rendering can be imagined as a snapshot of a part of the world at a given time point.
[ 64 ]
www.it-ebooks.info
Chapter 3
We can reuse our scene graph to reach all entities with our world updates. To achieve this, we implement a public update() member function in the SceneNode class. Analogous to the way we have proceeded for the draw() function, we split up update() into two parts: an update for the current node, and one for the child nodes. We thus write two private methods updateCurrent() and updateChildren(), of which the former is virtual.
All update functions take the frame time dt as a parameter of type sf::Time (the SFML class for time spans). This is the frame time we computed for the Game class in Chapter 1, Making a Game Tick. In our game, we work with fixed time steps, so dt will be constant. Nevertheless, we pass the frame time every time to the scene nodes and make entity behavior dependent on it. This leaves us the flexibility to change the frame rate or to experiment with other approaches to compute the frame time.
We add the following methods to our SceneNode class:
public: |
|
void |
update(sf::Time dt); |
private: |
|
virtual void |
updateCurrent(sf::Time dt); |
void |
updateChildren(sf::Time dt); |
The implementation is the same as for rendering. The definition of updateCurrent() remains empty, by default we do nothing for a scene node.
void SceneNode::update(sf::Time dt)
{
updateCurrent(dt);
updateChildren(dt);
}
void SceneNode::updateCurrent(sf::Time)
{
}
void SceneNode::updateChildren(sf::Time dt)
{
FOREACH(Ptr& child, mChildren) child->update(dt);
}
[ 65 ]
www.it-ebooks.info
Forge of the Gods – Shaping Our World
In derived classes, we can now implement specific update functionality, such as movement of each entity. In the Entity class, we override the virtual
updateCurrent() method, in order to apply the current velocity. The class definition of Entity is expanded by the following lines of code:
private: |
|
virtual void |
updateCurrent(sf::Time dt); |
We offset the position by the velocity depending on the time step. A longer time step leads to a bigger offset, meaning that our entity is moved further over longer time.
void Entity::updateCurrent(sf::Time dt)
{
move(mVelocity * dt.asSeconds());
}
Here, move() is a function of the indirect base class sf::Transformable. The expression move(offset) is a shortcut for setPosition(getPosition() + offset).
Since Aircraft inherits Entity, the update functionality for it is also inherited. We thus do not need to re-define updateCurrent() in the Aircraft class, unless we want to execute further actions specifically for aircraft.
One step back – absolute transforms
Relative coordinates are nice and useful, but there are cases where we still want to access the absolute position of an object in the world. For example, to find out whether two entities collide, relative positions won't help us—we need to know
where in the world the entities are located, not where in the local coordinate system.
To compute the absolute transforms, we can step upwards in the class hierarchy, and accumulate all relative transforms until we reach the root. This was also the reason why we introduced the parent pointer in the SceneNode class. In addition to the function getPosition(), which is inherited from sf::Transformable and returns the relative position, we add a new method getWorldPosition() to SceneNode, which returns the absolute position. First, we add a function getWorldTransform() that takes into account all the parent transforms. It multiplies all the sf::Transform objects from the root to the current node, its iterative implementation looks as follows. The position can be computed by transforming the origin sf::Vector2f() using the absolute transform.
sf::Transform SceneNode::getWorldTransform() const
{
sf::Transform transform = sf::Transform::Identity;
[ 66 ]
www.it-ebooks.info