dev.nlited.com

>>

Rocket

<<<< prev
next >>>>

2016-01-23 18:43:12 chip Page 1533 📢 PUBLIC

Rocket is my first Direct2D program. It is graphics only, no sound.

Rocket Direct2D

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:

  1. Main: The container for the main window. This handles all the window messages, player input, and manages the Game and Image objects.
  2. Game: This class encapsulates all the game logic, including all the ItemList.
  3. Image: Manages rendering the items to the Direct2D target.
  4. ItemList: Manages all the game items. This is a simple list manager.
  5. 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.


Main.cpp: LRESULT Main::MsgResized(void) { GetClientRect(hWnd,&rClnt); GameSetArena(hGame,&rClnt); ImgResize(hImg,&rClnt); return(0); }

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.


Main.cpp: LRESULT Main::MsgPaint(void) { if(!hImg) { ImgCreate(&hImg,hWnd); ImgResize(hImg,&rClnt); GameSetImage(hGame,hImg); } ImgPaint(hImg); ValidateRect(hWnd,0); return(0); }

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.


Main.cpp: LRESULT Main::MsgTimer(void) { GameUpdate(hGame); InvalidateRect(hWnd,0,0); return(0); }

Game.cpp

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 167.758ms | 3.141.42.41