Page1, Page 2, Page 3, Page 4, Page 5, Page 6, Page 7

Game Programming Tutorial (Page 4)

This is page 4 of step by step guide to programming a 2D side-scrolling game with some 3D. So we have a basic skeleton for our game engine and 2 layers of background to scroll behind the main background and other sprites. Next we'll program the scrolling mechanism and test it out.

This is where things get easier and a lot more fun - because now we can actually make some game stuff happen.

Step 45:

Go into your Projects folder ie: Projects>>MyGame>>MyGame (there should be a debug folder in there).. now add a new folder called Graphics. Copy your far_background1.dds and mid_background1.dds into that folder.

Step 46:

Open your project/solution in Visual Studio. We'll add a new global variable called g_level and set it to 1. We'll also make a structure (above the globals) which will hold information about background layers - like where it is or how fast it is moving or what texture map it is. Then we'll add a couple backgrounds of that structure type. You should end up with something like this:

struct stBackgroundType{ //<--added
    VEC pos1, pos2;           
//added.......
    
float x, y;
    
int width, height;
    RECT r1,r2;
    LPDIRECT3DTEXTURE9 texture;
}; //.......added
stBackgroundType g_far_background;  
//<--added
stBackgroundType g_mid_background;
//<--added
...

...
D3DXMATRIX g_projection_matrix;
D3DXMATRIX g_world_matrix;
D3DXMATRIX g_matrix_2d;

int                  g_level=1; //<-- added
int                  g_tile_x, g_tile_y; //<-- added
D3DCOLOR     g_screen_tint=0xFFFFFFFF; //<-- added

//---------------
// M A I N
//---------------
...

g_level will determine what level or world we are in - and will be needed to determine what graphics to load.
The backgrounds are set up to have 2 position markers. The backgrounds will be larger than the display height, so they can pan up and down a bit.. Since the levels are made to change mostly horizontally - we need the far backgrounds to repeat gradually horizontally. It's like taping 2 copies of a background together. Like this:

background_struct

I use pos1 and pos2 to store the actual position each copy will be shown at and x,y to store the offset of the background itself (ie: when it exceeds the display width it resets x to 0). r1 and r2 are used to determine which part of the original bitmap is shown at each pos... and texture holds the original image of the background. [note r2 will be used for the left side and r1 will be used for the right]
We'll use g_screen_tint later to make certain effects -- like when it gets dark out or if there's lightning or fading in and out...

STEP 47:

Now we actually load the graphics -- change your graphics function to something like this:

//---------------------------
// L O A D G R A P H I C S
//---------------------------

void LoadGraphics(bool restore_all) {
if (restore_all) {
    
//(this is where we'd put base graphics like the main character animation images - stuff we don't need to reload)
}
switch(g_level) {
    case 1:
        LOAD_TEXTURE(
"Graphics/far_background1.dds",g_far_background.texture);
        LOAD_TEXTURE(
"Graphics/mid_background1.dds",g_mid_background.texture);
    break;
}
}
//LoadGraphics

STEP 48:

Any time we use a new texture or sound or whatever, it's a good idea to immediately go to the CleanupGraphics function to make sure we release it when the program's done with it. Go to the CleanupGraphics() function and add the following:

//release graphics:
SAFE_RELEASE_FINAL(g_far_background.texture);
SAFE_RELEASE_FINAL(g_mid_background.texture);

STEP 49:

Note - g_tile_x and g_tile_y will be used to track where the main hero is in the world using a tile system. When we scroll the background, we'll want to make sure the background doesn't try to continue to scroll when the hero reaches the edge of the world map(cuz that would be weird).
GameScroll function takes an x value and y value which determine how far we are scrolling left-right and up-down.

Go into main_defs.h and add this to the PROTOTYPES section so the compiler knows what we're about to do...
//P R O T O T Y P E S
void GameScroll(float x, float y);

If you want you can add these to Main.cpp or put them somewhere else handy - or just use dynamic variables if you feel like having various level sizes.. These are just used to determine how many tiles wide and how many tiles high we'd like to allow our world to contain:

#define LEVEL_WIDTH 200
#define LEVEL_HEIGHT 50

Actually it's probably better to use variables for these - but I suppose we can just use these as maximum values and build our level to whatever size within these limits.. NOTE! If you use static values like this and don't save or load level size - your level data will be on crack when you load a level if you have changed these values! (>O<)! SO PLAN AHEAD! (^-^)

Now go back into Main.cpp and add this function somewhere after the main function...

//---------------------
// G A M E S C R O L L
//---------------------

