如果说你想要设计出一个可玩性较高的游戏,那么游戏引擎你必须要了解。因为往往好的游戏背后都有个一强大的游戏引擎,接下来我们就一步一步来走进游戏引擎。
首先我们来认识一下什么是游戏引擎,一句话简单的说,游戏引擎就是一组执行游戏中的公共任务的程序代码。考虑一下你所玩过的游戏,尝试设想一下他们的内部是如何设计的。如果您足够细心你就会发现,其实所有的游戏都有公共的设计元素。比如每种游戏都会有背景、片头屏幕、地形图以及背景音乐。这些可以说是公共元素,当你想要创建多个游戏,但是你又不想每次都做重复的工作,那么游戏引擎就显得十分重要了。
这样不免有人有疑问了,难道游戏引擎只是解决重复性工作吗?它还重要在什么地方呢?或者说它有什么还有好处值得我们去设计呢?
首先游戏引擎对于Windows游戏的另一个重要的好处在于,它允许隐藏针对Windows而与游戏设计无关的繁杂代码。一个游戏代码很多内容实质上是与游戏本身无关,但是这些代码又是每一个Windows应用程序所必须的。相对于通过剪切和复制这些常规代码来创建新游戏,大家难道不觉得如果可以将这些代码隐藏在游戏引擎之中,以后不需要在管它,不是更加方便吗?我们只需要知道它在哪里,但是却不需要去查看它,我们可以把更多精力放在游戏代码的更重要而且更有趣的部分,不是更好么?
说白了,游戏引擎很好地组织了游戏的代码,这样常规的应用程序任务就与游戏特有的任务分离开来了。它为游戏开发人员带来的好处是可以向游戏引擎中添加能够在以后所有的游戏中重用的特性。此外,使用游戏引擎还允许简化游戏的代码。
如果你有一个自己设计优良的游戏引擎,那么你就会发现,开发游戏的所需代码比不使用游戏引擎少的多,以后你若是开发了某些核心程序,你只需要将它们粘贴在你的游戏引擎之中就可以了。
例如以下事件可以适用于任何游戏的部分核心事件:
1)初始化 2)启动 3)结束 4)激活 5)停用 6)绘制 7)循环
既然讲到了游戏周期的概念,那么就不得不提一下游戏周期的概念,而游戏周期又不得不依赖游戏的计时机制,所谓的游戏计时机制就是所有的游戏都会都会依赖某种计时,使游戏将其执行分解为帧或者周期(当然有些很简单的游戏可能不需要计时机制),游戏的一个周期相当于一个时间片,通常对应于游戏图形和数据的一个快照。在任何给定的周期之中,游戏完成了更新其图形、执行其他任何角色和对象如何移动及彼此交互的计算和处理等工作。
下面就来一步一步设计一个游戏引擎:
首先第一步就是创建前面提到的游戏事件对应的处理程序。当游戏生成一个事件的时候,就会调用一个相对应的事件处理函数,使游戏做出相应的响应。比如下列函数:
BOOL GameInitialize(HINSTANCE hInstance);
void GameStart(HWND hWindow);
void GameEnd();
void GameActivate(HWND hWindow);
void GameDeactivate(HWND hWindow);
void GamePaint(HDC hdc);
void GameCycle();
注意一下第一个函数传递的参数是一个hInstance,它是以一个实例句柄,这是一种Win32数据类型,引用了一个应用程序的实例。
另外这些函数的特定实现是游戏特有的,必须由使用该游戏引擎的各个游戏提供。我们这次先关注游戏引擎,所以这次先不对里面的这些游戏函数做过多介绍。
接下来就是引擎类的设计:(游戏事件处理程序与游戏引擎之间存在紧密的联系,但是两者实际上是独立的。因为就其结构而言,将游戏引擎放在它自己的C++类中显然会更好。)
class GameEngine
{
protected:
//成员变量
static GameEngine * m_pGameEngine;
HINSTANCE m_hInstance;
HWND m_hWindow;
TCHAR m_szWindowClass[MAX_LOADSTRING]; //窗口类名称
TCHAR m_szTitle[MAX_LOADSTRING]; //主游戏窗口名称
WORD m_wIcon, m_wSmallIcon; //图标
int m_iWidth, m_iHeight; //窗口大小
int m_iFrameDelay;
BOOL m_bSleep;
public:
//构造函数/析构函数
GameEngine(HINSTANCE hInstance, LPTSTR szWindowclass, LPTSTR szTitle, WORD wIcon, WORD wSmallIcon, int iWidth = 640, int iHeight = 480);
virtual ~GameEngine();
//常规方法
static GameEngine * GetEngine(){ return m_pGameEngine; };
BOOL Initialize(int iCmdShow);
LRESULT HandleEvent(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam);
//访问方法
HINSTANCE GetInstance(){ return m_hInstance; };
HWND GetWindow(){ return m_hWindow; };
void SetWindow(HWND hWindow){ m_hWindow = hWindow; };
LPTSTR GetTitle(){ return m_szTitle; };
WORD GetIcon(){ return m_wIcon; };
WORD GetSmallIcon(){ return m_wSmallIcon; };
int GetWidth(){ return m_iWidth; };
int GetHeight(){ return m_iHeight; };
int GetFrameDelay(){ return m_iFrameDelay; };
void SetFrameRate(int iFrameRate){ m_iFrameDelay = 1000 / iFrameRate; };
BOOL GetSleep(){ return m_bSleep; };
void SetSleep(BOOL BSleep){ m_bSleep = BSleep; };
};
既然这是一个类,那么它就具有类的特征,它有自己的成员变量和自己的成员函数,首先先来介绍一下里面的成员变量,我们应该在它的类中定义了一个
指向自身的静态指针m_pGameEngine
,它主要用于游戏程序设计的外部访问。使用m_hIstance和m_hWindow成员变量将游戏程序的实例和主窗口句柄存贮在游戏引擎之中。窗口类的名称和主游戏窗口的名称存储在m_szWindowClass和m_szTitle成员变量之中,游戏的两个程序图标的数字ID存储在m_wIcon和m_wSmallIcon成员之中。游戏屏幕的宽度和高度存储在m_iWidth和m_iHeight成员之中。但是要注意,这个宽度和高度是游戏屏幕(或者游戏区)的大小而不是整个程序窗口的大小,整个程序窗口要更大一些,它还包括了边框、标题栏、菜单等等。m_iFrameDelay这和成员变量表示游戏周期之间的间隔的时间(单位是:ms)。最后m_bSleep 是一个布尔成员变量,表示游戏是否正在休眠(暂停)。
接下来就是引擎类之中其他成员函数的具体实现了。首先是构造函数和析构函数:
//
//GameEngine的构造函数/析构函数
//
GameEngine::GameEngine(HINSTANCE hInstance, LPTSTR szWindowClass, LPTSTR szTitle, WORD wIcon, WORD wSmallIcon, int iWidth, int iHeight)
{
//设置游戏引擎的成员变量
m_pGameEngine = this;
m_hInstance = hInstance;
m_hWindow = NULL;
if (lstrlen(szWindowClass) > 0)
{
lstrcpy(m_szWindowClass, szWindowClass);
}
if (lstrlen(szTitle) > 0)
{
lstrcpy(m_szTitle, szTitle);
}
m_wIcon = wIcon;
m_wSmallIcon = wSmallIcon;
m_iHeight = iHeight;
m_iWidth = iWidth;
m_iFrameDelay = 20; //默认为20帧每秒
m_bSleep = TRUE;
}
//析构函数
GameEngine::~GameEngine()
{}
接下来是Initialize()函数,注意一下这个函数不是最开始讲的GameInitialize()函数,在WinMain()中调用的第一个函数是GameInitialize()函数,这是一个游戏事件函数,属于这个游戏的专用代码,而不是游戏引擎的直接组成部分。而initialize()则是游戏引擎部分的方法,调用它可以用来初始化游戏引擎自身。
int GameEngine::Initialize(int iCmdLine)
{
//注册窗口类
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = m_hInstance;
wndclass.hIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(GetIcon()));
wndclass.hIconSm = LoadIcon(m_hInstance, MAKEINTRESOURCE(GetSmallIcon()));
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = m_szWindowClass;
if (!RegisterClassEx(&wndclass))
{
MessageBox(m_hWindow, L"注册窗口失败!", L"警告", MB_OK);
return FALSE;
}
//根据游戏的大小计算窗口大小和位置
int iWindowWidth = m_iWidth + GetSystemMetrics(SM_CXFIXEDFRAME) * 2;
int iWindowHeight = m_iHeight + GetSystemMetrics(SM_CYFIXEDFRAME) * 2 + GetSystemMetrics(SM_CYCAPTION);
if (wndclass.lpszMenuName != NULL)
{
iWindowHeight += GetSystemMetrics(SM_CYMENU);
}
int iXWindowPos = (GetSystemMetrics(SM_CXSCREEN) - iWindowWidth) / 2;
int iYWindowPos = (GetSystemMetrics(SM_CYSCREEN) - iWindowHeight) / 2;
//创建窗口
m_hWindow = CreateWindowEx(NULL, m_szWindowClass, m_szTitle, WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX, iXWindowPos, iYWindowPos, iWindowWidth, iWindowHeight, NULL, NULL, m_hInstance, NULL);
if (!m_hWindow)
{
MessageBox(m_hWindow, L"创建窗口失败!", L"警告", MB_OK);
return FALSE;
}
//更新和显示窗口
ShowWindow(m_hWindow,iCmdLine);
UpdateWindow(m_hWindow);
return TRUE;
}
再接着是游戏事件处理函数,实际上它是被我们熟知的Win32中的WndProc()函数所调用的,为什么我们不把代码直接写在WndProc()函数之中呢?虽然这看起来是浪费时间,但是我们这样就可以允许GameEngine类的方法处理消息,可以使用一种与游戏引擎一致的方法处理它们。来看看代码:
LRESULT GameEngine::HandleEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
//将Windows消息传递给游戏引擎的成员函数
switch (msg)
{
case WM_CREATE:
//设置游戏窗口并开始游戏
SetWindow(hWnd);
GameStart(hWnd);
return 0;
case WM_SETFOCUS:
//激活游戏并更新休眠状态
GameActivate(hWnd);
SetSleep(FALSE);
return 0;
case WM_KILLFOCUS:
GameDeactivate(hWnd);
SetSleep(TRUE);
return 0;
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(hWnd, &ps);
//绘制游戏
GamePaint(hdc);
EndPaint(hWnd, &ps);
return 0;
case WM_DESTROY:
//结束游戏并退出应用程序
GameEnd();
PostQuitMessage(0);
return 0;
default:
return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
最后我们来看一下如何才能在Win32的WinMain()之中来使用它们呢?来看看WinMain()函数:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int iCmdShow)
{
MSG msg;
static int iTickTrigger = 0;
int iTickCount;
if (GameInitialize(hInstance))
{
//初始化游戏引擎
if (!GameEngine::GetEngine()->Initialize(iCmdShow))
{
return FALSE;
}
//进入主消息循环
while (true)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
//处理消息
if (WM_QUIT == msg.message)
{
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//else
//{
if (!GameEngine::GetEngine()->GetSleep())
{
//检查滴答计数,看看是否过了一个周期
iTickCount = GetTickCount();
if (iTickCount - iTickTrigger)
{
iTickTrigger = iTickCount + GameEngine::GetEngine()->GetFrameDelay();
GameCycle();
}
}
//}
}
return (int)msg.wParam;
}
GameEnd();
return true;
}
当然还有我们的WndProc()函数,这里的WndProc()函数比较简单,实际上它要做的事情就是将所有消息都传递给HandleEvent()即可。
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
//将所有的Windows消息都传递给游戏引擎
return GameEngine::GetEngine()->HandleEvent( hWnd, msg, wParam, lParam);
}
以上就是一个游戏引擎的制作了,相当于提供了一个模板,,当然这只是一个初级的游戏引擎,你还可以在这基础上在添一些内容,使你的游戏引擎更加的丰富。