The previous tutorial introduced you to the template. You can now draw some lines and print some text in all kinds of colours, but that’s clearly far from the goal of making actual games, where bullets wizz past in glorious 3D and enemies flank you in the smartest ways. To get a bit closer to that, we’ll take the static coordinates from last time and make them a bit more flexible by using C++ variables and functions. We’ll need that for the next part, which is all about loops, sprites and other bouncy things.
Previous Part: The TemplateNext Part: Sprites and Loops
Getting the stuff you need
For this tutorial, you will be using the same template from the 2nd tutorial:
Extract the template zip file to a fresh directory (say, C:\Projects\variables
or where ever you saved your project from the previous tutorial) and load up the .sln
file.
Change the Tick
function in game.cpp
to this:
27 28 29 30 31 32 33 |
void Game::Tick(float deltaTime) { screen->Clear(80); screen->Line(100, 50, 200, 50, 0xffffff); screen->Line(150, 50, 150, 300, 0xffffff); screen->Line(100, 300, 200, 300, 0xffffff); } |
This paints the I of IMPRESSIVE in glorious white on a vibrant blue backdrop. Now imagine we would like thicker lines. We take the three line commands, copy them, and draw them one pixel to the right:
27 28 29 30 31 32 33 34 35 36 |
void Game::Tick(float deltaTime) { screen->Clear(80); screen->Line(100, 50, 200, 50, 0xffffff); screen->Line(150, 50, 150, 300, 0xffffff); screen->Line(100, 300, 200, 300, 0xffffff); screen->Line(101, 50, 201, 50, 0xffffff); screen->Line(151, 50, 151, 300, 0xffffff); screen->Line(101, 300, 201, 300, 0xffffff); } |
Now, that’s a bit disappointing: obviously, only the vertical line got thick now. Let’s draw all six lines one more time, this time one pixel lower:
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
void Game::Tick(float deltaTime) { screen->Clear(80); screen->Line(100, 50, 200, 50, 0xffffff); screen->Line(150, 50, 150, 300, 0xffffff); screen->Line(100, 300, 200, 300, 0xffffff); screen->Line(101, 50, 201, 50, 0xffffff); screen->Line(151, 50, 151, 300, 0xffffff); screen->Line(101, 300, 201, 300, 0xffffff); screen->Line(100, 51, 200, 51, 0xffffff); screen->Line(150, 51, 150, 301, 0xffffff); screen->Line(100, 301, 200, 301, 0xffffff); screen->Line(101, 51, 201, 51, 0xffffff); screen->Line(151, 51, 151, 301, 0xffffff); screen->Line(101, 301, 201, 301, 0xffffff); } |
This time the result is satisfying. However, this is not something you want to spend a career on: we need to find a smarter way to do this. In C/C++ you would use a function for this. But before we get to that, let’s try something else.
27 28 29 30 31 32 33 34 35 |
void Game::Tick(float deltaTime) { screen->Clear(80); int x = 100; int y = 0; screen->Line(100 + x, 50 + y, 200 + x, 50 + y, 0xffffff); screen->Line(150 + x, 50 + y, 150 + x, 300 + y, 0xffffff); screen->Line(100 + x, 300 + y, 200 + x, 300 + y, 0xffffff); } |
Here we have the original three lines again, but this time, each coordinate has either + x
or + y
attached to it. X
and Y
are variables here, and you may give them any name you like. The number they represent is simply added to the coordinate. This is useful; we can now make the ‘I’ move. Try this:
27 28 29 30 31 32 33 34 35 36 |
int x = 100; int y = 0; void Game::Tick(float deltaTime) { screen->Clear(80); screen->Line(100 + x, 50 + y, 200 + x, 50 + y, 0xffffff); screen->Line(150 + x, 50 + y, 150 + x, 300 + y, 0xffffff); screen->Line(100 + x, 300 + y, 200 + x, 300 + y, 0xffffff); x = x + 1; } |
Now, if we could draw those three lines multiple times with different variable values, we would be all set: we could draw super-fat characters; heck, we could even move them across the screen! It’s not a bullet yet, but it’s a start.
int x = 100;
line: It was moved outside the Tick
function. When it was inside the Tick
function, x
and y
were reset for every image, because the line int x = 100;
, int y = 0;
is executed for everytime Tick
is invoked. Since they are supposed to be set to their initial values only in the first frame, you would expect that they should go to the Init
function. However, one function cannot access variables in another function in C/C++, and even if two variables in different functions have identical names, they will still be two different things. Variables created outside a function however are available in all functions. And, they are initialized only once. In C/C++ terminology, the variables x
and y
were declared within the scope of the Tick
method, and now they are declared in the global scope. Functions
A function is a piece of code. You have seen three of them so far: Game::Init()
, Game::Tick()
, and Game::Shutdown()
. We can create our own too. Add the highlighted line to the game.h
header file:
11 12 13 |
void Shutdown(); void DrawI(int x, int y); void Tick( float deltaTime ); |
You can put it in many correct locations, but between Shutdown
and Tick
functions is probably a good place.
Then, return to game.cpp
, and add the following function above the Game::Tick
function:
23 24 25 26 27 28 |
void Game::DrawI( int x, int y ) { screen->Line( 100 + x, 50 + y, 200 + x, 50 + y, 0xffffff ); screen->Line( 150 + x, 50 + y, 150 + x, 300 + y, 0xffffff ); screen->Line( 100 + x, 300 + y, 200 + x, 300 + y, 0xffffff ); } |
And, change the Game::Tick
function to this:
34 35 36 37 38 |
void Game::Tick(float deltaTime) { screen->Clear(80); DrawI(0, 0); } |
In other words: When we type DrawI( 0, 0 )
, we actually execute the code specified in Game::DrawI()
method. And, whatever we type after DrawI
(in this case: 0, 0
) gets passed to the function: Inside the function, x
and y
will contain the values 0
and 0
. So, our fat character code becomes:
34 35 36 37 38 39 40 41 |
void Game::Tick(float deltaTime) { screen->Clear(80); DrawI(0, 0); DrawI(1, 0); DrawI(0, 1); DrawI(1, 1); } |
And that’s quite a bit more reasonable.
By the way, the screen->Clear
and screen->Line
that you have been using all along are functions too! Have a look at surface.cpp
, line 151:
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
void Surface::Line(float x1, float y1, float x2, float y2, Pixel c) { ... float b = x2 - x1; float h = y2 - y1; float l = fabsf(b); if (fabsf(h) > l) l = fabsf(h); int il = (int)l; float dx = b / (float)l; float dy = h / (float)l; for (int i = 0; i <= il; i++) { *(m_Buffer + (int)x1 + (int)y1 * m_Pitch) = c; x1 += dx, y1 += dy; } } |
Not everything in this function will make sense right now, but rest assured: this is how you draw a proper line in C/C++.
Assignment
Here’s your task for today:
- Create a function that draws a fat I. Call it
DrawFatI
. This function will useDrawI
in turn, of course. - Modify
Game::Tick()
in such a way that the fat I moves across the screen.
Once you have completed this assignment you may continue with the next part.
Previous Part: The TemplateNext Part: Sprites and Loops
btw = the example code:
generates a BUNCH of warnings.. converting int to float, possible data loss.
replacing the first two lines with:
float x = 100.0f;
float y = 0.0f;
removes all the error messages – as
screen->Line()
is expecting 4 floats and color.Yes, the
Screen::Line
function expects floats, but you are usingint
s. Setting your variables to float, will cause the addition operator to perform an implicit conversion tofloat
thus removing the warnings.is thsi statement true??
“the variables x and y were declared within the scope of the Tick method, and now they are declared in the global scope.”
i dont think these are GLOBAL… they are inside the namespace declaration – so arnt they namespace scope. they can be accessed anywhere by prefixing with the namespace Tmpl8.x and Tmpl8.y – but ui didnt think that these were truely global scope.
if we put the declaration of x and y BEFORE the “namespace Tmpl8” line.. then they would be truly global… wouldnt they?
just want to see whether im misunderstanding something!
by the way.. putting them outside of the Tmpl8 namespace would be HIGHLY dangerous as any other declarations of x and y at global scope in ANY other modules would result in redefinition errors!
That is correct. We generally try to avoid declaring variables in the global namespace.
Technically speaking, variables declared outside of any scope is called the “global namespace scope” and can be accessed using the “default scope” operator:
You can also include any namespace into the current scope, making that scope available in the current scope:
So all variables are in namespace scope, but the ones outside any scope block are in “global namespace scope.”.
should this line in your tutorial/course:
“By the way, the surface->Clear and screen->Line that you have been…”
be screen->Clear, not surface->Clear ?
Yes, I have updated the article.
completed 🙂
modified game.h with:
and modified game.cpp with:
moves it across and reset to the start when it has been drawn in position 599
Looks good.
Hey how do i use delta to calculate fps ? Could be fun
FPS stands for “Frames Per Second” and is written \(\frac{f}{s}\). To convert delta time (\(\Delta{t}\)) to FPS, just divide by 1:
\[FPS=\frac{1}{\Delta{t}}\]
Usually you don’t want to compute this every frame, so instead you should accumulate the number of frames over a period of time (for example 1 second) then divide the number of frames by the elapsed time:
this works pretty well for me. the fat I goes right and when it reaches the end goes back left and it’s a loop.
int move = 0;
int direction = 1;
void Game::Tick(float deltaTime)
{
screen->Clear(80);
direction = move == 0 ? 1 : move == 500 ? -1 : direction;
DrawFatI(30, move);
move = move + direction;
}
void Game::DrawFatI(int dimension, int x) {
for (int i = 0; i < dimension; i++)
for (int j = 0; j < dimension; j++)
DrawI(x + i, j);
}