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

Game Programming Tutorial (Page 5)

This is page 5 of step by step guide to programming a 2D side-scrolling game with some 3D. Now that we can scroll backgrounds, let's add some tiles...

Instead of just making normal 2D tiles like I did with Chicken Utopia, I'm going to attempt to make organic 3D tiles which will work well with 2D character sprite animations.

STEP 57:

To make this 2D game somewhat 3D - we should switch to object space and make some modifications to our D3D settings.
Go to the InitRenderVars function and make the following changes:

ViewDesc.Position.x=0.0f;ViewDesc.Position.y=0.0f;ViewDesc.Position.z=600;

This just puts the camera at location 0,0,600 (depth 600).
And we'll need to change the D3DXMatrixLookAtLH so y axis is in negative direction -1.0f (so things aren't upside down):

D3DXMatrixLookAtLH(&g_viewing_matrix,&ViewDesc.Position, &ViewDesc.Target, &D3DXVECTOR3 (0.0f, -1.0f, 0.0f));

And since we're looking down 0,0,0 - we'll need to change the world position so everything is centered around that.

D3DXMatrixTranslation(&g_world_matrix, (FLOAT)-g_half_width, (FLOAT)-g_half_height, 0);

g_half_width we set earlier as the display's width divided by 2. So if the display width is 800 and display height is 600, then this would translate the world -400, -300 to make it centered.

STEP 58:

Some black lines appear at the edges of our backgrounds when we zoom in - so to prevent sampling at the edge of the texture, we'll make the following changes.
Go into InitVars()

g_far_background.r1.left=1;
...
g_far_background.r2.right=g_width-1;
...
g_mid_background.r1.left=
1;
...
g_mid_background.r2.right=g_width-1;

The reason I use g_width instead of the desc width is cuz it's not necessarily the correct width - but I do know the images are made to be the same width as the display-width.
Now go to the GameScroll() function and make these changes too (for the same reason):

g_far_background.pos1.x=tempx-1;
...
g_mid_background.pos1.x=mx-1;
...

Now go to the render function and add the D3DXSPRITE_OBJECTSPACE flag to tell it to render in 3D object space:

g_sprite->Begin(D3DXSPRITE_ALPHABLEND | D3DXSPRITE_OBJECTSPACE);

Cool - now we can do neat tricks with the camera, lighting, and 3D stuff and whatnot.. I've decided that instead of using 3D blocks for tiles or flat 2D tiles, I'll try making parallax sprite layer blocks instead - combining 2D sprites with 3D coordinates in layers for a more organic looking 3D tile sort of effect. I've never done this before(nor heard of it being done) so this could be an interesting experiment.. (^-^)
The code in the main.cpp file should look something like this so far:

#include "headers.h"
#include "Commdlg.h"
#include "Dlgs.h"
#include "resource.h"
#include <Dxerr.h>
#include <atlbase.h>
#include "debug.h"
#define TIMING_DELAY 8
#define
LEVEL_WIDTH 200
#define LEVEL_HEIGHT 50
#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;}
struct stBackgroundType{
    VEC pos1, pos2;
    
float x, y;
   
 int width, height;
    RECT r1,r2;
    LPDIRECT3DTEXTURE9 texture;
};

stBackgroundType g_far_background;
stBackgroundType g_mid_background;

D3DCOLOR text_color = 0xFF008811;
RECT text_position;
bool DeviceLost=false;
LPD3DXSPRITE g_sprite = NULL;
LPD3DXFONT g_font = NULL;
LPD3DXFONT g_font2 = NULL;
TCHAR* g_current_directory;
unsigned int g_directory_buf_size, g_size_of_directory = 260;
D3DLIGHT9 g_light;
float LPX=50.0f,LPY=50.0f,LPZ=0.0f;
D3DXMATRIX g_viewing_matrix;
D3DXMATRIX g_projection_matrix;
D3DXMATRIX g_world_matrix;
D3DXMATRIX g_matrix_2d;
int g_level=1;
int g_tile_x,g_tile_y;
D3DCOLOR g_screen_tint=0xFFFFFFFF;

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

