Sign in to follow this  

Correct design for a DirectX Engine

This topic is 3718 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi ! I'm making a 2D DirectX Engine and I'm looking for a correct design. I have currently a problem with circular include. The Design is something like this : I've a class D3DManagement who manage things like window creation, drawing, message loop, setting matrices ... A class named GameManager who manage the game logic: input, rules ... (All of theses classes are singletons) I'm trying to make a little tank game so I've a class called Tank who is derived of a Sprite class (a layer who manage 2d textured sprite). Without the Tank class, everything works. The error is :
Quote:
[...] 1>Sprite.cpp 1>c:\documents and settings\tom\mes documents\visual studio 2005\projects\emptyproject\tank.h(12) : error C2504: 'Sprite' : base class undefined [...]
GameManager.h (singleton)
class D3DManagement;
class Tank;

#include "D3DManagement.h"
#include "Tank.h"

class GameManager
{
Tank *tank;
//...
};

D3DManagement.h (singleton)
class GameManager;

#include "GameManager.h"

class D3DManagement;
{
//...
};

Sprite.h
#include "D3DManagement.h"

class Sprite
{
//...
};

Tank.h
class Sprite;

#include "Sprite.h"

class Tank : public Sprite
{
//...
};

Thanks in advance. [Edited by - Kr00pS on October 10, 2007 10:06:15 AM]

Share this post


Link to post
Share on other sites
You have a circular dependancy. Tank.h includes Sprite.h which includes GameManager.h which includes D3DManagement.h which includes Tank.h.

You need to fix that loop somehow, by removing one of the includes.

Share this post


Link to post
Share on other sites
Hum I know, but I can't remove any include.

D3DManagement has the Direct3D device. I want to separate the lib (DirectX) and the game, but I can't with this design. :/

D3DManagement (Direct3D: draw, matrices, etc) and GameManager (gameloop, init game) have dependencies together.

Share this post


Link to post
Share on other sites
Oh whoops - I didn't notice you knew there was a circular include, sorry.

To me it looks like D3DManagement.h shouldn't include GameManagement.h - why does the lower level D3D related class need to know about the higher level game related class?

Share this post


Link to post
Share on other sites
Use pointers (or references), forward-declare your classes in your .h files and include files only in cpp if possible.

In your example :

class D3DManagement;
class Tank;

#include "D3DManagement.h"
#include "Tank.h"

class GameManager
{
Tank *tank;
//...
};



First, forward-declaring and THEN including is useless.
Second, as your working only with pointer to tanks, only forward declare and include in GameManager.cpp :

// GameManager.h
class D3DManagement;
class Tank;

class GameManager
{
Tank *tank;
//...
};

// GameManager.cpp
#include "GameManager.h"

#include "D3DManagement.h"
#include "Tank.h"

// do stuff that uses Tank and D3DManagement here

Share this post


Link to post
Share on other sites
Quote:
Original post by Evil Steve

To me it looks like D3DManagement.h shouldn't include GameManagement.h - why does the lower level D3D related class need to know about the higher level game related class?


The D3DManagement need GameManager because my design is crap. I don't know how to separate properly the lower layer of the higher layer. Look at theses files:

D3DManagement.h
#pragma once

#include <d3dx9.h>
#include <iostream>
#include <sstream>

class GameManager;

#include "GameManager.h"
#include "Logger.h"

using namespace std;

#define D3DSAFE_RELEASE(p) if (p != NULL) { p->Release(); }

class D3DManagement
{
private:
LPDIRECT3D9 m_D3DObject;
LPDIRECT3DDEVICE9 m_D3DDevice;

HWND m_MainWindowHandle;

int m_Width;
int m_Height;

bool m_Keys[256];

// ------------------------------------------------------------------------------------------------
D3DManagement() {}
~D3DManagement() {}
// ------------------------------------------------------------------------------------------------

public:
// ------------------------------------------------------------------------------------------------
static D3DManagement *GetInstance()
{
static D3DManagement instance;
return &instance;
}
// ------------------------------------------------------------------------------------------------

HRESULT BuildWindow(string name, int width, int height);

HRESULT InitD3D();
void MessageLoop();
void Draw();
void Cleanup();

void SetMatrices();

LPDIRECT3DDEVICE9 GetDevice();
HWND GetHandle();

bool GetKey(int key);
void SetKey(int key, bool value);
};

LRESULT CALLBACK MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);




D3DManagement.cpp
#include "D3DManagement.h"

const int FPS = 100;
const int FRAMERATE = FPS/1000;

LRESULT CALLBACK MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;

case WM_SETCURSOR:
SetCursor(NULL);
//D3DManagement::GetInstance()->GetDevice()->ShowCursor(FALSE);
return 0;

case WM_KEYDOWN:
if(wParam == VK_ESCAPE)
{
GameManager::GetInstance()->SetGameOver(true);
PostQuitMessage(0);
return 0;
}


D3DManagement::GetInstance()->SetKey((int)wParam, true);

return 0;


