Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
87 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Introduction
 Setting up Direct3D
 Rendering a
 Wireframe
 Isometric View

 Let's Texture This
 Beast!

 Adding Lighting
 Something More
 Impressive!

 Wrapping This
 All Up


 Printable version

 


First Steps: Setting Up DirectX

The first stage in learning to use "Enhanced 2D" requires the creation of a minimal Win32 application. The sources that come with this article include WinMain.cpp (for each example), which is about as minimal as you can get if you still want your program to be called "Win32"! The only significant items to note in this code are the following:

  • The inclusion of CEngine *Engine as a global variable. CEngine is my basic tile engine class.
  • Just before the main message loop in WinMain, note the call to Engine->GameInit()
  • The main loop itself repeatedly calls Engine->GameMain()
  • When closing down, Engine->GameDone() is called.

The DXEngine Framework

I'm a diehard C++ fan. I admit it. Classes, Object Oriented Programming and similar concepts appeal to me in ways that probably can't be described to minors… or at least intellectually, anyway. ;-) Because of my love for C++, I've designed the framework used for all the demonstrations in an object oriented manner. DXEngine is the base class for the game itself. Its an abstract class - meaning you can't instantiate it. It includes all of the code necessary to initialize DirectDraw and Direct3D, and hides it away in a single call to InitDirectX(). All a programmer wanting this functionality in his/her program needs to do is inherit this class and write GameInit(), GameMain() and GameDone(). Is that not nifty?

Setting up DirectDraw

DXEngine does quite a lot of work to bring DirectDraw to life. Much of this code originally came from LaMothe's work (although the BOB engine itself was trashed very quickly - talk about slow). The function that does all the work for setting up DirectDraw is InitDirectDraw() (no surprises there!). It doesn't differ significantly from the steps that you would need in general - although it does take the time to work in both windowed and fullscreen modes. Basically, it gets an interface to DirectDraw, sets the cooperation level, creates the screen mode, creates a primary and secondary surface and then sets up a clipper. Nothing too revolutionary.

A few points in it need to be highlighted, however, in the context of Direct3D:

  • When setting up the Cooperation Level, I included the keyword DDSCL_FPUSETUP. This tells DirectDraw that Direct3D is likely to be using the floating point unit. Its not compulsory, but it can improve performance. You should avoid putting doubles in your code if you include this statement.
  • When setting the surface description (ddscaps), I included the keyword DDSCAPS_3DDEVICE. No prizes for guessing what this does… it tells Windows that the surface you want should be compatible with 3D device rendering. Not including this can result in nothing happening when you start rendering.

Setting Up Direct3D

InitDirect3D() handles most of this work. Since this is all new material to most people, I'll go through the initialization process in detail. Fortunately, its not as complicated as it could be. Direct3D 7 actually includes some helper functions to make this even easier - but we'll learn more doing it the hard way! (See the DirectX 7 SDK for details of the easy way to do this).

The first step in initializing Direct3D is to query DirectDraw for an interface (ReportError is part of my DXEngine class - it displays a message box):

LPDIRECT3D3 Direct3D; if (FAILED(DirectDraw->QueryInterface(IID_IDirect3D3,(LPVOID *)&Direct3D))) { ReportError("Direct3D Query Failed"); };

The second step is to enumerate 3D devices. This is done with the FindDevice command (part of the Direct3D interface). The following code looks for hardware accelerated Direct3D devices:

D3DFINDDEVICESEARCH search; D3DFINDDEVICERESULT result; memset(&search,0,sizeof(search)); search.dwSize = sizeof(search); search.bHardware = 1; memset(&result,0,sizeof(result)); result.dwSize = sizeof(result); if (FAILED(Direct3D->FindDevice(&search,&result))) { ReportError("3D Hardware Not Found!"); exit(10); };

Assuming that this found a device, the next stage is to go ahead and create it. This can be as simple as calling CreateDevice (again, part of the Direct3D interface). The following line of code searches for a hardware accelerated device:

LPDIRECT3DDEVICE3 D3DDevice; Direct3D->CreateDevice(IID_IDirect3DHALDevice,BBuffer,&D3DDevice,NULL);

In my code, I actually expand on this somewhat; if a HAL device isn't found, DXEngine will look for an MMX device, then a regular RGB (software emulated) device. This isn't strictly necessary, though. The final stage in starting up Direct3D involves the creation of a viewport. Viewports tell Direct3D how to render the world. Part of this structure sets up 3D clipping - and is only really of use if you are using Direct3D's transformation functions. It's of no use to this article, so we can ignore it. The important part is this:

LPDIRECT3DVIEWPORT3 Viewport; D3DVIEWPORT2 Viewdata; memset(&Viewdata,0,sizeof(Viewdata)); Viewdata.dwSize = sizeof(Viewdata); Viewdata.dwWidth = ScreenWidth; Viewdata.dwHeight = ScreenHeight; // Create the viewport if (FAILED(Direct3D->CreateViewport(&Viewport,NULL))) { ReportError("Failed to create a viewport"); }; if (FAILED(D3DDevice->AddViewport(Viewport))) { ReportError("Failed to add a viewport"); }; if (FAILED(Viewport->SetViewport2(&Viewdata))) { ReportError("Failed to set Viewport data"); }; D3DDevice->SetCurrentViewport(Viewport);

What this does is it sets up a viewport structure with the screen size (if you wanted to render a smaller window, you can change these values). Then it creates the viewport, adds it to the device, adds the data to the viewport itself, and tells the device to use it. Overly complicated, if you ask me, but it gets the job done. When you are finished with Direct3D, it's a good idea to release the variables you've allocated:

if (Viewport) Viewport->Release(); if (Direct3D) Direct3D->Release();

If you don't do this, memory leaks are pretty likely. You may also make it impossible to run other Direct3D programs (until you reboot)… not a good idea - especially if you want to keep any fans you might have!





Next : Rendering a Wireframe Isometric View