- •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
Waiting and Maintenance Area – Menus
Updating the menu
So, let's see our code in actual use. Do you remember MenuState from the previous chapter? There we implemented some menu logic, so you could choose between two options. Now this can be cleaned up a lot; as a result the menu state shrinks drastically.
MenuState::MenuState(StateStack& stack, Context context) : State(stack, context)
, mGUIContainer()
{
...
auto playButton = std::make_shared<GUI::Button>( *context.fonts, *context.textures);
playButton->setPosition(100, 250); playButton->setText("Play"); playButton->setCallback([this] ()
{
requestStackPop();
requestStackPush(States::Game);
});
mGUIContainer.pack(playButton);
}
The constructor initializes the buttons and packs them into the Container. As you can see, the lambda expression we give to the Button is the place where we actually describe the action we want the button to do. The rest of the functions are changed to use the GUI container to render and handle events.
void MenuState::draw()
{
sf::RenderWindow& window = *getContext().window; window.setView(window.getDefaultView()); window.draw(mBackgroundSprite); window.draw(mGUIContainer);
}
bool MenuState::update(sf::Time)
{
return true;
}
bool MenuState::handleEvent(const sf::Event& event)
{
mGUIContainer.handleEvent(event); return false;
}
[ 146 ]
www.it-ebooks.info
Chapter 6
This gives us much cleaner code than what we had in the previous chapter. Now we also don't have any risk of code duplication if we would like to have a menu in another place, such as the pause state. The pause menu looks very similar to this code, so we won't go through that as well.
The promised key bindings
We implement a new state; the SettingsState which you access through a button on the menu. What is the purpose for the SettingsState in our example? Currentlywe have the key bindings inside there. Everything we need has already been implemented with the GUI, so we only have to define the actual state.
The constructor of the SettingsState class one is pretty big so we snip it.
SettingsState::SettingsState(StateStack& stack, Context context) : State(stack, context)
, mGUIContainer()
{
mBackgroundSprite.setTexture( context.textures->get(Textures::TitleScreen));
mBindingButtons[Player::MoveLeft] = std::make_shared<GUI::Button>(...);
mBindingLabels[Player::MoveLeft] = std::make_shared<GUI::Label>(...);
... // More buttons and labels updateLabels();
auto backButton = std::make_shared<GUI::Button>(...); backButton->setPosition(100, 375); backButton->setText("Back"); backButton->setCallback([this] ()
{
requestStackPop();
});
mGUIContainer.pack(mBindingButtons[Player::MoveLeft]);
mGUIContainer.pack(mBindingLabels[Player::MoveLeft]);
...
mGUIContainer.pack(backButton);
}
[ 147 ]
www.it-ebooks.info
Waiting and Maintenance Area – Menus
We have put all the binding buttons and the associated labels in a static array, and associate to them the action they bind for. This means we don't have to duplicate a lot of callbacks, which you may have notice are missing. Instead, we have marked the button as one that toggles. We also have an updateLabels() function call. It's a helper function to make sure the labels are writing out the correct name for the key.
void SettingsState::updateLabels()
{
Player& player = *getContext().player;
for (std::size_t i = 0; i < Player::ActionCount; ++i)
{
sf::Keyboard::Key key = player.getAssignedKey(static_cast<Player::Action>(i));
mBindingLabels[i]->setText(toString(key));
}
}
Thanks to having it in an array, it's easy to loop through the labels and update them. Now moving on to the handleEvent() function, where we actually do something with the buttons.
bool SettingsState::handleEvent(const sf::Event& event)
{
bool isKeyBinding = false;
for (std::size_t action = 0; action < Player::ActionCount; ++action)
{
if (mBindingButtons[action]->isActive())
{
isKeyBinding = true;
if (event.type == sf::Event::KeyReleased)
{
getContext().player->assignKey (static_cast<Player::Action>(action), event.key.code);
mBindingButtons[action]->deactivate();
}
break;
}
}
if (isKeyBinding) updateLabels();
else
mGUIContainer.handleEvent(event); return false;
}
[ 148 ]
www.it-ebooks.info
Chapter 6
So how do we do this? If a button is pressed, then it will be activated, and since it's a button that toggles, it will stay active until we tell it to deactivate. So we loop through the array of buttons and check if anyone of them is active. If they are, then we are currently binding a key. When we get a key released event, then we change that binding on the player to the new key with the specified action before we deactivate the button again. After the loop, we update the labels, such that they have the correct name for everything. If no button is active, then we are not currently trying to bind a key, and should pass the event to the GUI container instead.
When we are done, the result should look like, as shown in the following screenshot:
[ 149 ]
www.it-ebooks.info