int WINAPI WinMain(HINSTANCE hInstC,HINSTANCE hInstP,LPSTR lpCmdLine,int nCmdShow) {
    BEGIN_MEMORY_LEAK_CHECK;
    RESET_REPORT;
    INIT_WINDOW;
    g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL),
0);
    INIT_D3D_SIMPLE;
    InitRenderVars();
    LoadGraphics(
true);
    InitVars();
    
//---MAIN_LOOP--->
    while(!g_exit_game) {
        DWORD starting_point = GetTickCount();
        message_pump();
       
 if (g_WindowReactivate==1) { //begin
            g_WindowReactivate=
0;             ShowWindow(g_hwnd,SW_MAXIMIZE);UpdateWindow(g_hwnd);SetFocus(g_hwnd);
           
continue;
       }
       
if (!g_bWindowActive) continue;
       
//ALL INPUT GOES HERE!!!
       if (KEYPRESS(RIGHT)) {GameScroll(
-1.0f,0.0f); }
       if (KEYPRESS(LEFT)) {GameScroll(
1.0f,0.0f); }
       if (KEYPRESS(DOWN)) GameScroll(
0.0f,-1.0f);
       if (KEYPRESS(UP)) GameScroll(
0.0f,1.0f);
       if (KEYPRESS(ESCAPE)) { PostMessage(g_hwnd, WM_DESTROY,
0, 0); }
       render();
       do{}while ((GetTickCount() - starting_point) < TIMING_DELAY);
    }
//<---main_loop---
    CleanupGraphics();
    END_D3D;
    END_WINDOW;
    UnhookWindowsHookEx( g_hKeyboardHook );
    SHOW_PROBLEMS;
    SHOW_MEMORY_LEAK_DATA;
    return
0;
}
//-----------------------------end main------------------------------------------

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

void GameScroll(float x, float y) {
//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);
    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-1;

    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-1;
//}
//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;
//}
}//GameScroll

// I N I T L I G H T S
void init_lights(void)
{ //LIGHT_TYPE D3DLIGHT_SPOT D3DLIGHT_POINT //DIFFUSE_RGBA //AMBIENT_RGBA //SPECULAR_RGBA //Light Direction //Light Position //RANGE //Attenuation 1,2,3 //Falloff,Theta,Phi //light#
CREATE_LIGHT(g_light, D3DLIGHT_DIRECTIONAL,
1.0f,1.0f,1.0f,1.0f, 0.3f,0.3f,0.3f,0.3f, 0.6f,0.6f,0.6f,0.4f,
1.0f,-1.0f,-1.0f,
LPX,LPY,LPZ, 3000.0f, 1.0f,0.0f,0.0f, 0.12f,0.898f,1.047f,1);
return;
}//init_lights

#define SET_D3D(p1,p2) g_gpu->SetRenderState(p1, p2);
//------------------------------
// I N I T R E N D E R V A R S
//------------------------------

