- •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
Diverting the Game Flow – State Stack
Summary
Here we conclude the fifth chapter of the book. Through its pages, we tried to pass on a lot of useful information about state managing and game flow. We talked about states and stacks of states, both in concept and in implementation. Also, we saw how to implement a handful of screens that we usually see in games, using our StateStack system to our advantage. Navigation between states was also covered and we even talked about functionality we don't use in the game, but that will certainly come handy in the future! Better to know and not need it, than to need it and not know about it!
As you noticed, this chapter is of extreme importance not only to this game, but every game you might make. All the code written from this chapter and on will be compliant with the state architecture which makes it a good reason to grasp these concepts the best you can!
The next chapter will concern the graphical user interface of the game. We will discuss how to implement a basic version of a widget hierarchy containing buttons and labels.
[ 136 ]
www.it-ebooks.info
Waiting and Maintenance
Area – Menus
Most games have menus and it's something the player expects when opening up a new game. Even in its simplest form, there is a user interface that responds to the user, and gives him the information he needs to enjoy the game. You might have noticed that we implemented a simple menu in the previous chapter, but it's a prime example where you should refactor and extract into its own class. This is what we will do in this chapter:
•Design a user interface components hierarchy
•Implement the base component class
•Implement containers, labels, and buttons
•Create a proper title screen
•Create a settings screen
What we aim in this chapter is to give you a fundamental understanding of creating a graphical user interface (GUI). GUI design is a huge topic that deserves its own book, but we will do a crash course together.
Normally, in a GUI you use the mouse as an input source; you click on a button and something happens. But for our examples we keep it simple by having you navigate by the keyboard. The difference lies in complexity of the methods: one does not exclude the other. But the focus of this chapter is the components of the hierarchy.
www.it-ebooks.info
Waiting and Maintenance Area – Menus
The GUI hierarchy, the Java way
The architecture for the GUI framework will resemble a lot of other toolkits such as Java's Swing/AWT library as it's a well working concept. Note that it is not exactly reproduced, rather is used as a source of inspiration. In the end, what we aim to achieve is a working menu state based on this design, but without pumping the state full of boilerplate GUI code.
We create a namespace GUI in order to make the distinction clear to other parts of our game, since a lot of the names such as "component" are generic, and can be
misinterpreted easily. We start with one core base class, which the entire hierarchy rests on. We call it Component and in our case it is quite small. It defines the interface that we will be using regularly besides setting up the objects. The class defines a couple of virtual functions, one of which is the handleEvent() function. We let the Component class inherit from sf::Drawable for the same reason as the scene nodes. To have an interface for drawing to an SFML window:
[ 138 ]
www.it-ebooks.info
Chapter 6
namespace GUI
{
class Component : public sf::Drawable
,public sf::Transformable
,private sf::NonCopyable
{
public:
typedef std::shared_ptr<Component> Ptr;
public: |
|
|
Component(); |
virtual |
~Component(); |
virtual bool |
isSelectable() const = 0; |
bool |
isSelected() const; |
virtual void |
select(); |
virtual void |
deselect(); |
virtual bool |
isActive() const; |
virtual void |
activate(); |
virtual void |
deactivate(); |
virtual void |
handleEvent(const sf::Event& event) = 0; |
private: |
|
bool |
mIsSelected; |
bool |
mIsActive; |
};
}
You might be wondering about the pure virtual isSelectable() function and the other virtual ones. They exist for buttons and containers. For the moment, you can just assume that isSelectable() returns false, and that the virtual ones are based on the two variables with similar names. The core for the GUI is the Component::handleEvent() function because this is where the magic happens. The typedef of a shared pointer of Component to the name Component::Ptr is for convenience purposes. You don't need it, but it makes code more readable, by simplifying the name we use.
[ 139 ]
www.it-ebooks.info
Waiting and Maintenance Area – Menus
The class template std::shared_ptr is a C++11 smart pointer. In contrast to std::unique_ptr, multiple pointers share an object. Reference counting ensures that the object remains alive as long as any shared_ptr points to it. If an object is not referenced anymore, it will be destroyed.
While unique pointers result in zero performance and memory overhead, shared pointers are rather expensive because of reference counting semantics and thread safety. Therefore, they should be used with care— shared ownership is rarely required.
The function template std::make_shared() allows construction of shared pointer objects. Instead of the first statement with the new
operator, you can use the second. This enables an internal optimization (object and reference counter are stored together).
std::shared_ptr<T> s(new T(a, b)); auto s = std::make_shared<T>(a, b);
The reason for using a shared pointer, instead of the unique pointer we have been so diligent in using before, is because it is more flexible. We want to give you a bare-bones setup for developing GUI on which you can extend upon. On one hand, std::shared_ptr allows users of GUI components to hold a std::weak_ptr which becomes invalid as soon as the Component is destroyed. But more importantly, a component can be shared in different places. If two different containers (maybe
in different application states) use exactly the same component with the same attributes, they can share it.
The function for handling events is virtual because its implementation is different for every class inheriting from Component. Further classes we define are GUI::Container, GUI::Button, and GUI::Label. These are the most basic
components that you will need, and it will be easy to expand the system with more components later.
So we start with containers. What is the purpose of a container? Well, obviously to bind other components together logically. But remember, since we are using a shared pointer, it doesn't mean that the container owns the components. It is also responsible for selecting one of the components inside its list, in order to highlight
it. This is to know when you try to activate a button, which button should it activate. So let's look at the implementation of the Container class. We start with the public interface which consists of the following functions:
Container::Container() : mChildren()
, mSelectedChild(-1)
{
}
[ 140 ]
www.it-ebooks.info
Chapter 6
void Container::pack(Component::Ptr component)
{
mChildren.push_back(component);
if (!hasSelection() && component->isSelectable()) select(mChildren.size() - 1);
}
bool Container::isSelectable() const
{
return false;
}
Nothing is really exciting here. We have our list with children, and we can pack a new component into this list. The only special thing is that we check if we have a currently selected child. If not, we check if the incoming child is selectable, and if it is, we select it. Lastly, a container is not a selectable component.
Now to the exciting part:
void Container::handleEvent(const sf::Event& event)
{
if (hasSelection() && mChildren[mSelectedChild]->isActive())
{
mChildren[mSelectedChild]->handleEvent(event);
}
else if (event.type == sf::Event::KeyReleased)
{
if (event.key.code == sf::Keyboard::W || event.key.code == sf::Keyboard::Up)
{
selectPrevious();
}
else if (event.key.code == sf::Keyboard::S
|| event.key.code == sf::Keyboard::Down)
{
selectNext();
}
else if (event.key.code == sf::Keyboard::Return || event.key.code == sf::Keyboard::Space)
{
if (hasSelection()) mChildren[mSelectedChild]->activate();
}
}
}
[ 141 ]
www.it-ebooks.info
Waiting and Maintenance Area – Menus
Now this is, as said before, where the magic happens. Depending on the current state of the container and what input we get from the events, the action that is performed is different. Let's go through the function.
First we check if we have a valid selection through the helper function hasSelection(), and whether the component is active. All the helper function does is; check if the mSelectedChild variable is a valid index, zero, or more. If both conditions are true, then the active component is the one that should receive the events instead of the container managing it. This gives a possibility to have a composite hierarchy in the GUI. In our simple example, this feature is not used, but it would be extremely useful when you implement an input box, or any kind of component that needs to capture input.
So, what if the container is the one in focus for the input? Then we provide ways for the user to simply navigate the container and activate any selected component. In order to make the code easier to read, we use helper functions instead of having a large function with a lot of logic in it.
void Container::select(std::size_t index)
{
if (mChildren[index]->isSelectable())
{
if (hasSelection()) mChildren[mSelectedChild]->deselect();
mChildren[index]->select(); mSelectedChild = index;
}
}
void Container::selectNext()
{
if (!hasSelection()) return;
//Search next component that is selectable int next = mSelectedChild;
do
next = (next + 1) % mChildren.size(); while (!mChildren[next]->isSelectable());
//Select that component
select(next);
}
void Container::selectPrevious()
{
if (!hasSelection()) return;
[ 142 ]
www.it-ebooks.info
Chapter 6
//Search previous component that is selectable int prev = mSelectedChild;
do
prev = (prev + mChildren.size() - 1) % mChildren.size(); while (!mChildren[prev]->isSelectable());
//Select that component
select(prev);
}
The helper functions don't have too much logic in it, and thus aren't so complex. The first one takes care of the book-keeping needed with the selection, to make sure only one component is marked as selected. The selectPrevious() and selectNext() functions only implement the stepping to find the next selectable component and the looping of the menu selection.
The Container acts as the root for the GUI we want to show. You create a GUI object at the top in your state, and then you pack other components into it. Here is a demonstration using labels:
auto demoLabel = std::make_shared<GUI::Label>(
"This is a demonstration!", *getContext().fonts); mGUIContainer.pack(demoLabel);
// Later in code...
mWindow.draw(mGUIContainer);
while(mWindow.pollEvent(event))
mGUIContainer.handleEvent(event);
You see in the states code how this simplifies a lot of problems, and makes the code a lot cleaner and nicer to read. Label is a very small class, as all it does is simply show some text on the screen.
Label::Label(const std::string& text, const FontHolder& fonts) : mText(text, fonts.get(Fonts::Label), 16)
{
}
bool Label::isSelectable() const
{
return false;
}
void Label::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
[ 143 ]
www.it-ebooks.info
Waiting and Maintenance Area – Menus
states.transform *= getTransform(); target.draw(mText, states);
}
void Label::setText(const std::string& text)
{
mText.setString(text);
}
The only thing to note is that labels are not selectable. They are just a bunch of text, so there is no reason to be able to select them.
Let's get to the interesting part that we want in the GUI buttons. Fortunately, together with the logic already implemented in containers and components, buttons themselves don't need a lot of logic, other than the data they require for rendering and execution on activation.
So what do buttons add to their class? First we have the sprite and the text that a button renders. But most often, we want a button to execute something when it is pressed. Instead of querying the button if it has been pressed, we go with callbacks using the handy std::function class. When the button is activated by the GUI container, we execute the callback.
However, the callback functionality is not favorable in every scenario, so we also support the poll method where you ask the button if it is held down. In our system, we call it toggle, that is, the button remains in a pressed state until explicitly told to change. Normally, the button deactivates itself after the callback has been fired. If the button can be toggled on the other hand, it will not deactivate; this must be explicitly done by the user.
In the context of a button, the terms "activate" and "deactivate" refer to the two states pressed and released. We call them "activate" and "deactivate" because these are generalized terms for GUI design. Another case when we would like to activate but are not pressing the actual object could be, for instance, a selected item in a list box.
Button::Button(const FontHolder& fonts, const TextureHolder& textures) // ...
{
mSprite.setTexture(mNormalTexture); mText.setPosition(sf::Vector2f(mNormalTexture.getSize() / 2u));
}
bool Button::isSelectable() const
{
return true;
}
[ 144 ]
www.it-ebooks.info
Chapter 6
void Button::select()
{
Component::select();
mSprite.setTexture(mSelectedTexture);
}
void Button::deselect()
{
Component::deselect();
mSprite.setTexture(mNormalTexture);
}
void Button::activate()
{
Component::activate(); if (mIsToggle)
mSprite.setTexture(mPressedTexture); if (mCallback)
mCallback(); if (!mIsToggle) deactivate();
}
void Button::deactivate()
{
Component::deactivate(); if (mIsToggle)
{
if (isSelected()) mSprite.setTexture(mSelectedTexture);
else
mSprite.setTexture(mNormalTexture);
}
}
We only have to book-keep the current texture of the sprite and its activation/ deactivation scheme, thanks to the code we have written in the previous classes. And the callback makes it very easy to hook in your own code to be run.
[ 145 ]
www.it-ebooks.info