case WM_KEYUP:
D3DManagement::GetInstance()->SetKey((int)wParam, false);

return 0;
}

return DefWindowProc(hwnd, msg, wParam, lParam);
}

bool D3DManagement::GetKey(int key)
{
return m_Keys[key];
}

void D3DManagement::SetKey(int key, bool value)
{
m_Keys[key] = value;
}

HRESULT D3DManagement::BuildWindow(string name, int width, int height)
{
m_Width = width;
m_Height = height;

WNDCLASSEX wc = {sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, MsgProc, 0L, 0L, GetModuleHandle(NULL),
NULL, LoadCursor(NULL, IDC_ARROW), NULL, NULL, "D3DM", NULL};

if(!RegisterClassEx(&wc))
{
MSG_ERROR("Window registration failed");
return E_FAIL;
}

MSG_SUCCESS("Windows registration succeeded");

m_MainWindowHandle = CreateWindow("D3DM", "Direct3D Test", WS_POPUP | WS_SYSMENU | WS_VISIBLE, 100, 100,
m_Width, m_Height, GetDesktopWindow(), NULL, wc.hInstance, NULL);

if (m_MainWindowHandle == NULL)
{
MSG_ERROR("Window creation failed");
return E_FAIL;
}

MSG_SUCCESS("Windows creation succeeded");

ShowWindow(m_MainWindowHandle, SW_SHOW);
UpdateWindow(m_MainWindowHandle);


for (int i = 0; i < 256; i++)
{
m_Keys[i] = false;
}


return S_OK;
}

void D3DManagement::MessageLoop()
{
float last_time = timeGetTime();

MSG msg;

PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);

while (msg.message != WM_QUIT)
{
float current_time = timeGetTime();
float delta_time = (current_time - last_time) * 0.001f;

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);

GameManager::GetInstance()->ManageInput();
}

if ((current_time - last_time) >= FRAMERATE)
{
GameManager::GetInstance()->ManageGame();

Draw();

last_time = current_time;
}
}
}

HRESULT D3DManagement::InitD3D()
{
if (((m_D3DObject = Direct3DCreate9(D3D_SDK_VERSION)) == NULL))
{
MSG_ERROR("Can't create Direct3D object");
return E_FAIL;
}

MSG_SUCCESS("Direct3D object creation succeeded");

D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));

d3dpp.Windowed = TRUE;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferWidth = m_Width;
d3dpp.BackBufferHeight = m_Height;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

if (FAILED(m_D3DObject->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_MainWindowHandle,
D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &m_D3DDevice)))
{
MSG_ERROR("Can't create Direct3D device");
return E_FAIL;
}

MSG_SUCCESS("Direct3D device creation succeeded");

m_D3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
m_D3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

// Desactivation du Z buffer
m_D3DDevice->SetRenderState(D3DRS_ZWRITEENABLE, false);

SetMatrices();

return S_OK;
}

void D3DManagement::Draw()
{
m_D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

m_D3DDevice->BeginScene();

GameManager::GetInstance()->Draw();

m_D3DDevice->EndScene();

m_D3DDevice->Present(NULL, NULL, NULL, NULL);
}

void D3DManagement::Cleanup()
{
D3DSAFE_RELEASE(m_D3DObject);
D3DSAFE_RELEASE(m_D3DDevice);
}

void D3DManagement::SetMatrices()
{
D3DXMATRIX matWorld;
D3DXMatrixTranslation(&matWorld, 2, 2, 0);
m_D3DDevice->SetTransform(D3DTS_WORLD, &matWorld);

// ------------------------------------------------------------------------------------------------
D3DXVECTOR3 vecEyePt (0.0f, 3.0f, 0.0f);
D3DXVECTOR3 vecLookatPt (0.0f, 0.0f, 0.0f);
D3DXVECTOR3 vecUpVec (0.0f, 1.0f, 0.0f);

D3DXMATRIXA16 matView;

D3DXMatrixLookAtLH(&matView, &vecEyePt, &vecLookatPt, &vecUpVec);
m_D3DDevice->SetTransform(D3DTS_VIEW, &matView);

// ------------------------------------------------------------------------------------------------

D3DXMATRIX matOrtho;
D3DXMatrixOrthoLH(&matOrtho, D3DX_PI/4, 1.0f, 1.0f, 100.0f);
m_D3DDevice->SetTransform(D3DTS_PROJECTION, &matOrtho);
}

LPDIRECT3DDEVICE9 D3DManagement::GetDevice()
{
return m_D3DDevice;
}

HWND D3DManagement::GetHandle()
{
return m_MainWindowHandle;
}





GameManager.h

#pragma once

class D3DManagement;
class Sprite;
class Tank;

#include "D3DManagement.h"
#include "Sprite.h"
#include "Tank.h"


#include "TextManager.h"

