- •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
Making a Game Tick
Eventually, we have two while loops. The outer one is the game loop as we know it, and calls the render() method. The inner one collects user input, and computes the game logic; this loop is executed at a constant rate. If rendering is slow, it may happen that processEvents() and update() are called multiple times before one render() call. As a result, the game occasionally stutters, since, not every update is rendered, but the game doesn't slow down. On the other hand, fast rendering can lead to render() being called multiple times without a logic update in between. Rendering the same state multiple times does not change anything on the screen, but it allows for techniques such as interpolations between two states to smoothen the game flow.
If you are interested in the topic, there is a famous article with detailed explanations at http://gafferongames.com/game-physics/fix-your-timestep.
Other techniques related to frame rates
SFML provides a few utilities that are worth knowing with respect to time handling and frame updates. One of them is sf::sleep(), a function that interrupts the execution for a given time, which gives the processor an opportunity to work on other tasks. Sleeping is not very accurate, so you should not use it for exact timing purposes. The method sf::RenderWindow::setFramerateLimit() tries to achieve the specified frame rate by calling sf::sleep() internally. It is a nice function for testing purposes, but it also lacks precision.
Another important technique is vertical synchronization, also known as V-Sync. Enabled V-Sync adapts the rate of graphical updates (calls of
sf::RenderWindow::display()) to the refresh rate of the monitor, usually around 60Hz. This can avoid graphical artifacts such as screen tearing, where a part of your window shows the old frame, and another the new one. You can enable or disable V-Sync using the method sf::RenderWindow::setVerticalSyncEnabled().
Displaying sprites on the screen
Now let's get something interesting on the screen. Instead of just rendering a boring single color circle to the screen, let's actually render an image. To do this, SFML provides a couple of tools to make your life easy. First we have the sf::Texture class that holds the actual image data loaded from the hard drive. Next is the sf::Sprite class that represents an instance with position and orientation in the scene. The texture describes the image, while the sprite describes where and how to put one on the screen.
[ 24 ]
www.it-ebooks.info
Chapter 1
A simple example of their relationship is as follows:
sf::Texture texture;
if (!texture.loadFromFile("path/to/file.png"))
{
// Handle loading error
}
sf::Sprite sprite(texture); sprite.setPosition(100.f, 100.f); window.clear(); window.draw(sprite); window.display();
Here, we load a texture from the hard drive, and check if loading has succeeded. We construct a new sprite to use the texture and set its position to (100, 100), relative to the upper-left window corner.
File paths and working directories
About file paths, it would be useful to make some things clear. First, the slashes that separate directories in a path in order to locate a file will be most often forward slashes (/), especially in Unix-like operating systems. In Windows, you will occasionally see back slashes (\) used instead, but probably not everywhere. Do not let this confuse you, the only true cross-platform way to specify a file path is using forward slashes, so make sure they are always your choice, even in Windows.
Also, it is of extreme importance to note that every program being run in the operating systems that SFML supports has a working directory while being executed. Usually, when you run the program from a graphical window manager, the working directory is exactly where the executable is located; however, this is not always the case. It is possible to run a program with any working directory, and it is even possible to change the working directory programmatically from inside the program. In the context of a game, the point of understanding working directories is for loading files, which are loaded in paths relative to the executable in most cases.
All paths passed on to SFML are relative to the working directory, unless you make them absolute by starting them with a slash in Unix-like operating systems, or with a drive letter in Windows.
Real-time rendering
In a game simulation, it is highly likely that there will be changes to what's drawn in every frame. Even when there is no player input, you will observe in most cases some kind of animation, which will effectively change the end result on the screen for that given frame.
[ 25 ]
www.it-ebooks.info
Making a Game Tick
Many programs use a render-on-demand approach. They will only redraw a new frame on the screen when a change actually happens, minimizing the performance costs in applications where the screen doesn't change very often. But, by the nature of gaming software, this approach would be made redundant, as we would be requesting new frames all the time. Because of this, game programmers adopted entirely the concept of real-time rendering, which ignores frame requests as they were previously known, and blindly draws to the screen as fast as possible. If while playing your favorite game you eventually noticed a usual FPS count of 30 or 60, this is due to the fact that our eyes can't perceive, in regular circumstances, more frames than that amount in a second. Because it makes virtually no difference to the end user, the programmers limit the FPS count to such a number, and save the processor power for other tasks, such as logic processing. In short, nowadays, the whole scene is rendered again and again, independently of what changed since the last frame.
To explain the notion of real-time rendering a bit further, we would like to mention the concept of double buffering that comes inherently attached to it. Double buffering is a technique that was created to negate graphical glitches derived from asynchronies in the rendering tasks. Before this technique became widely used, programmers needed to have additional concerns when drawing to the screen, to ensure that only what belongs to a frame is drawn in it, and that there are no remains of pixels from previous frames.
Double buffering defines two virtual screens to draw graphics to. The front buffer and the back buffer are the names chosen to address these render targets. The front buffer is what is currently displayed on the screen, while the back buffer is the one we are drawing to at the moment, preparing a new frame. Once our frame is fully drawn in the back buffer, we use the SFML's sf::RenderWindow::display() function to put the contents of the back buffer on the screen. The back buffer becomes the front buffer, while the front buffer that was set will now be the back buffer to draw on. This way we ensure that we always have a buffer with the previous frame unharmed, as well as a working buffer that we can safely change at any time, without affecting what is on the screen. By clearing, drawing, and then displaying these buffers in an interleaved manner, we achieve double buffering.
[ 26 ]
www.it-ebooks.info
Chapter 1
Adapting the code
In our code, we replace sf::CircleShape with sf::Sprite, which only requires minor modifications. We load an image file called Eagle.png. The relative path to it is Media/Textures. Don't worry if you don't have the file; you can download it together with the whole source code.
// Game.hpp class Game
{
public:
Game();
...
private:
sf::Texture mTexture; sf::Sprite mPlayer;
...
};
// Game.cpp Game::Game() : ...
,mTexture()
,mPlayer()
{
if (!mTexture.loadFromFile("Media/Textures/Eagle.png"))
{
// Handle loading error
}
mPlayer.setTexture(mTexture); mPlayer.setPosition(100.f, 100.f);
}
No code changes have to be done to get our render() function to work with sprites instead of shapes. SFML is nice in that way. Everything that can be drawn to a window has to inherit from the abstract class sf::Drawable. As long as it is possible, the SFML API keeps a consistency to it. This makes it a lot easier to use, because if you have used a function on one class, you can be pretty sure there will be identical or similar functions in other classes.
Now you have achieved the results you observed in the screenshot we had in the beginning of the chapter. You are also very well on the way to making your game. With these few tools alone you would be able to make a simple game, such
as Snake or Pac-Man with ease.
[ 27 ]
www.it-ebooks.info