You have been using a rather fundamental feature of C++ for a while now, and today you'll actually be properly introduced to it. It's the feature that adds the '++' to C, the feature that will add class to your code. Or rather: classes. It's time to get object-oriented.
This will actually require a different way of thinking on your part. Briefly: you wrote programs that executed one line after the other, calling functions, changing variables. That's logical, because that is how your computer works. The CPU will simply soak in instructions and one by one, operating on memory, and interfacing with the screen, mouse, and keyboard. There is a different way to look at things. It starts with objects.
Your game world consists of objects. Take the previous article: there is a player tank, there is a backdrop (which consists of tiles), there is a screen that you want to draw to. Later on, you'll want to add bullets, enemies, more levels and so on. This is a list of things, not a list of operations. You could actually argue that the most logical way to start thinking about what a game will do is actually: what objects do I need? And, slightly more detailed: what do these objects do? And what properties do these objects have?
As said, this is something you have already been using. Consider the default code in the Tick function:
void Game::Tick( float a_DT )
m_Screen->Clear( 0 );
m_Screen->Print( "hello world", 2, 2, 0xffffff );
m_Screen->Line( 2, 10, 66, 10, 0xffffff );
When you open up game.h you will see that your game has a variable m_Screen, which is an object. The type of this object is 'Surface'. In the above code snippet we are telling this surface to do things: it's asked to clear itself, to print something, and to draw a line. A surface can do more: you can find out what by looking at surface.h, line 70. There you will see that a surface can also Resize itself, amongst other things. A Surface also has some properties: a width, a height, a bunch of pixels, and some other things. The things that the Surface can do are called methods. The properties of an object are called member variables.
For this tutorial, you are supposed to have the code from the previous article in a working shape. We will build upon that.
Let's apply this in a more interesting way. In the previous tutorial, you added a player-controlled tank, with collision detection, to a tile-based backdrop. In this tutorial, we'll make the tank move by itself, using three simple rules:
- If the tank can move to the right, it will
- If the tank cannot move the the right, it will:
- Move up, if it is in the lower half of the screen
- Move down, if it is in the top half of the screen.
- When the tank reaches the right side of the screen, it is respawned at the left side.
The main object that we will be working on is a tank:
PPut this code in game.cpp, right above Game::Init(). Once you have that in place, you can create your tank:
So: you can now make variables of type Tank. The object does not yet have any properties though, and it can't do anything yet… Our particular tank will need to perform one task: move. We want to draw it once every time Game::Tick is executed, so when it moves, it should do one step. In terms of properties for our tank, there are a few obvious ones: position (x and y), and orientation. Considering this, the tank class now becomes:
int x, y, rotation;
When we first create a tank, we need to set its x and y and rotation. Until now, you would have done this in the Game::Init() function, but now there is a better way. It is called a constructor, and it looks like this:
x = 0;
y = 4 * 64;
rotation = 0;
int x, y, rotation;
The good thing is that when you create your tank (by creating a variable of type Tank), the constructor is executed. So, basically the constructor is the Init function of your class. And, best of all, each class can have its own, and it's executed automatically for you.
Now that we have a tank (called mytank), we can use it. First of all, we can access its properties: mytank.x, mytank.y and mytank.rotation. We can also make it do something. Add the following line to your Game::Tick function:
When you compile the program, you will get an error: we told C++ that there exists a tank, and that it can be told to Move(), but we didn't specify what happens in that case. Let's fix that. In the tank class, replace void Move(); by:
if (x > (16 * 64)) x = 0;
tank.Draw( x, y, m_Screen );
Note that this does not implement all the rules specified earlier, we'll safe that for the assignment :) When you try to compile this code, you will get an error. The above code assumes that m_Screen can be used in our tank class, but apparently it can not… There is a reason for that: m_Screen belongs to another object, which is called Game, and we can't just access it. Even though this is annoying right now, this is actually good: Member variables belong to their own object. This allows us to use x and y in a tank class, and x and y in a bullet class as well: the tank x and y will be referred to (in our program) as mytank.x and mytank.y; x for a bullet might be mightybullet.x. And, if there are multiple bullets, they all have their own x and y: bullet1.x, bullet2.x, and so on.
That doesn't solve the surface problem, obviously. Lucikly, the solution is not hard. The game does know about m_Screen, and the game moves the tank. So, the game should tell the tank about the Surface as well. Like this:
mytank.Move( m_Screen );
And, the tank should listen to that:
void Move( Surface* a_Screen )
if (x > (16 * 64)) x = 0;
tank.Draw( x, y, a_Screen );
Note that it's not called m_Screen anymore, because it is a different variable now: it's a function argument (hence the a_ prefix, which is not mandatory, but clear). This time, all is well, and the tank does its limited behavior, which you get to fix in the assignment.
A few final words before you start hacking away:
You have been using classes without knowing. There is a class Game, a Surface, and a Sprite. The template has some more objects, which you didn't use yet. Having a class means nothing by the way; you merely tell C++ what something looks like. To actually create something, you create a variable of that type, such as mytank. This variable is called an instance. Each instance has its own set of member variables, as defined in the class definition.
You use classes for many reasons:
- It allows you to think in high-level concepts before you get to the details;
- It groups data on a per-object basis rather than in one big messy pool;
- It groups data and the code that operates on that data.
We will dive deeper in the subject at a later time. For now, you know enough for the.....
- Correctly implement the three rules for the tank object.
- Add two more tanks. One starts at tile column 4, one starts at tile column 8.