void InitRenderVars() {
SET_D3D(D3DRS_LIGHTING, TRUE)
SET_D3D(D3DRS_ZENABLE, TRUE)
SET_D3D(D3DRS_AMBIENT, D3DCOLOR_XRGB(100,100,100))
init_lights();
//setup camera:
ViewDesc.Position.x=0.0f;ViewDesc.Position.y=0.0f;ViewDesc.Position.z=600;
ViewDesc.Target.x=ViewDesc.Target.y=ViewDesc.Target.z=0.0f;
ViewDesc.xa=ViewDesc.ya=0.0f;
ViewDesc.uni_scale=1.0f;
D3DXMatrixIdentity(&ViewDesc.RotationMatrix);
D3DXMatrixIdentity(&ViewDesc.TranslationMatrix);
D3DXMatrixIdentity(&ViewDesc.ScaleMatrix);
D3DVIEWPORT9 vp; vp.X = 0; vp.Y = 0; vp.Width = g_width; vp.Height = g_height; vp.MinZ = 0.0f; vp.MaxZ = 1.0f; g_gpu->SetViewport(&vp);
D3DXMatrixIdentity(&g_world_matrix);
D3DXMatrixIdentity(&g_viewing_matrix);
D3DXMatrixIdentity(&g_projection_matrix);
D3DXMatrixLookAtLH(&g_viewing_matrix,&ViewDesc.Position, &ViewDesc.Target, &D3DXVECTOR3 (0.0f, -1.0f, 0.0f));
D3DXMatrixPerspectiveFovLH(&g_projection_matrix,D3DXToRadian(45),(float)g_width/(float)g_height,NEAR_Z,FAR_Z);
g_gpu->SetTransform(D3DTS_VIEW,&g_viewing_matrix);
g_gpu->SetTransform(D3DTS_PROJECTION,&g_projection_matrix);

D3DXMatrixIdentity(&g_world_matrix);
D3DXMatrixTranslation(&g_world_matrix, (FLOAT)-g_half_width, (FLOAT)-g_half_height, 0);
g_gpu->SetTransform(D3DTS_WORLD, &g_world_matrix);
 //create 2D graphics processing objects:
 if (!g_sprite) {
    if (FAILED(D3DXCreateSprite(g_gpu, &g_sprite))) FATAL("D3DXCreateSprite FAILED",Problem);
    D3DXFONT_DESC FontDesc = {20,0,800,0,false,DEFAULT_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_PITCH,"Arial"};
    D3DXFONT_DESC FontDesc2= {12,0,800,0,false,DEFAULT_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_PITCH,"Arial"};
    D3DXCreateFontIndirect(g_gpu,&FontDesc,&g_font);
    D3DXCreateFontIndirect(g_gpu,&FontDesc2,&g_font2);
  }
}//InitRenderVars

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

void LoadGraphics(bool restore_all) {
 if (restore_all) {
     //reload all graphics including main hero stuff
 }
 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

//----------------------------
// I N I T V A R S
//----------------------------

void InitVars() {
ShowCursor(FALSE);
g_current_directory = new TCHAR[g_size_of_directory]; //DIRECTORY
g_directory_buf_size = GetCurrentDirectory(g_size_of_directory, g_current_directory);
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=1;
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_width-1;
g_far_background.pos1.x=0;g_far_background.pos1.y=0;
g_mid_background.texture->GetLevelDesc(0, &desc);
g_mid_background.width=desc.Width;
g_mid_background.height=desc.Height;
g_mid_background.r1.left=1;
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_width-1;;
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;
g_far_background.pos1.z=g_mid_background.pos1.z=g_mid_background.pos2.z=0;
}//InitVars

//-------------------------------
// C L E A N U P G R A P H I C S
//-------------------------------

void CleanupGraphics() {
//release graphics:
SAFE_RELEASE_FINAL(g_far_background.texture);
SAFE_RELEASE_FINAL(g_mid_background.texture);
//release objects:
SAFE_RELEASE_FINAL(g_font);
SAFE_RELEASE_FINAL(g_font2);
SAFE_RELEASE_FINAL(g_sprite);
SAFE_RELEASE_FINAL(g_zbuffer);

if (DeviceLost) return; //don't release stuff after this until game exits...
SAFE_DELETE(g_current_directory);
ShowCursor(TRUE);
}//CleanupGraphics

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

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

DRAW(g_far_background.texture,&g_far_background.r1,&g_far_background.pos1);
DRAW(g_far_background.texture,&g_far_background.r2,NULL);
DRAW(g_mid_background.texture,&g_mid_background.r1,&g_mid_background.pos1);
DRAW(g_mid_background.texture,&g_mid_background.r2,&g_mid_background.pos2);

g_sprite->End();

END_SCENE;
}//render

STEP 59:

Ok let's do some graphics for the tiles we'll need. Open photoshop or whatever 2D editing software you like. First we'll start by creating each "tile" image at whatever size - bigger is better - so even if you make an image at say 600x600 to start - we'll resize it after later -- I'll show you how to do that without distortion that might effect tile-repeat.

Even though it seems kinda boring or generic - we'll start with a grass tile. To make it more interesting, we'll try to make it organic and a bit dynamic so it moves from wind and player interaction. I start with File>>New>>600x600 and hit Ctrl+Alt+0 to see it with actual pixel size (View>>Actual Pixels).