class GameManager
{
private:
Tank *m_Player;

bool m_GameOver;

// ------------------------------------------------------------------------------------------------
GameManager() {}
~GameManager() {}
// ------------------------------------------------------------------------------------------------

public:
// ------------------------------------------------------------------------------------------------
static GameManager *GetInstance()
{
static GameManager instance;
return &instance;
}
// ------------------------------------------------------------------------------------------------

void Init();
void Destroy();

void Draw();

void GameLoop();

void ManageGame();
void ManageInput();

void SetGameOver(bool value);
bool IsGameOver();
};




GameManager.cpp

#include "GameManager.h"

void GameManager::Init()
{
D3DManagement::GetInstance()->BuildWindow("D3DApp", 1024, 768);

D3DManagement::GetInstance()->InitD3D();

//D3DManagement::GetInstance()->Init();

TextManager::GetInstance()->Init();

m_Player = new Tank("tank.bmp", 300, 200);

m_GameOver = false;
}

void GameManager::Destroy()
{
TextManager::GetInstance()->Destroy();

D3DManagement::GetInstance()->Cleanup();

delete m_Player;
}

void GameManager::GameLoop()
{
while (m_GameOver != true)
{
D3DManagement::GetInstance()->MessageLoop();
}
}

void GameManager::Draw()
{
m_Player->Draw();

TextManager::GetInstance()->DrawMessage("Tank Battle 0.1a", 10, 10, D3DCOLOR_XRGB(255,255,255));
}

void GameManager::ManageGame()
{
m_Player->Update();
}

void GameManager::ManageInput()
{
if (D3DManagement::GetInstance()->GetKey(VK_UP))
{
m_Player->DecreasePosition(false, true, 32);
}

if (D3DManagement::GetInstance()->GetKey(VK_DOWN))
{
m_Player->IncreasePosition(false, true, 32);
}

if (D3DManagement::GetInstance()->GetKey(VK_LEFT))
{
m_Player->DecreasePosition(true, false, 32);
}

if (D3DManagement::GetInstance()->GetKey(VK_RIGHT))
{
m_Player->IncreasePosition(true, false, 32);
}
}

void GameManager::SetGameOver(bool value)
{
m_GameOver = value;
}

bool GameManager::IsGameOver()
{
return m_GameOver;
}


Share this post


Link to post
Share on other sites
As I can see, 'GameManager.h' doesn't even use 'D3DManagement', 'GameManager.cpp' does.

Remove both 'class D3DManagement' and '#include "D3DManagement.h"' from 'GameManager.h' and add only '#include "D3DManagement.h"' to the beginning of 'GameManager.cpp'.

Do the same thing for Tank, except that you should keep 'class Tank' in 'GameManager.h' because this files declares a pointer to a Tank.

I haven't looked at all your code but I'm sure you can also do this elsewhere. The rule is :
(class X is located in X.h)
- If a .h header uses in any way an instance (non-pointer) of X or accesses X's members (via a pointer or not), it needs to include X.h
- If a .h header uses only pointers or references to an X and does not accesses X's members, it needs to forward declare class X
- If a .cpp source file uses in any way an instance (non-pointer) of X or accesses X's members (via a pointer or not) AND the .h header file did not include X.h, it must include X.h

Anyways I think there's some nice tutorial on organising C++ code files in the ressources section of this website, check it out.

Share this post


Link to post
Share on other sites
Rule of thumb: do NOT include header files in headers.

Some exceptions apply to the above statement, but most of the time you don't need it.

First thing, when you go about designing your architecture, think about what you require and how you intend to use it. So far I've not run in to a single problem I couldn't solve by just organizing my headers.

Inclusion guards, never needed them, and probably never will...

Forward declarations are great, but don't overdo it!

Share this post


Link to post
Share on other sites
My design thought:

Dont inherit Tank from Sprite.

Why?

Is tank a sprite in reality? No it isnt. Sprite is mere representation (possibly one of many) of the tank entity itself. Prefer containment to inheritance and your code will be more flexible.

You can also add TankController class later which can be user input driven or AI!

Nice article about game obejct design is here (and guess what - their example is a tank! :o)
http://www.gamasutra.com/features/20050414/rouwe_pfv.htm

MaR

Share this post


Link to post
Share on other sites
I was going to say the same thing as Mar_dev. You should think about using composition rather then inheriting directly from sprite.

One trick I find works well is when you're designing an inheriting class is to ask yourself if the IS A makes sense. So in your example, tank IS A sprite, this doesn't really make sense because a tank isn't a sprite, it has a visual representation like the armor and the wheels, but it's not really a sprite.

Always important to seperate the logic regarding an object from the actual visual representation of this data. Another way to look at it is when you're cycling through your list of renderable objects, does it really make sense to be rendering a class which has logic in it to move and shoot?

Don't sweat it too much though, design is something that takes a lot of time to start to begin to become good with. Just study some of the various design patterns and how they are applied with games and it will become easier with time.

One extra note, avoid using singletons in your code base, they lead to lots of problems further down the road. They may seem to work well in smaller projects but as your project grows you will find they really get in the way and cause more problems then they are worth.

Share this post


Link to post
Share on other sites

This topic is 3718 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this