void GameScroll(float x, float y) {
//scroll far background
// if (((x<0)&&(g_tile_x<LEVEL_WIDTH))||((x>0)&&(g_tile_x>0))) {
    float tempx=g_far_background.x;
    tempx+=x;
   
 if (tempx<0) tempx=g_width+tempx;
    
if (tempx>=g_width) tempx=(tempx-g_width);

Keep in mind if x<0, then the background is going left -- which means the hero is actually travelling through the world to the right. So we only want to travel right if we haven't reached the far right side of the tiles/gamemap. And likewise, we only want to travel left (x>0) if we haven't reached the far left side.. For now - I commented out the code that controls this because we are stuck at g_tile_x=0 for now - so without it commented out - we can't test scrolling for going left...
We use tempx to find out where the background will be in the next frame. It scrolls between 0 and g_width(display resolution width) and snaps it to the right side if it's <0 or snaps to the right side if it's > g_width.. (we'll attach another piece to the left side of it so it seems like a seemless repeating background)

    g_far_background.r1.right=(LONG)(g_width-tempx);
    g_far_background.r2.left=(LONG)(g_width-tempx);
    g_far_background.x=tempx;
    g_far_background.pos1.x=tempx;
 //}
}//GameScroll

r1 and r2 are RECT's used to determine from where in the original bitmap we will sample. So the first rectangle, r1 will get its image from 0,0 to r1.right, r1.bottom. So if you're wondering - yeah I kinda did this backwards - background copy using r1 will be to the right of the copy using r2...
r1.right is the only part that needs to change. If the first copy of the background is placed at tempx, 0 - then it will be on the right side and the other copy will be to the left, sampling from g_width-tempx to g_width... well I guess we haven't initialized these variables yet, so we better do that too...

STEP 50:

Go back to InitVars()
Add the following initializations:

g_far_background.x=g_far_background.y=g_mid_background.x=g_mid_background.y=0;
D3DSURFACE_DESC desc; g_far_background.texture->GetLevelDesc(0, &desc);
g_far_background.width=desc.Width;
g_far_background.height=desc.Height;
g_far_background.r1.left=0;
g_far_background.r1.top=0; g_far_background.r1.bottom=g_far_background.height;
g_far_background.r2.top=0; g_far_background.r2.bottom=g_far_background.height;
g_far_background.r2.right=g_far_background.width;
g_far_background.pos1.x=0;g_far_background.pos1.y=0;

May as well add this too cuz we'll need it later:

g_mid_background.texture->GetLevelDesc(0, &desc);
g_mid_background.width=desc.Width;
g_mid_background.height=desc.Height;
g_mid_background.r1.left=0;
g_mid_background.r1.top=0; g_mid_background.r1.bottom=g_mid_background.height;
g_mid_background.r2.top=0; g_mid_background.r2.bottom=g_mid_background.height;
g_mid_background.r2.right=g_mid_background.width;
g_mid_background.pos1.x=g_mid_background.pos1.y=g_mid_background.pos2.x=g_mid_background.pos2.y=0;
g_mid_background.y=0.0f; //init cuz we'll scroll this vertically too

We set everthing to 0,0 to width,height -- except for r1.right and r2.left (which are determined by the GameScroll function)

STEP 51:

Let's add some test controls (^_^)
Go back to where it says ALL INPUT GOES HERE in your main loop and add these input responses:

//ALL INPUT GOES HERE!!!
if (KEYPRESS(RIGHT)) GameScroll(-1.0f,0.0f);
if (KEYPRESS(LEFT)) GameScroll(
1.0f,0.0f);

STEP 52:

Well I guess we'd better render our scene so we can actually see what's happening on the screen.
I'll add a couple handy definitions to simplify the code a bit. BEGIN_SCENE clears the backbuffer(where stuff gets rendered) to black - (also clearing the z-buffer) and tells the gpu we are going to render. END_SCENE causes a page flip which brings the backbuffer to the front buffer so we can see the final rendered scene and sets DeviceLost to true so we know to try to recover. (you could put this in main.cpp or in maindefs.h - wherever you prefer)

#define BEGIN_SCENE g_gpu->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,0x00000000,1.0f, 0); \
g_gpu->BeginScene();
#define END_SCENE g_gpu->SetTexture(0,NULL); g_gpu->EndScene(); \
ghr=g_gpu->Present(NULL,NULL,NULL,NULL); if(ghr==D3DERR_DEVICELOST) {DeviceLost = true; }

In main.cpp, somewhere after the main function, we'll add the render function like so:

//----------------
// R E N D E R
//----------------

void render() {
    HANDLE_LOST_DEVICES;
    BEGIN_SCENE;
    g_sprite->Begin(D3DXSPRITE_ALPHABLEND);

    g_sprite->Draw(g_far_background.texture,&g_far_background.r1,NULL,&g_far_background.pos1,g_screen_tint);
    g_sprite->Draw(g_far_background.texture,&g_far_background.r2,NULL,NULL,g_screen_tint);

    g_sprite->End();
    END_SCENE;
}
//render

First we handle any lost device, setup the backbuffer for rendering, start sprite drawing with ALPHABLEND (see documentation for other flags[ie: D3DXSPRITE_SORT_DEPTH_BACKTOFRONT]), draw 1 copy of the background at pos1 sampling only the necessary part of the background. Then we show the second copy at 0,0 and sampling from r2.left to pos1.. I didn't actually end up using pos2 for the far background - but we'll need it for other layers..
Actually -- I kinda made this more complex than it needed to be - we could have just used 2 pos variables and moved each image without changing the sampling RECT's... but meh - whatever... it's probably a totally unnecessary optimization(if it's actually faster - I'm not sure).

Make sure to add render(); in the main loop right before the timing part:

if (KEYPRESS(ESCAPE)) {  PostMessage(g_hwnd, WM_DESTROY, 0, 0); }
render(); //<-- ADDED
do{}while ((GetTickCount() - starting_point) < TIMING_DELAY);

NOW TEST IT! (^__^)
Hit F7 to build and if all is good - F5 to test it out... Use left and right arrow keys to test scrolling...

STEP 53:

Let's add the midground scrolling part now. In the GameScroll function - after the g_far_background.pos1.x=tempx; - add the following (it works the same sort of way)

float mx=g_mid_background.x;
mx=mx+x+x;
if (mx<0) mx=g_width+mx;
if (mx>=g_width) mx=(mx-g_width);
g_mid_background.r1.right=(LONG)(g_width-mx);
g_mid_background.r2.left=(LONG)(g_width-mx);
g_mid_background.x=mx;
g_mid_background.pos1.x=mx+0.5f;

The middle background is closer so I add x twice so it go faster..

STEP 54:

Going back to the render function - I just added this #define - not necessary but it looks nicer to me

#define DRAW(p1,p2,p3) g_sprite->Draw(p1,p2,NULL,p3,g_screen_tint);
//----------------
// R E N D E R
//----------------
void render() {

STEP 55:

I remove these
g_sprite->Draw(g_far_background.texture,&g_far_background.r1,NULL,&g_far_background.pos1,g_screen_tint);
g_sprite->Draw(g_far_background.texture,&g_far_background.r2,NULL,NULL,g_screen_tint);
and replace with these:
DRAW(g_far_background.texture,&g_far_background.r1,&g_far_background.pos1);
DRAW(g_far_background.texture,&g_far_background.r2,NULL);
and add these:
DRAW(g_mid_background.texture,&g_mid_background.r1,&g_mid_background.pos1);
DRAW(g_mid_background.texture,&g_mid_background.r1,&g_mid_background.pos2);
Note - we need pos2 this time cuz this one scrolls vertically too - so we can't default it as a 0,0 position.

Now when you run the program - you should see both layers scrolling differently -- parallax scrolling..
If there's white where it should be transparent in the second layer - then your .dds exporter isn't working right. You may need another exporter or just use .PNG format -- if you want you can use DirectX SDK Texture Tool to convert a .PNG to a .DDS of the format you like (should be X8R8G8B8).

Looks pretty cool eh! (^_^)

STEP 56:

Let's allow the middle background to scroll up and down a bit (which it can cuz it's taller than the display).
In the GameScroll function - add the following code

//if (((y<0)&&(g_tile_y<LEVEL_HEIGHT))||((y>0)&&(tile_y>0))) {
float my=g_mid_background.y;
my+=(y/
4.0f);
g_mid_background.y=my;
g_mid_background.pos1.y=my; g_mid_background.pos2.y=my;

//}

I comment out the vertical tile limit check just cuz, for now, we're stuck at g_tile_y=0 - but it will come in handy later to limit verticle scrolling when we're at the top or bottom of the world tile-map.
I set my to move at 1/4 the speed input cuz the middlebackground isn't tall enough to scroll too far.
You might want to use a vertically shorter level style - like 25 instead of 50 tiles for example -- then maybe use 1/2 speed: my+=(y/2.0f); instead cuz then you notice the vertical scrolling more easily.. It's up to you...

Now onto the tiles...

Click >>PAGE 5<< to continue on to the next page of this tutorial.

Return to Home Page