- •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 1
Frame-independent movement
If you run everything we have done so far, you will be able to move the circle, but it won't move uniformly. It will probably be very fast, because currently we have done the movement in a very naive way. Right now your computer will be running the update() function as fast as it can, which means it will probably call it a couple of hundreds of times each second, if not more. If we move the shape by one pixel for every frame, this can count up to several 100 pixels every second, making our little player fly all over the screen. You cannot just change the movement value to something lower, as it will only fix the problem for your computer. If you move to a slower or faster computer, the speed will change again.
So how do we solve this? Well, let's look at the problem we are facing. We are having a problem because our movement is frame-dependent. We want to provide the speed in a way that changes depending on the time a frame takes. There is a simple formula you should remember from your old school days. It's the formula that goes: distance = speed * time. Now why is this relevant for us? Because with this formula we can calculate a relevant speed for every frame, so that the circle always travels exactly the distance we want it to travel over one second, no matter what computer we are sitting on. So let's modify the function to what we actually need to make this work.
void Game::update(sf::Time deltaTime)
{
sf::Vector2f movement(0.f, 0.f); if (mIsMovingUp)
movement.y -= PlayerSpeed; if (mIsMovingDown)
movement.y += PlayerSpeed; if (mIsMovingLeft)
movement.x -= PlayerSpeed; if (mIsMovingRight)
movement.x += PlayerSpeed;
mPlayer.move(movement * deltaTime.asSeconds());
}
The major difference we have made here is that we now receive a time value every time we call the update. We calculate the distance we want to travel every frame, depending on how much time has elapsed. We call the time that has elapsed since the last frame delta time (or time step), and often abbreviate it as dt in the code. But how do we get this time? We are lucky because SFML provides the utilities for it.
[ 21 ]
www.it-ebooks.info
Making a Game Tick
In SFML, there is a class that measures the time from when it was started. What we have to do is to measure the time each frame takes. We are talking about the class sf::Clock. It has a function called restart(), which lets the clock return the elapsed time since its start, and restarts the clock from zero, making it ideal for our current situation. SFML uses the class sf::Time for all time formats; it is a convenient data type that can be converted from and to seconds, milliseconds, and microseconds. Here's the modified Game::run() member function:
void Game::run()
{
sf::Clock clock;
while (mWindow.isOpen())
{
sf::Time deltaTime = clock.restart(); processEvents();
update(deltaTime);
render();
}
}
There is no big difference; we create a clock, and in every frame we query it for its current elapsed time, restart the clock, and then pass this time to the update function.
Fixed time steps
The solution we have come up with so far is sufficient for many cases. But it is not a perfect solution, because you can have problems in certain scenarios where delta times vary strongly. The code can be quite hard to debug, because it is impossible to get 100 percent reproducible results, since every frame is unique, and you can't guarantee that the delta time remains the same.
Consider that a frame may sometimes take three times the average delta time. This can lead to severe mistakes in the game logic, for example, when a player moves three times the distance and passes through a wall he would normally collide with.
This is why physics engines expect the delta time to be fixed.
[ 22 ]
www.it-ebooks.info
Chapter 1
The following is a figure describing the problem we are referring to:
Normal |
|
|
Lag |
|
|
|
|||
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
What we will do now is use a technique called fixed time steps. We write code that guarantees that in any circumstances, we always give the same delta time to the update function, no matter what happens. If you find that sounding difficult, there is no big difference from what we already have. We just have to do some book-keeping in our code for how much time has passed since we last called the update() function.
void Game::run()
{
sf::Clock clock;
sf::Time timeSinceLastUpdate = sf::Time::Zero; while (mWindow.isOpen())
{
processEvents();
timeSinceLastUpdate += clock.restart(); while (timeSinceLastUpdate > TimePerFrame)
{
timeSinceLastUpdate -= TimePerFrame; processEvents(); update(TimePerFrame);
}
render();
}
}
The actual effect of this change is that we accumulate how much time has elapsed in a variable timeSinceLastUpdate. When we are over the required amount for one frame, we subtract the desired length of this frame (namely TimePerFrame), and update the game. We do this until we are below the required amount again. This solves the problem with variable delta times, as we are guaranteed that the same amount of frames is always run. In the application you can download, the logic frame rate will be set to 60 frames per second by having the TimePerFrame constant
equal to sf::seconds(1.f / 60.f).
[ 23 ]
www.it-ebooks.info