Rocket is my first Direct2D program. It is graphics only,
no sound.
Rocket runs in a normal Win32 window, so it is no problem to mix
GDI and Direct2D operations and the old familiar Windows messaging
system is still there. The downside to this approach is that the
offscreen image needs to be painted onto the window, which is an
expensive operation. It typically takes 3-4ms to
draw all the objects to the offscreen image, then 16-18ms to paint the
image to the window. This is acceptable for programs where the display
changes infrequently, but not for action games where the refresh rate
is a critical statistic.
UPDATE: The above statements were not entirely correct, the "paint
method" has exactly the same performance as the officially sanctioned,
but more complicated, "SwapChains" method. The timing measurements
were misleading. The "draw" operations were merely updating the sprite
locations and did not paint any pixels. All the graphics operations
were buried inside the paint call. This misled me into thinking the
final bitblt was taking more time. The over-arching frames per second
measurements were identical for both the paint and swap-chains
methods.
The worst of the DirectX stuff is wrapped in the MyD2D class in the
Util project.
The Rocket program consists of 5 major components:
Main: The container for the main window. This handles all
the window messages, player input, and manages the Game and Image
objects.
Game: This class encapsulates all the game logic, including
all the ItemList.
Image: Manages rendering the items to the Direct2D
target.
ItemList: Manages all the game items. This is a simple
list manager.
Item: The individual game items, including the rocket,
bullets, and rocks. The game logic and mechanics for the individual
items are found here.
Main.cpp
The global ghD2D object is created before the window. This performs
all the "factory" creation and initialization for Direct2D.
Main.cpp:
int Main::Create2(void) {
int Err= ERR_OK;
DWORD Style= WS_OVERLAPPEDWINDOW;
DWORD StyleX= WS_EX_APPWINDOW|WS_EX_OVERLAPPEDWINDOW;
RECT rPos= { 100, 100, 700, 500 };
if(IsErr(Err= GameCreate(&hGame))) {
Err= Error(Err,__FUNCTION__": Unable to create Game.");
} else if(IsErr(Err= MyD2DCreate(&ghD2D,ghInst))) {
Err= Error(Err,__FUNCTION__": Unable to create MyD2D Factory.");
} else if(IsErr(CreateClass())) {
Err= Error(ERR_SYSCREATE,__FUNCTION__": Unable to create class '%S'",ClassName);
} else {
hWnd= CreateWindowEx(StyleX,ClassName,WndName,Style,rPos.left,rPos.top,RWID(rPos),RHGT(rPos),0,0,ghInst,this);
if(!IsWindow(hWnd)) {
Err= Error(ERR_SYSCREATE,__FUNCTION__": CreateWindowEx(%S,%S) failed.",ClassName,WndName);
}
}
return(Err);
}
I need to resize the image whenever the window is resized.
I paint the image to the window in response to the WM_PAINT
message. This is also where the image is created. I can't create the
image until the final DC for the window has been created, which I
can't be sure until the first WM_PAINT.
The game is periodically updated in response to a WM_TIMER message.
Main::MsgTimer() calls GameUpdate() to update all the game items. The
window is then invalidated to trigger a WM_PAINT message.
The Game class encapsulates all the game logic. This includes
initializing a new game/level, updating the game items, and
determining when the game/level is over. The Game object contains
the ItemList, Image (created by Main), Arena dimensions, and control
state.
Game.cpp:
HITEMS hItems;
HIMAGE hImg;
RECT rArena;
UINT CtrlState;
This class is pure logic, it performs no graphics operations
directly.
The first thing is to create the ItemList object to manage all the
items. Then the hItems is filled with the initial game items: the
rocket and a bunch of random rocks.
Game.cpp:
int Game::Create2(void) {
int Err= ERR_OK;
if(IsErr(Err= ItemsCreate(&hItems,(HGAME)this))) {
Err= Error(Err,__FUNCTION__": Unable to create Items.");
} else if(IsErr(Err= CreateItems())) {
Err= Error(Err,__FUNCTION__": Unable to create starting items.");
}
return(Err);
}
int Game::CreateItems(void) {
int Err= ERR_OK;
UINT n1;
if(IsErr(Err= CreateItem(ITEM_ROCKET))) {
Err= Error(Err,__FUNCTION__": Unable to create rocket.");
} else {
for(n1=0;n1<ITEM_MAX;n1++) {
if(IsErr(Err= CreateItem(ITEM_ROCK))) {
Err= Warn(Err,__FUNCTION__": Unable to add Rock #%d",n1);
break;
}
}
}
return(Err);
}
CreateItem() is a simple function that creates a new Item and adds
it to the ItemList.
Game.cpp:
int Game::CreateItem(UINT ItemID) {
int Err= ERR_OK;
HITEM hItem;
if(IsErr(Err= ItemCreate(&hItem,ItemID,0))) {
Err= Error(Err,__FUNCTION__": Unable to create ItemID %d",ItemID);
} else if(IsErr(Err= ItemsAdd(hItems,hItem))) {
Err= Error(Err,__FUNCTION__": Unable to add ItemID %d",ItemID);
}
return(Err);
}
When the Main object creates the Image, it needs to share it with
Game so it can be used during the GameUpdate() calls.
Game.cpp:
int Game::SetImage(HIMAGE hImg) {
int Err= ERR_OK;
this->hImg= hImg;
return(Err);
}
Game needs to know when the size of the Arena has changed so Game
can share that information with ItemsList where it is used in the
update logic for the individual Items.
Game.cpp:
int Game::SetArena(const RECT *pR) {
int Err= ERR_OK;
rArena= *pR;
ItemsSetArena(hItems,&rArena);
return(Err);
}
Game translates user inputs from keystrokes into control state
flags, which are used during the game updates.
Game.cpp:
#define SETBIT(Var,Bit,OnOff) { if(OnOff) { Var|= Bit; } else { Var&= ~Bit; } }
int Game::SetControl(UINT PlayerID, UINT Key, UINT OnOff) {
int Err= ERR_OK;
switch(Key) {
case VK_LEFT: SETBIT(CtrlState,CTRL_LEFT,OnOff); break;
case VK_RIGHT: SETBIT(CtrlState,CTRL_RIGHT,OnOff); break;
case VK_DOWN: SETBIT(CtrlState,CTRL_THRUST,OnOff); break;
case VK_SPACE: SETBIT(CtrlState,CTRL_FIRE,OnOff); break;
}
return(Err);
}
The Game is updated by telling ItemList to update all the items,
resetting the Image, drawing all the items to the Image, and
finalizing the Image.
Game.cpp:
int Game::Update(void) {
int Err= ERR_OK;
ItemsUpdate(hItems,GetTickCount());
ImgBegin(hImg);
ItemsDraw(hItems,hImg);
ImgEnd(hImg);
return(Err);
}
Image.cpp
The Image performs all the graphics operations needed to compose
each displayed video frame.
WebV7 (C)2018 nlited | Rendered by tikope in 73.169ms | 18.222.161.119