- •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
Company Atop the Clouds – Co-op Multiplayer
The new Player class
The Player class needed to be reworked quite a bit in order to support a multiplayer mode. Players are not hardcoded anymore in a state context scope, but rather, there is one player for each human-controlled aircraft in the world.
Every player is now identified by the same identifier that classifies one aircraft, so they can be paired up fast. Also, the constructor of Player now looks like the following:
Player(sf::TcpSocket* socket, sf::Int32 identifier, const KeyBinding* binding);
We pass on a socket instance or a nullptr, defining whether the Player class is being used in a networked or a single-player game. This socket, if passed, is valid for sending data to the server, which we will do next!
The identifier is exactly what you'd expect, the same that maps to an aircraft too.
Finally, we also have KeyBinding being passed here. We will be passing it three different things: The defined keys for the player 1, player 2, and nullptr in case this Player instance does not receive local input, but rather is controlled by the server!
As for event and input handling by the Player class, it now works a little differently too.
Now, the real-time input is only delivered to the local player aircraft—the ones with actual human players controlling them. In consequence, each client has total control over its planes along with immediate responsiveness and smoothness. At the same time, that input is sent to the server, so every client is aware of that movement.
Latency
Programming and maintaining efficient server software is already a very hard task; however, to add even more complexity to this duty, we must deal with latency too. This topic is very broad but we will still try to give you some starting tips on how to deal with these issues.
Roughly, latency is the delay a network packet takes to reach its destination. The bigger the latency, the more we get behind in the networked simulation, and in consequence, the gameplay gets worse.
[ 264 ]
www.it-ebooks.info
Chapter 10
This little nightmare is one of the hardest troubles to deal with in network programming. It will make players have a different experience based on their connection and other network conditions, that is, it will make the game very smooth for some players while completely unplayable for others; it will be a mess. Unfortunately, it is not in the hands of the programmer to deal with the network's latency at all. A programmer can at best prepare the software to behave a little better in the worst case scenarios, where the latency is high. This is usually a very hard task to get right and is one of the main reasons game development companies need specialized and experienced network programmers to achieve a good simulation for all players, independently of how bad their connection is, within reasonable limits. Latency becomes a more and more determining factor as the geographical distance between peers increases. Already the speed-of-light delay between different continents of the Earth amounts to a fraction of a second, router logic on the way through the Internet may add even more. Until a peer receives an answer, data must be sent to the other peers and back; thus, we have the delay of both ways (also called round-trip time). Therefore, physics significantly limits the way how multiplayer games can be played across large distances.
Latency versus bandwidth
Do not confuse latency with bandwidth: The former denotes the time delay, while the latter denotes the capacity of the link. If you imagine a link as a pipe, the latency is related to its length, and thus to the time the water requires to flow through it. Bandwidth however is determined by the cross-section of the pipe; it specifies how much water can flow through it in a certain amount of time.
You cannot make a single bit arrive faster by increasing the bandwidth. What you can do however is to send many bits in parallel, so that a bigger chunk of data still needs less time to be transmitted. The bandwidth determines how much data you can send in a certain amount of time.
View scrolling compensation
The view is now simulated both in the client and the server and updated at the same speed in both of them. However, since the updates are happening in different threads or even different machines, some discrepancies may occur occasionally. Also, when a new player joins the game, he has to be informed of how far the view currently is, so it can keep up with it. In order to keep the view synchronized between all clients and the server, the server will send the view's position in every tick and the clients will employ a little trick to smoothly resynchronize the view.
[ 265 ]
www.it-ebooks.info
Company Atop the Clouds – Co-op Multiplayer
The trick is simple. When the view is scrolled, we multiply the scroll offset with a compensation factor:
mWorldView.move(0.f, mScrollSpeed * dt.asSeconds() * mScrollSpeedCompensation);
Now we just need to ensure that the factor is 1 when the views are in sync, and vary it, so the view scrolls faster or slower depending on how distanced it is from the server's view position. We do this by dividing both positions whenever we get the update:
mWorld.setWorldScrollCompensation(currentViewPosition / currentWorldPosition);
This will keep the view synchronized while never losing smoothness, unless something is very wrong and is too far behind, which should never happen in normal circumstances.
Aircraft interpolation
After the compensation technique, we decided to implement another little trick for the aircraft synchronization. Again, this is a simple way to achieve synchronization and it doesn't give the smoothest results most often. We still wrote it so you could face different algorithms and techniques and hopefully learn from them.
Remember, that each client commands its own planes and just informs the server of what is happening locally. With this information, the server keeps track of where every aircraft that is located. In every tick, it sends that information to its peers so they can synchronize with the true simulation data.
The planes would move quite accurately anyway with the real-time input information that comes from the server, but still some desynchronization could happen eventually. Because of this, when we get the real position of the aircraft in every tick, we move our aircraft slowly into that position, so, at best, after some ticks the aircraft are completely synchronized:
if (aircraft && !isLocalPlane)
{
sf::Vector2f interpolatedPosition = aircraft->getPosition() + (aircraftPosition - aircraft->getPosition()) * 0.1f; aircraft->setPosition(interpolatedPosition);
}
[ 266 ]
www.it-ebooks.info