Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
SFML Game Development.pdf
Скачиваний:
194
Добавлен:
28.03.2016
Размер:
4.19 Mб
Скачать

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]