资源描述
Windows游戏编程快速入门方法
Easideao(简单思路)
序言:
从2001年到2005年,在不知不觉中我已经渡过了4年的职业游戏开发生涯。在这4年里经常会有些网友向我询问编程的入门有没有捷径。每一次我都不知怎么回答才算合适,我也一直想表达一下我的思路和想法,但一直都没有能力把自己的见解在书面上表达出来,其实我认为编写程序并不是很难的事情。最关键的是你对他是否有兴趣,最难的是坚持学习。如果没有兴趣,即使你刚刚入了一点们如果不坚持下去,也是一事无成。
虽然毅力在学习的过程中有着不可置疑的位置,但是有个合适的方法和适合自己的方法还是很重要的。假如你的兴趣和毅力都过了关,我接下来将以一个游戏的代码编写过程写下来,我坚持写下来,你坚持读完,按照我讲述的步骤去做。我这里不会把所有细节都讲述出来,因为那是太庞大的任务,我的力量无法实现,我们下面的方法就是:我说怎么做,你就怎么做,先知道怎样做一些事情,当你能够按照我说的做出正确的结果说明你已经会了,如果有不懂得再去查看相关资料。
上面说的有些繁琐,我自己也不太愿意写下去了,我的文笔水平有限,请大家谅解。接下来最重要的就是跟着我做。如果你有什么意见或问题可以给我发E-mail : chinagdh@。
第一章 Windows程序
1. 打开Visual Studio ,选择File -> Blank Solution。
2. 在Name栏里输入 BattleCity 并按 ok 按钮, 按browse选择解决方案存放位置
3.在Solution Explorer 里在 Solution ‘BattleCity’上按右键。在下拉菜单中选择 Add -> New Project。
4.在Add New Project 对话框里选择 Visual C++ Projects -> Win32 -> Win32 Project,在Name栏里打入 Tank 并按回车
5.选择Application Settings 并在 Empty project 前面打钩,创建一个空的Win32 项目。
6.在Tank项目上按右键 选择Add -> New Folder 增加文件夹,并命名WinApp
7.在WinApp文件夹上按右键 选择Add -> Add New Item
8.选择Visual C++ -> C++ File(.cpp) 在Name栏里输入 WinApp.cpp。
9.反复7.8步 增加 WinApp.h AppEntry.cpp AppEntry.h
10.双击 WinApp.h 打开文件 我们在WinApp.h头文件中加入以下代码
11.以同样的方法处理AppEntry.h , 这种方法保证头文件只被include一次,这是我喜欢用的一种方法也可以在第一行写#pragma once
12.打开AppEntry.h 加入代码 #include <windows.h>
13.打开WinApp.h 加入代码 #include "AppEntry.h"
14.打开WinApp.cpp 加入代码 #include "WinApp.h"
15.定义主程序句柄和主窗口句柄
16.增加获得主程序句柄和主窗口句柄的全局函数
17. 为方便以后获得主程序句柄和主窗口句柄 在 WinApp.h 中声明
HINSTANCE GetAppHandle();
HWND GetMainWnd();
18.定义Windows程序主函数,这是一个Windows程序的入口函数,我们认为程序从此函数开始执行。
19.在Solution Explorer 中选择Tank项目,按右键选择Build 编译一下,看程序是否可以编译。
编译成功会在Output窗口中出现提示信息 Build:1 succeeded, 0 failed, 0 skipped 表示 成功一个 0 失败 0 跳过
20.设置Tank生成的路径,在项目Tank上按右键选择Properties
21.选择程序生成路径(Output File)为 ../RunTime/Tank.exe
22.选择运行路径(Working Directory) 为 ../RunTime 在编译一下
23.增加必要的几个函数 程序的初始化 结束 主循环 消息处理函数,代码如下
#include "WinApp.h"
// 定义主程序句柄
HINSTANCE g_hTheApp = NULL;
// 定义主窗口句柄
HWND g_hMainWnd = NULL;
//////////////////////////////////////////////////////////////////////////
// 获得主程序句柄
HINSTANCE GetAppHandle()
{
return g_hTheApp;
}
// 获得主窗口句柄
HWND GetMainWnd()
{
return g_hMainWnd;
}
//////////////////////////////////////////////////////////////////////////
// Windows程序初始化
HWND AppInit( HINSTANCE hInstance, int nShowCmd );
// Windows程序结束
void AppTerm();
// Windows消息循环和主循环
int AppMsgLoop();
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );
//////////////////////////////////////////////////////////////////////////
// Windows程序主函数
int WINAPI WinMain(
IN HINSTANCE hInstance,
IN HINSTANCE hPrevInstance,
IN LPSTR lpCmdLine,
IN int nShowCmd
)
{
// 保存主程序句柄
g_hTheApp = hInstance;
// 初始化Window窗口和程序
g_hMainWnd = AppInit( g_hTheApp, nShowCmd );
if( g_hMainWnd == NULL )
{
MessageBox( NULL, "Can't Create Main Window", "Error", MB_OK );
return 0;
}
// 进入主消息循环
return AppMsgLoop();
}
// Windows程序初始化
HWND AppInit( HINSTANCE hInstance, int nShowCmd )
{
return NULL;
}
// Windows程序结束
void AppTerm()
{
}
// Windows消息循环和主循环
int AppMsgLoop()
{
return 0;
}
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
return DefWindowProc( hWnd, msg, wParam, lParam );
}
24.在AppInit()函数里注册程序和创建主窗口
// Windows程序初始化
HWND AppInit( HINSTANCE hInstance, int nShowCmd )
{
// 程序实例类名
static char szWindowsClassName[] = "TankBattleCity";
static int iWindowWidth = 800; // 窗口宽度
static int iWindowHeight = 600; // 窗口高度
// 注册窗口类
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
// 设置主窗口消息处理函数
wc.lpfnWndProc = AppWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( 0, IDI_APPLICATION );
wc.hCursor = LoadCursor( 0, IDC_ARROW );
wc.hbrBackground = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));
wc.lpszMenuName = 0;
wc.lpszClassName = szWindowsClassName;
if( !RegisterClass( &wc ) )
{
MessageBox( NULL, "RegisterClass - Failed", "Error", MB_OK );
return false;
}
// 创建主窗口
HWND hWnd = CreateWindow( szWindowsClassName
, szWindowsClassName
, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
, CW_USEDEFAULT
, CW_USEDEFAULT
, iWindowWidth
, iWindowHeight
, 0
, 0
, hInstance
, 0 );
if( NULL == hWnd )
{
MessageBox( NULL, "Create Main Window - Failed", "Error", MB_OK );
return NULL;
}
// 显示主窗口
ShowWindow( hWnd, SW_SHOW );
// 更新主窗口显示内容
UpdateWindow( hWnd );
return hWnd;
}
25.编写AppMsgLoop() 的程序主循环
// Windows消息循环和主循环
int AppMsgLoop()
{
MSG msg;
ZeroMemory( &msg, sizeof(MSG) );
_AppLoop:
// 检测是否有消息要处理
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
// 如果接收不到消息表示要退出程序
if( !GetMessage( &msg, NULL, 0, 0 ) )
{
// 转向结束程序
goto _ExitApp;
}
// 执行消息处理
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
// 如果没有任何消息要处理就调用主循环
Sleep(1);
}
goto _AppLoop;
_ExitApp:
AppTerm();
return (int)msg.wParam;
}
26.在AppWndProc()函数里加入推出程序消息处理
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
PostQuitMessage( 0 );
break;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
到现在我们已经可以编译并执行一个没有做任何事情的Windows程序,这里我不想和任何人讨论关于编码好坏的问题,例如在AppMsgLoop()函数中用了goto和标号。我个人觉得在这里不适宜讨论这个问题。我们重要的是知道如何让程序工作,有关于Windows编程的细节你可以查阅《Windows程序设计》一书。
第二章 简化的程序接口
前一章中我们介绍了如何编写Windows程序,已经完成了一个简单的有一个主窗口的Windows程序,接下来我们钻心写我们的游戏程序了。为了以后的编写方便使各部分程序内容彼此独立,我们现在开始定义一个程序简单的程序接口,我们以后就不需要再顾虑太多的Windows程序相关的内容。
程序接口的实现我们用到了C++中最常用的一种功能 虚拟函数(virtual function) 来实现接口功能。在这里你只需要知道在基类中定义的函数前面加了一个virtual关键字,以后派生类中都可以重写该函数内容。
1. 打开AppEntry.h 输入下面程序
#ifndef __AppEntry_H__
#define __AppEntry_H__
#include <windows.h>
class IAppEntry
{
public:
// 初始化程序
virtual bool Initialize( HINSTANCE hInstance, HWND hWnd ) = 0;
// 结束程序
virtual void Terminal() = 0;
// 主处理函数
virtual void Process() = 0;
// 主渲染函数
virtual void Render() = 0;
// 主窗口消息处理函数
virtual LRESULT WndProc( UINT message, WPARAM wParam, LPARAM lParam ) = 0;
// 定义Window程序类名函数
virtual const char *WindowClassName() = 0;
// 定义Window窗口宽度
virtual const int WindowWidth() = 0;
// 定义Window窗口高度
virtual const int WindowHeight() = 0;
// 获得 主程序句柄
virtual HINSTANCE GetInstance() = 0;
// 获得 主窗口句柄
virtual HWND GetWnd() = 0;
};
#endif // __AppEntry_H__
这里每一个函数 后面都写了一个 = 0 表示该函数是纯虚函数, 再派生类中必须重写。这种方法是定义接口的常用方法。
2. 打开WinApp.cpp 加入下面代码
// Windows消息循环和主循环
int AppMsgLoop();
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );
// 获得简单程序接口实例,我们将在GameApp.cpp中定义
IAppEntry *AppEntryClass();
//////////////////////////////////////////////////////////////////////////
// Windows程序主函数
int WINAPI WinMain(
IN HINSTANCE hInstance,
IN HINSTANCE hPrevInstance,
IN LPSTR lpCmdLine,
IN int nShowCmd
)
3. 在WinMain函数中加入下面代码
// Windows程序主函数
int WINAPI WinMain(
IN HINSTANCE hInstance,
IN HINSTANCE hPrevInstance,
IN LPSTR lpCmdLine,
IN int nShowCmd
)
{
// 保存主程序句柄
g_hTheApp = hInstance;
// 初始化Window窗口和程序
g_hMainWnd = AppInit( g_hTheApp, nShowCmd );
if( g_hMainWnd == NULL )
{
MessageBox( NULL, "Can't Create Main Window", "Error", MB_OK );
return 0;
}
// 初始化主程序
if( !AppEntryClass()->Initialize( g_hTheApp, g_hMainWnd ) )
{
MessageBox( NULL, "Initialize AppEntry Failed", "Error", MB_OK );
return 0;
}
// 进入主消息循环
return AppMsgLoop();
}
4. 在AppTerm() 函数里加入下面代码
// Windows程序结束
void AppTerm()
{
// 结束程序
AppEntryClass()->Terminal();
}
5. 在AppMsgLoop()函数里加入下面代码
// Windows消息循环和主循环
int AppMsgLoop()
{
MSG msg;
ZeroMemory( &msg, sizeof(MSG) );
_AppLoop:
// 检测是否有消息要处理
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
// 如果接收不到消息表示要退出程序
if( !GetMessage( &msg, NULL, 0, 0 ) )
{
// 转向结束程序
goto _ExitApp;
}
// 执行消息处理
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
// 如果没有任何消息要处理就调用主循环
AppEntryClass()->Process();
Sleep(1);
}
goto _AppLoop;
_ExitApp:
AppTerm();
return (int)msg.wParam;
}
6. 在AppWndProc()函数里加入下面代码
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
PostQuitMessage( 0 );
break;
}
// 调用简单程序接口的消息处理函数,如果返回不是FALSE 就直接完成消息处理
// 否则 继续调用Windows程序默认消息处理函数.
LRESULT lr = AppEntryClass()->WndProc( msg, wParam, lParam );
if( lr != FALSE )
return lr;
return DefWindowProc( hWnd, msg, wParam, lParam );
}
7. 在Tank项目上建一个新目录GameApp并创建两个文件 AppGame.cpp AppGame.h
8. 在AppGame.h里 加入下面代码
#ifndef __AppGame_H__
#define __AppGame_H__
#include "WinApp.h"
class CAppGame : public IAppEntry
{
public:
CAppGame();
virtual ~CAppGame();
// 初始化程序
virtual bool Initialize( HINSTANCE hInstance, HWND hWnd );
// 结束程序
virtual void Terminal();
// 主处理函数
virtual void Process();
// 主渲染函数
virtual void Render();
// 主窗口消息处理函数
virtual LRESULT WndProc( UINT message, WPARAM wParam, LPARAM lParam );
// 定义Window程序类名函数
virtual const char *WindowClassName();
// 定义Window窗口宽度
virtual const int WindowWidth();
// 定义Window窗口高度
virtual const int WindowHeight();
// 获得 主程序句柄
virtual HINSTANCE GetInstance();
// 获得 主窗口句柄
virtual HWND GetWnd();
protected:
// 保存主程序句柄
HINSTANCE m_hInstance;
// 保存主窗口句柄
HWND m_hWnd;
};
#endif // __AppGame_H__
9. 打开AppGame.cpp 加入简单接口程序实例,和获得程序实例的函数
#include "AppGame.h"
// 定义主游戏程序实例
CAppGame theAppGame;
// 定义获得简单程序接口实例函数,这个在WinApp.cpp中用到,而且必须定义
// 这是Windows程序调用游戏程序的接口
IAppEntry *AppEntryClass()
{
return &theAppGame;
}
////////////////////////////////////////////////////////////////////////
10.加入下面代码从写简单程序接口的纯虚函数内容
//////////////////////////////////////////////////////////////////////////
CAppGame::CAppGame()
{
m_hInstance = NULL;
m_hWnd = NULL;
}
CAppGame::~CAppGame()
{
}
// 初始化程序
bool CAppGame::Initialize( HINSTANCE hInstance, HWND hWnd )
{
m_hInstance = hInstance;
m_hWnd = hWnd;
return true;
}
// 结束程序
void CAppGame::Terminal()
{
}
// 主处理函数
void CAppGame::Process()
{
}
// 主渲染函数
void CAppGame::Render()
{
}
// 主窗口消息处理函数
LRESULT CAppGame::WndProc( UINT message, WPARAM wParam, LPARAM lParam )
{
return FALSE;
}
// 定义Window程序类名函数
const char *CAppGame::WindowClassName()
{
return "TankBattleCity";
}
// 定义Window窗口宽度
const int CAppGame::WindowWidth()
{
return 800;
}
// 定义Window窗口高度
const int CAppGame::WindowHeight()
{
return 600;
}
// 获得 主程序句柄
HINSTANCE CAppGame::GetInstance()
{
return m_hInstance;
}
// 获得 主窗口句柄
HWND CAppGame::GetWnd()
{
return m_hWnd;
}
11.打开WinApp.cpp, 并在 AppInit()函数里做如下修改
// Windows程序初始化
HWND AppInit( HINSTANCE hInstance, int nShowCmd )
{
// 程序实例类名
const char *szWindowsClassName = AppEntryClass()->WindowClassName();
static int iWindowWidth = AppEntryClass()->WindowWidth(); // 窗口宽度
static int iWindowHeight = AppEntryClass()->WindowHeight(); // 窗口高度
// 注册窗口类
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
12.介绍一点小工具。在Tank项目上加入一个Utility目录 加入FormatString.h 和FormatString.cpp 并加入下面代码
#ifndef __FormatString_H__
#define __FromatString_H__
#include <assert.h>
// 格式串垫片函数模版
template <const int iBufLen> inline const char *FormatString( const char *szFormat, ... )
{
static char szOutStr[iBufLen];
szOutStr[0] = 0;
va_list vl;
va_start( vl, szFormat );
vsprintf(szOutStr, szFormat, vl);
va_end(vl);
assert( strlen(szOutStr) < iBufLen && "Critical Damage" );
return szOutStr;
}
// 我给出一个常用的缓冲大小为1024字节的宏
#define FSTR FormatString<1024>
// 用法:
// OutputDebugString( FSTR( "Format Out %s", "ok" ) );
#endif // __FormatString_H__
我们可以在任何需要输出字符串格式化时使用它 ,这种方法很方便。这个不能用来嵌套调用,也不能用于复杂线程调用,static char szOutStr[iBufLen]; 这是个唯一的内存地址,其他函数调用改变了他的内容就会出错。也就是说用这个工具组合出来的字符串要立即调用。
现在我们项目文件如下图:
第三章 显示图片和基本绘图
这一章我将介绍如何用WindowsGDI显示图片到程序主窗口上,用GDI显示只是为了简单方便,来完成我们的任务,如果要更高效的方法可以使用 DirectX 3D 或 DirectDraw接口。
1. 首先我们准备好下面这张图片我把它放在运行目录RunTime里
取名为ImageC.bmp,接下来就是如何把这张图片显示出来。
2.在Tank项目里加入Render目录,并增加文件 GDIGraphicsDevice.cpp GDIGraphicsDevice.h GDISurface.cpp GDISurface.h GDITextRender.cpp GDITextRender.h
3.打开GDIGraphicsDevice.h 加入下面代码
#ifndef __GDIGraphicsDevice_H__
#define __GDIGraphicsDevice_H__
#include <Windows.h>
// 绘图设备,这里指最后能够显示在屏幕上的设备,这是虚拟的,
// 我们把它叫做图形设备。
class CGDIGraphicsDevice
{
public:
CGDIGraphicsDevice();
virtual ~CGDIGraphicsDevice();
// 创建设备, 让设备操作对象指向 某个窗口
bool Create( HWND hWnd, int iWidth, int iHeight );
// 释放设备
void Release();
// 更新显示,使主表面内容显示到屏幕上
void UpdateFrame( HDC hdc );
// 获得操作指向的窗口
HWND GetWnd();
protected:
// 调整窗口大小
void AdjuestWindowSize( int iWidth, int iHeight );
protected:
// 操作指向的窗口句柄
HWND m_hWnd;
};
#endif // __GDIGraphicsDevice_H__
4.打开GDIGraphicsDevice.cpp 加入下面代码
#include "GDIGraphicsDevice.h"
CGDIGraphicsDevice::CGDIGraphicsDevice()
{
m_hWnd = NULL;
}
CGDIGraphicsDevice::~CGDIGraphicsDevice()
{
}
bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )
{
return true;
}
void CGDIGraphicsDevice::Release()
{
}
void CGDIGraphicsDevice::UpdateFrame( HDC hdc )
{
}
HWND CGDIGraphic
展开阅读全文