In this final chapter, we will explore the C++ concept of extending other people's classes. More specifically, we will look at the SFML Drawable
class and the benefits of using it as a base class for our own classes. We will also scratch the surface of the topic of OpenGL shaders and see how writing code in another language OpenGL Shading Language (GLSL), which can be run directly on the graphics card, can lead to smooth graphical effects that might otherwise be impossible. As usual, we will also use our new skills and knowledge to enhance the current project.
Here is a list of the topics in the order we will cover them:
The Drawable
class has just one function. It has no variables either. Furthermore, its one and only function is pure virtual. This means that if we inherit from Drawable
, we must implement its one and only function. The purpose of this, which you may remember from Chapter 12, Abstraction and Code Management â Making Better Use of OOP, is that we can then use our class, which inherits from drawable
, as a polymorphic type. Put more simply, anything that SFML allows us to do with a Drawable
object, we will be able to do with our class that inherits from it. The only requirement is that we must provide a definition for the pure virtual function, draw
.
Some classes that inherit from Drawable
already include Sprite
and VertexArray
(among others). Whenever we have used Sprite
or VertexArray
, we have passed them to the draw
function of the RenderWindow
class.
The reason that we have been able to draw every object we have drawn in this book is that they have all inherited from Drawable
. We can use this knowledge to our advantage.
We can inherit from Drawable
with any object we like, as long as we implement the pure virtual draw
function. This is also a straightforward process. The header file (SpaceShip.h
) of a hypothetical SpaceShip
class that inherits from Drawable
would look as follows:
class SpaceShip : public Drawable { private: Sprite m_Sprite; // More private members public: virtual void draw(RenderTarget& target, RenderStates states) const; // More public members };
In the previous code, we can see the pure virtual draw
function and a Sprite. Notice there is no way to access the private Sprite
outside of the class, not even a getSprite
function!
The SpaceShip.cpp
file would then look something like the following:
void SpaceShip::SpaceShip { // Set up the spaceship } void SpaceShip::draw(RenderTarget& target, RenderStates states) const { target.draw(m_Sprite, states); } // Any other functions
In the previous code, notice the simple implementation of the draw
function. The parameters are beyond the scope of the book. Just note that the target
parameter is used to call draw
and passes in m_Sprite
as well as states
, the other parameter.
While it is not necessary to understand the parameters to take full advantage of Drawable
, in the context of the book, you might be intrigued. You can read more about the SFML Drawable
class on the SFML website: http://www.sfml-dev.org/tutorials/2.3/graphics-vertex-array.php#creating-an-sfml-like-entity
In the main game loop, we could now treat a SpaceShip
instance as if it were a Sprite
, or any other class that inherits from Drawable
:
SpaceShip m_SpaceShip;
// create other objects here
// ...
// In the draw function
// Rub out the last frame
m_Window.clear(Color::Black);
// Draw the spaceship
m_Window.draw(m_SpaceShip);
// More drawing here
// ...
// Show everything we have just drawn
m_Window.display();
It is because SpaceShip
is a Drawable
that we can treat it like a Sprite
or VertexArray
, and because we overrode the pure virtual draw
function, everything just works as we want it to. Let's look at an alternative way of encapsulating the drawing code into the game object.
It is also possible to keep all the drawing functionality within the class that is the object to be drawn by implementing our own function, within our class, perhaps like the following code:
void drawThisObject(RenderWindow window) { window.draw(m_Sprite) }
The previous code assumes that m_Sprite
represents the visual appearance of the current class we are drawing, as it has throughout this and the previous project. Assuming that the instance of the class that contains the drawThisObject
function is called playerHero
, and further assuming we have an instance of RenderWindow
called m_Window
, we could then draw the object from the main game loop with the following code:
playerHero.draw(m_Window);
In this solution, we pass the RenderWindow
, m_Window
, into the drawThisObject
function as a parameter. The drawThisObject
function then uses the RenderWindow
to draw the Sprite
, m_Sprite
.
This solution certainly seems simpler than extending Drawable
. The reason we do things the way suggested (extending Drawable) isn't really of any great benefit, in its own right, for this project. The actual reason we will soon draw a neat explosion using this method is because it is a good technique to learn.
With each project we have completed throughout the book, we have learned more about games, C++, and SFML. Possibly the biggest improvements we have made from one game to the next is in the structure of our code–the programming patterns that we have used.
If there were a fourth project to this book, we could take things even further. Unfortunately, there isn't, but have a think about the following idea for improving our code.
Imagine every object in our game is derived from a single, simple, abstract base class. Let's call it GameObject
. Game object would probably have concrete functions for getPosition
and others. It would likely have a pure virtual update
function (because every object updates differently). Furthermore, consider that GameObject
inherits from Drawable
.
Now look at this hypothetical code:
vector<GameObject> m_GameObjects; // Code to initialise all game objects // Including tiles, characters, enemies, bullets and anything else // In the update function for (i = m_GameObjects.begin(); i != m_GameObjects.end(); i++) { (*i).update(elapsedTime); } // That's it! // In the draw function // Rub out the last frame m_Window.clear(Color::Black); for (i = m_GameObjects.begin(); i != m_GameObjects.end(); i++) { m_Window.draw(*i); } // Show everything we have just drawn m_Window.display(); // That's it!
The preceding code is a big step up in terms of encapsulation, code manageability, and elegance when compared to even this final project. If you look at the previous code, you will notice there are, however, unanswered questions, such as where collision detection fits in, for example. Hopefully, however, you can see that further study (by building lots of games) will be necessary to master C++.
Although we will not be implementing an entire game in this manner, we will see how we can design a class (ParticleSystem
) and pass it directly to m_Window.draw(m_MyParticleSystemInstance)
.
18.216.20.236