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

Forge of the Gods – Shaping Our World

In the second part of the function, we move the found node out of the container into the variable result. We set the node's parent pointer to nullptr, erase the empty element from the container, and return the smart pointer containing the detached node.

Ptr result = std::move(*found); result->mParent = nullptr; mChildren.erase(found);

return result;

}

Now that the basic tree operations of our scene graph are working, we can concentrate on the actual rendering.

Making scene nodes drawable

Our SceneNode class shall be rendered on the screen, thus we derive it from sf::Drawable. It shall store its current position, rotation, and scale as well as providing an interface to access them. Instead of re-inventing the wheel, we derive also from sf::Transformable, which gives us all that for free. In addition, we derive privately from sf::NonCopyable to state that our scene nodes cannot be copied (copy constructor and copy assignment operator are disabled).

We override the pure virtual draw() function of sf::Drawable to render the whole scene node. We also provide a new virtual function drawCurrent() which only draws the current object (but not the children). It can be overridden by classes deriving from SceneNode.

class SceneNode : public sf::Transformable, public sf::Drawable, private sf::NonCopyable

{

public:

typedef std::unique_ptr<SceneNode> Ptr;

public:

 

SceneNode();

void

attachChild(Ptr child);

Ptr

detachChild(const SceneNode& node);

private:

 

 

 

virtual void

draw(sf::RenderTarget& target,

 

sf::RenderStates states) const;

 

 

 

 

 

 

[ 58 ]

 

 

 

 

www.it-ebooks.info

Chapter 3

virtual void

drawCurrent(sf::RenderTarget& target,

 

sf::RenderStates states) const;

private:

 

std::vector<Ptr>

mChildren;

SceneNode*

mParent;

};

The draw() function allows our class to be used as shown in the following code snippet:

sf::RenderWindow window(...); SceneNode::Ptr node(...);

window.draw(*node); // note: no node->draw(window) here!

The window class internally calls our draw() function. No other classes need access to it, so we can make it private. If your compiler already supports this feature, you can declare draw() with the final keyword, to prevent the function from being overridden in classes derived from SceneNode.

Our function to draw the scene node has to deal with different transforms:

The current node's transform that determines position, rotation, and scale relative to the parent node. It is retrieved via base class method sf::Transfo rmable::getTransform().

The parameter states, which is passed by value and has a member variable transform of type sf::Transform. This variable holds the information where to render the parent scene node.

Two transforms can be chained (applied one after other) using the overloaded multiplication operators. Our expression with operator*= combines the parent's absolute transform with the current node's relative one. The result is the absolute transform of the current node, which stores where in the world our scene node is placed.

void SceneNode::draw(sf::RenderTarget& target, sf::RenderStates states) const

{

states.transform *= getTransform();

Now, states.transform contains the absolute world transform. We can draw the derived object with it by calling drawCurrent(). Both parameters are forwarded.

By the way, the sf::Sprite class handles transforms very similarly: it also combines its own transform with the one passed in the render states. Thus, we effectively copy SFML's behavior in SceneNode.

[ 59 ]

www.it-ebooks.info

Forge of the Gods – Shaping Our World

Eventually, we have to draw all the child nodes. We iterate through our container of smart pointers, and recursively invoke draw() on each element, again forwarding both parameters.

drawCurrent(target, states);

for (auto itr = mChildren.begin();

itr != mChildren.end(); ++itr)

{

(*itr)->draw(target, states);

}

}

In the code, we have factored out the last part into a function drawChildren().

In C++11, iteration through sequences can be achieved with the range-based for statement. It has the following syntax, which can be read as "for each variable in sequence":

for (Type variable : sequence)

If all current compilers supported range-based for, we could use the following code to iterate over all child nodes:

for (const Ptr& child : mChildren)

{

child->draw(target, states);

}

Fortunately, C++ is a very powerful language. It allows us to emulate the rangebased for loop with a few limitations, using a self-written macro FOREACH. We chose it to make code more readable, and to make porting to C++11 easier, when the feature is widely supported. Our loop then looks as follows:

FOREACH(const Ptr& child, mChildren)

{

child->draw(target, states);

}

Note the increase in expressiveness and readability over the classical iterator loop.

[ 60 ]

www.it-ebooks.info

Chapter 3

Drawing entities

Meanwhile, our data structure for hierarchical rendering is operational, but nothing is actually rendered yet. In order to do so, the virtual method drawCurrent() must be overridden in derived classes. One of these derived classes is Entity; it inherits SceneNode, so that entities can be placed in the scene and rendered by SFML.

Since we want our plane to be rendered as an image on the screen, we use a sf::Sprite object. This sprite becomes a member of the Aircraft class.

class Aircraft : public Entity // inherits indirectly SceneNode

{

public:

 

explicit

Aircraft(Type type);

virtual void

drawCurrent(sf::RenderTarget& target,

 

sf::RenderStates states) const;

private:

 

Type

mType;

sf::Sprite

mSprite;

};

In drawCurrent(), we only draw the sprite. We call sf::RenderTarget::draw(), and pass const sf::Drawable& (our sprite) as first argument and the render states as second argument.

void Aircraft::drawCurrent(sf::RenderTarget& target, sf::RenderStates states) const

{

target.draw(mSprite, states);

}

As you see, we needn't care about transforms anymore. Implementing rendering for a new entity has now become extremely simple! This is due to the fact that all the actual work is performed in our scene node and SFML.

Connecting entities with resources

There is one thing we have concealed: how our sprite is initialized. As you know, sprites need a texture to refer to, but where to get it from?

In Chapter 2, Keeping Track of Your Textures – Resource Management, we have

developed the class template ResourceHolder<Resource, Identifier> which is able to store SFML resource objects such as textures. Now the time for its first application has come. We need to choose the template arguments with which we instantiate the template. Because we draw a sprite which needs a texture, the resource type will be sf::Texture.

[ 61 ]

www.it-ebooks.info

Forge of the Gods – Shaping Our World

Next, we ought to find a type for the resource identifier. For aircraft textures, we could use the enum Aircraft::Type. However, there might be other objects than airplanes that also require textures. The landscape is such an example. Therefore, we create a new enumeration Textures::ID with identifiers for the textures. So far, we only have two texture IDs, one for each of our aircraft types.

namespace Textures

{

enum ID

{

Eagle,

Raptor,

};

}

Having defined the identifier type, we are ready to instantiate our resource holder for textures. Because it is used in several places, we create a type definition for it:

typedef ResourceHolder<sf::Texture, Textures::ID> TextureHolder;

We will now go back to our Aircraft class. In its constructor, we want to initialize the sprite with the correct texture. We could add a constructor parameter const sf::Texture&, but we directly pass a const reference to our texture holder instead. This has the advantage that the knowledge about the used texture stays local to our plane—the creator of the plane need not know what texture it uses. Also, if the plane suddenly requests more than one texture (for example, for an attached missile), we do not have to change the constructor signature and the code that invokes it.

Our constructor has now this declaration:

Aircraft(Type type, const TextureHolder& textures);

The TextureHolder class, which is an instantiation of the ResourceHolder class template, provides a function const sf::Texture& get(Textures::ID id) const which we can invoke with an identifier to get a texture back.

We do not have the identifier yet, but we have the plane type, stored in the constructor parameter type. We can map the aircraft type to the corresponding texture ID, for example, with a switch statement:

Textures::ID toTextureID(Aircraft::Type type)

{

switch (type)

{

case Aircraft::Eagle: return Textures::Eagle;

[ 62 ]

www.it-ebooks.info

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