Set your foreground and background colors to a couple different shades of green. Pick the brush tool: brush and set the size to say 140 and scribble in a bunch of green on the bottom half of the image.
Now select the down arrow beside where it says "Brush" at the top of the screen and you should see a brush menu.
Scroll down the brush menu until you see a 3-bladed-grass brush and choose it. Select a master diameter of say 150 and scribble the grass around the green area so it's blades stick up above it a bit. Select a couple different shades of green and do the same for a size between 200-300. The do it again for a huge size - say 340-375... You should get something like this:

grass1

STEP 60:

If you have a picture of some grass and it kinda matches up, you can add a new layer and change the opacity of that layer to say 35% and move it down so it's just in the grass area. You may want to use the transform tool to distort it to match up a bit better(click on one of the resize corners to activate it and then pick: transform) You can also use the stamp tool: stamp -- hold ALT and pick an area you wish to copy-paint and paint to the area where you'd like to mimic the bitmap. Also you can use smudgesmudge to touch up areas to match a bit better. You could also temporarily make another layer behind the image and fill it in with light blue(or orange or whatever) to see any transparency artifacts that might show up that you may want to fix. Another thing I'd do is play with the contrast and saturation until it looks pretty cool. You may end up with something like this when you're done:

grass2

Save this copy as grass1.psd
If you want to make it look a little better along the bottom - select the burn tool burn and a radius of 40 and maybe and exposure of 25 and add a bit of shadow along the bottom.

STEP 61:

