- •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
As a practical case, we can picture a texture that is only a square, such as a floor's tile. Using this texture with a sf::Sprite object would normally
allow us to see only one tile, no matter how big dimensions we have set with sf::Sprite::setTextureRect(). However, as soon as the texture activates its repeating mode, sf::Sprite would now render a nice tiled floor, without any extra effort!
Composing our world
Up to now, we have taken a look at entities and the scene graph, we know how to render and update objects in the world, and we have seen how views and scrolling work. We have a concrete knowledge about many building blocks, now it is time to assemble them to shape a model of our fictional world.
Completely unforeseen, we create a new class called World. On one side, our World class must contain all the data related to rendering:
•A reference to the render window
•The world's current view
•A texture holder with all the textures needed inside the world
•The scene graph
•Some pointers to access the scene graph's layer nodes
On the other hand, we store some logical data:
•The bounding rectangle of the world, storing its dimensions
•The position where the player's plane appears in the beginning
•The speed with which the world is scrolled
•A pointer to the player's aircraft
Concerning functionality, we implement public functions to update and draw the world. We also add two private functions to load the texture and to build up the scene. Since we only have one world and we don't want it to be copied, the class derives privately from sf::NonCopyable.
class World : private sf::NonCopyable
{
public: |
|
|
|
explicit |
World(sf::RenderWindow& window); |
||
void |
update(sf::Time dt); |
||
void |
draw(); |
||
|
|
|
|
|
|
[ 74 ] |
|
|
|
|
www.it-ebooks.info
Chapter 3
private: |
|
|
void |
loadTextures(); |
|
void |
buildScene(); |
|
private: |
|
|
enum Layer |
|
|
{ |
|
|
Background, |
|
|
Air, |
|
|
LayerCount |
|
|
}; |
|
|
private: |
|
|
sf::RenderWindow& |
mWindow; |
|
sf::View |
|
mWorldView; |
TextureHolder |
|
mTextures; |
SceneNode |
|
mSceneGraph; |
std::array<SceneNode*, LayerCount> |
mSceneLayers; |
|
sf::FloatRect |
|
mWorldBounds; |
sf::Vector2f |
|
mSpawnPosition; |
float |
|
mScrollSpeed; |
Aircraft* |
|
mPlayerAircraft; |
};
For the scene layers, we use an array of pointers with the size LayerCount.
std::array is a C++11 class template for fixed-size static arrays. It offers the same functionality and performance as C arrays, but has many advantages. It provides value semantics, which allow copies, assignments, and passing or returning objects from functions. There is no implicit conversion to pointers, and index access is checked in debug mode. Additionally, an STL-conforming interface with useful methods such as size(), begin(), or end() is provided. Because of the additional safety and features, std::array should always be preferred over C arrays.
[ 75 ]
www.it-ebooks.info
Forge of the Gods – Shaping Our World
World initialization
In the constructor, we build up the world. The following figure can help to imagine the dimensions of our world:
mWorldBounds.top
World bounds
View after some time
Initial view
mSpawnPosition.y
+
Spawn position |
mWorldBounds.top |
|
|
|
+mWorldBounds.height |
mWorldBounds.left mWorldBounds.left mSpawnPosition.x +mWorldBounds.width
The constructor initializes the important attributes. The mWorldBounds rectangle is initialized so that the upper-left corner lies at position (0, 0). Its width equals the window's width, and for its height we take an arbitrary value, here 2000. This is rather small value, but it shows already after a short time that the desert texture ceases to repeat any longer, leaving behind a black background.
The mSpawnPosition vector is initialized depending on the world bounds and the window. According to the previous figure, the vector's x coordinate is assigned the middle of the screen, and its y coordinate is the same as the bottom of the world minus a half screen height.
[ 76 ]
www.it-ebooks.info
Chapter 3
: mWindow(window) |
|
, mWorldView(window.getDefaultView()) |
|
, mWorldBounds( |
|
0.f, |
// left X position |
0.f, |
// top Y position |
mWorldView.getSize().x, |
// width |
2000.f) |
// height |
, mSpawnPosition( |
|
mWorldView.getSize().x / 2.f, |
// X |
mWorldBounds.height - mWorldView.getSize() |
|
, mPlayerAircraft(nullptr) |
// Y |
{ |
|
loadTextures(); |
|
buildScene(); |
|
mWorldView.setCenter(mSpawnPosition);
}
For the scroll speed, we choose a negative value, since we scroll upwards and the y axis points downwards. The aircraft pointer is initialized with a null pointer literal. In the constructor body, we call our two private functions that are in charge of further initialization. Finally, we move the view to the correct start position. As you see in the figure, its center initially matches the player's spawn position.
Loading the textures
Now, let's have a look at texture loading. Thanks to our ResourceHolder class, this part could not be simpler:
void World::loadTextures()
{
mTextures.load(Textures::Eagle, "Media/Textures/Eagle.png"); mTextures.load(Textures::Raptor, "Media/Textures/Raptor.png"); mTextures.load(Textures::Desert, "Media/Textures/Desert.png");
}
We do not handle exceptions here, since the World class cannot react to them. Without the textures, we are not able to construct our world meaningfully, thus it is reasonable that we let possible exceptions abort the constructor.
[ 77 ]
www.it-ebooks.info