Let's make the grass repeatable. If you've already done this -- then when you resize it, you need to change resample to "Nearest Neighbor" instead of bicubic - otherwise there will be unwanted edge artifacts.
To avoid that possible problem - I'll just resize it now to 256x128 (note: you'll need to turn off constrain proportions).
After resize - Hit Ctrl+A then Ctrl+C then File>>New>>256x128(make sure the size is right)
Now hit Ctrl+V to paste the copy in. Hold down the shift key to move it in a straight line about half way across not quite touching the bottom (because we'll want to add trim to the bottom) - you should have this:

grass_offset

And hit Ctrl+V again to paste another copy and holding shift(to move in alignment), move it left and line it up with the other one (there may be a small space - just use the arrow keys to fine-tune the alignment).. You should have this:

grass3

Merge the layers.. Now you can use stamp, smudge, or whatever to remove the seam(note you can change the opacity of the stamp to make it more subtle). While we're at it - we may as well add a little trim to the bottom of the tile(but not near the edges) - you can use stamp and smudge for this too.. You should get something like this:

grass4

STEP 63:

After doing this I decided to use the shadowed version afterall. I also sprinkle a little bit of dodge highlights along the tops of the grass(careful not to go to close to the sides causing artifacts). Also you may need to use erase tool a bit along the bottom to make sure no color actually reaches the bottom of the image(which would also cause a noticable unwanted effect against certain backgrounds). After final touchups:

grass5

Now save as grass_tile1.psd
I'll show you how to add depth and motion later...

STEP 64:

We can apply the same strategy to make some under-tile. This time when resizing - we'll resize to 128x128 and make the tile a bit darker. Starting off I made this:

undergrass1

STEP 65:

We copy it and do something like this:

step1 then: step 2 then: step 3 to again to get: step 4

Then we merge all layers and use stamp and smudge to remove seams -- and now we have a seamless repeatable tile:

undertile2

Save as under_grass1.psd

STEP 66:

To give this layer some scrolling depth and wind effect - we'll need some floating layers which will scroll over top of it slightly closer to the camera. To make such a layer -- open under_grass1.psd and go Select>>Color Range... and using "image" - use the picker to select the darkest black you can see(and use 40-50 fuzziness) - then select OK.
Hit Delete key and go Filter>>Blur>>Blur.. Since this layer is slightly closer and more exposed to light - go to image>>adjustments>>brightness/contrast - and increase the brightness by say 10. Maybe boost the saturation by 5-10 too... set opacity to 96.. you should have an ugly layer like this (but it will look good in final scrolling):

undergrass_layer2

Now save this as under_grass1_layer2.psd

Step 67:

Do the same thing in step 66 but use a higher fuzziness value(ie: 88) and make only the brightest parts of the grass visible and brighten them by say 20 and increase saturation a bit. Reduce opacity to 90.

undergrass_layer3

Save as under_grass1_layer3.psd

STEP 68:

Let's make a couple tile edges for the grass. This is easy. Load grass_tile1.psd and just use the eraser eraser and smudge tools to make this:

right_grass

Now use the rectangular marquee tool select and hit Ctrl+A to select all. Now click Select>>Transform Selection and look for the little squares along the top that look like this: selectx -- Make sure to click the top right square to make it black(as in the picture), and set the X width to 64. Your selection will now be 0,0 to 64,128(which you can see if you hit F8 to look at the info window).

Click Image>>Crop and now Save as grass_right1.psd

For the left side - you'd do it similarily and then to prepare a 64 width crop - using the top left square as black and then selecting the left side and dragging it right until x is 192. Hit rect marquee and apply. Then hit crop and you should get this:

grass_left

STEP 69:

You can do the same to add trimmings and shadows to get these tiles too for the other grass.. Open a new 1024x1024 window and with snap settings on - test out putting copies of each tile side by side. If you want, make a new background layer with a checkerboard set so each square is 64x64 and give it an opacity setting of say 70. Align tiles and use smudge, stamp or whatever to illiminate any seams or weirdness. Here's some tiles you might want to have prepared(using techniques described earlier):

bottomleft_trimshadowright_trim

STEP 70:

Ok - once you're pretty sure the tiles blend seamlessly when placed side by side - start a new bitmap. File>>New>>1024x1024. This is where we'll put most of the tiles for the first level or area.
First we'll make a tile alignment guide which we can put behind our tile bitmaps - it's not essential but it comes in handy.
Now hit F8 to open the info window. Select color red. Bucket fill with red. Using the x,y info in the info window as a guide - select from 1,1 to 1023,1023 and hit delete(leaving a red outline). You should probably zoom in a bit more so the lines don't seem to disappear on you. Click layer 1 to make sure it's selected. Go Window>>Actions>>Create New Action. Hit record. Hit Ctrl+A, then ctrl+C, ctrl+V then select the move tool. Click once on one of them corner thingys to bring up the move options panel on the top. Now choose the top-left using the reference location square thingy so that x=0.0 and y=0.0 in the panel. Now change x=64 by typing it directly into the panel(make sure the black dot is on the top left in the reference thingy). This should move the copied layer 64 pixels to the right. Click the rectangle marquee tool and click apply. Hit Ctrl+V again and do the same sort of thing except instead of making x=64 - this time we'll leave it as 0 and set y=64. This should make another copy that is 64 pixels down. In the layers window - right click over a layer and select merge visible. Now click stop on the actions panel to end recording. In the actions window - select the action you created (ie: Action 1).. Now click the play button and you'll see it create more lines to make 4 squares in the top left. Keep hitting play until the entire image is full of squares.
tile guide
Now save as Tile_Guide.psd

STEP 71:

With Tile_Guide.psd open - go File>>Save As>>"Tiles1.psd" to begin the tiles for the first area or level..
Open all the tile graphics you've made so far. Make sure snap settings are on. You can use Window and look at the bottom to select which image you're currently working on. Go to one of the tile images and hit Ctrl+A, Ctrl+C then Go Window>>Tiles1.psd and hit Ctrl+V and use the move tool to line in up somewhere - perhaps starting with the top-left area. (it's handy to use Ctrl + or Ctrl - to zoom in and out while you do this and it's probably best to place the top left corner over the red lines).
(Note: If you are still finding problems at edges - you can use rectangle select, copy and paste and arrow key the copy over by 1 pixel to cover up the unwanted artifact. )
You should have something like this:
tiles1

Note - It's a good idea to have a layered copy and a copy with the background tile guide invisible and all the other layers merged (via merge visible). You never know when you want to move pieces around or touch up some art - so a layered copy will come in handy.

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

Return to Home Page