在PPC上编写游戏
第一章:工具
在Pocket PC上编写游戏首先需要的就是微软的“eMbedded Visual Tools 3.0”。它是可以免费下载的(省略),但文件大小超过300M,所以请选择适当的时候下载(用28.8K的Modom需要大约30个小时)。你也可以在软件零售商店买到它的CD-ROM版本。请访问微软的网站来获取更多的信息。
eMbedded Visual Tools包含C和C++的编译器,链接器,C和Windows的
标准
excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载
库,标准的包含文件,集成开发环境,模拟器,帮助文件和开发工具。其中最大的就是这个开发包的基础知识库——MSDN。推荐在Windows NT或Windows 2000上安装使用。因为模拟器无法在除此之外的操作系统上工作。
因为Windows CE被
设计
领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计
成可以支持不同种类的CPU,如StrongARM、MIPS、SH3、SH4和X86。它也支持不同的设备,例如手持设备,口袋设备或者是移动电话。但是在将来,我们或许会在住宅电话、电视、微波炉等设备上见到它,当然,其他设备也都有可能。
我们可以把EVC分成三个重要的部分:IDE(集成开发环境),各种CPU平台的开发包和ActiveSync。
这个EVC的集成开发环境有些类似Microsoft Visual Studios的集成开发环境。你还需要安装各种平台的开发包和支持每个平台的特定文件(微软也提供这些平台的构建开发包,供你开发你自己的Windows CE设备)。安装程序将询问你是否安装其他开发平台,如HPC、PsPC和PPC。如果你不打算开发其他设备的软件,你可以不安装它们。当然,这个
方法
快递客服问题件处理详细方法山木方法pdf计算方法pdf华与华方法下载八字理论方法下载
可以使你用这个工具开发其他Windows CE设备的程序,你所需要做的只是安装这个平台的开发包。
ActiveSync让你和你的设备通信。它的功能是最基本的。它支持所有的设备。
当你将EVC安装到你的台式电脑后,你就可以为你的CE设备开发软件了,包括可执行文件、动态链接库(DLL)、设备驱动程序ActiveX应用程序等等。当然,游戏可能回会用到其中的两种,那就是可执行文件和动态链接库。我们将在下一章学习怎么创建它们。
如果你更喜欢DOS环境,你可以使用旧的开发方式,使用你自己的文本编辑器,用make在命令行下编译链接程序。这个文件通常位于:
编译和链接器 (for PPC)位于
安装目录/Microsoft eMbedded Tools/Evc/Wce300/bin
库和包含文件位于
安装目录/Windows CE Tools/wce300/MS Pocket PC/lib
安装目录/Windows CE Tools/wce300/MS Pocket PC/include
另外一些重要的文件是支持文件,当你想安装程序,在和设备台式电脑间交换数据或捕捉屏幕,将会用到它们。
要学习如何使用EVC,你可以阅读在线帮助或者只是阅读本文,但我无法让你学会有关EVC的所有知识,因为我在试图指导你达到另外一点,那就是让你学会开发你自己的游戏程序。
第二章:操作系统接口
这一章主要的目标是使你的程序能够在机器上运行。当然,我们需要遵守操作系统规则。那么我们怎样创建一个应用程序呢?
这里有一段很短的代码,它可以在你的Pocket PC上运行。
#include "windows.h"
LRESULT MainWndProc(HWND hWnd, UINT message, WPARAM uParam, LPARAM lParam)
{
if(message == WM_LBUTTONDOWN)
{
DestroyWindow(hWnd);
return 0;
}
return DefWindowProc(hWnd, message, uParam, lParam);
}
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
WNDCLASS wc;
memset(&rc,0,sizeof(wc));
wc.style = CS_HREDRAW|CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) MainWndProc;
wc.hInstance = hInstance;
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszClassName = L"myapp";
if(RegisterClass(&wc))
{
HWND hWndMain;
if(hWndMain = CreateWindow(L"myapp",L"Hello",
WS_VISIBLE,0,0,240,320,0,0,hInstance,0))
{
MSG msg;
ShowWindow(hWndMain, SW_MAXIMIZE);
UpdateWindow(hWndMain);
while(GetMessage(&msg,hWndMain,0,0))
DispatchMessage(&msg);
return msg.wParam;
}
}
return 0;
}
好了,它可以运行了,它将显示一个空白的屏幕,并且在你点击屏幕后结束。如果你是刚刚学习编程,而且从来没有使用过EVC或是VC,你可以按照下面步骤操作:
执行EVC
选择FILE菜单的NEW...项
选择WCE Application并且点选你所需要的CPU平台
给你的工程命名,并且选择在硬盘中存放的目录。
单击OK按钮
接下来应用程序创建巫师询问你是要创建何种的应用程序,选择'An empty project'然后点机完成。随后工程信息对话框出现,点击OK进行下一步。
现在你得到了一个空的工程,如果要学习使用EVC的用户界面,请看看它的在线文档。现在,让我们为它添加第一个源文件。点击FILE菜单的NEW...项。新弹出的对话框将为你显示一些信息。
选择C++文件类型,点选'Add to project'检查框。在文件名编辑框内键入文件名,如'startup.c',然后按OK键。
一个新的空白文件将会出现供你编辑。
键入或复制上述代码到空白文件中,并且保存。
选择要编译的平台和CPU。然后按F7或从project菜单中选择build来编译、链接工程。对于iPaq,设定当前的激活平台为Pocket PC,设定CPU和编译模式为Win32 (WCE ARM) release.
在build之后。你的第一个应用程序保存在“ARMRel”的子目录中
通常,在没有差错的build完工程后。可执行文件将会自动被上传到你的Pocket PC中,如果它已经正确连接到你的台式电脑上。如果没有,你可以选择Build菜单中的Update Remote Output Files将它上传。当然你也可以通过ActiveSync或EVC自带的Remote File Viewer工具将它手动上传。
这个应用程序将出现在你的开始菜单中。(注意,新建工程的默认上传位置是\Windows\Start menu目录,但中文版Pocket PC并不存在该目录,它对应的目录是\Windows\“开始”菜单,你要在必须要在Projet菜单的setting中设置,在弹出的对话框中选择Debug标签页,修改其中的Download directory内容为"\Windows\“开始”菜单")
在下面的章节中,我们将开始理解这些代码是如何工作的。
首先,我们需要告诉操作系统关于你的新的应用程序的信息。因为Win32是一个多任务的操作系统,所有运行的应用程序必须把自己的的信息存放在操作系统的内部数据库中。你需要填充一个WNDCLASS结构,通过使用RegisterClass这个系统API(应用程序接口),来将这些信息告诉操作系统。如果操作系统接受你的信息,这个函数返回一个非零值。
WNDCLASS结构中有三个参数比较重要,hInstance、lpszClassName和lpfnWndProc. “hInstance”是一个用来识别你应用程序的唯一的数字(或是进程ID).你将会从操作系统中得到这个值。我们将在程序的几个地方用到它。“lpszClassName”是当我们创建窗口时用来识别应用程序自己的唯一的字符串。最后的一个是“lpfnWndProc”。它是一个用来获得系统通知消息的函数名称。
其他的成员可以被设定为零,而且不会有任何错误。但是我们通常通过hbrBackground来设定窗口的背景颜色。如果这个成员不设定(也就是赋值为零),系统将不会填充窗口的客户区,而是给你发送WM_ERASEBKGND消息,然后由你自己来填充背静。
下面的步骤是创建主窗口。这里的“窗口”是指一个与其他运行中的应用程序共享着的显示区域。因为你的应用程序需要这个区域来显示正文(菜单条、标题栏、客户区等等)。它也被用来获得鼠标动作(鼠标移动、鼠标点击等等)。窗口是拥护和程序交互的途径,CreateWindow函数创建一个窗口(一个应用程序可以拥有一个或多个窗口)。这个窗口需要联系到刚才用RegisterClass注册的数据。它的第一个参数必须和WNDCLASS的lpszClassName成员一致。
如果函数成功了,它返回一个窗口的句柄(HANDLE),变量类型是HWND。这是一个唯一的32bit (双字)值,用来识别所创建的窗口。如果失败,它返回空(就是零值)。
创建的窗口并没有立即显示到屏幕上。这里有两个函数——ShowWindow和UpdateWindow,供你将窗口显示到屏幕上。通常Pocket PC的应用程序在它们运行时占据整个屏幕(因为Pocket PC的屏幕实在是太小了)。我总是使用代码以获得最大的屏幕显示区域。
接下来,程序进入一个循环。就是这个循环保证了多任务系统的工作。所有的系统消息通过DispatchMessage被发送到住窗口过程MainWndProc。循环将在窗口被DestroyWindow销毁或被系统终结后退出。退出循环后,程序被系统杀掉。你的应用程序信息也被自动移除。
有没有其他简单的方法让一个程序在Pocket PC上执行?当然有,那就是利用现有的窗口类来开始你的应用程序,因为你不需要注册一个新的窗口类。这样的代码将比本文介绍这个小。通常就是一个基于对话框的应用程序。我不打算讲这个方法,因为,这样将失去一些控制应用程序的方法,而且需要花更多的时间来学习Win32的资源脚本文件格式。
Pocket PC的游戏编程指导教程
第三章:离屏技术(也就是我们常说的屏幕缓冲区)
导读
在我们学习离屏(OFF SCREEN)技术之前,让我们看看ON SCREEN是什么?为什么没有任何游戏程序使用这种方法?此外,还有另外一个基础问题,我们怎么在窗口的客户区绘制文字、图片或者是图画?
通常,在屏幕上显示任何东西的方法是使用GDIs(图形设备接口)和APIs(应用程序接口)。Windows的窗口区域被划分成两个重要部分:客户区和非客户区(比如菜单、标题栏和边界框)。大多数窗口是可以移动的。因此它所显示的内容也跟着窗口自己的左上角一起关联移动。GDIs和APIs帮助我们管理这种关联。
Windows的GDI是用来对所有硬件设备提供一种硬件无关支持的程序。因为各个厂家的硬件技术是不同的,所以用这些相同的代码是无法获得硬件的最大性能。实现它们的目的只是保证支持。然而,很多有些开发者想要获得硬件设备的最大性能,他们不用GDIs和APIs,而是直接访问硬件。当然这些方法也可以工作,但却依赖于使用的硬件,它们可能无法在全部的设备上工作。
Windows CE的显示技术又如何呢?有人都能改变显示适配器吗?当然不能,因为它是一种嵌入式系统。通常硬件厂商不会在系统中包含优化代码。然而,这种显示速度已经能够应付某些游戏类型了。
Windows GDIs
设备正文的句柄,通常
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
示为hDC,是一个连接GDI程序的32位的整数值。通常,大多数窗口有它自己的DC(设备文本),并且你可以通过它的hWnd(窗口句柄)获得它的DC。方法如下:
HDC hDC;
hDC = GetDC(hWnd);
要调用GDIs和APIs,例如画一条线,你需要将hDC作为这些GDI函数的第一个参数。
MoveToEx(hDC,0,0,NULL); //将作图点移动到(0,0)
LineTo(hDC,240,300); //从作图点画线到(240,300)
做完这些,你还必须要从内存中释放hDC
ReleaseDC(hWnd,hDC);
这里还有一个特殊的绘图工作,那就是在收到WM_PAINT消息时,这意味着系统要求你画出你的客户区域,这些代码通常在MainWndProc中完成。下面的代码就的功能就是在窗口的正中央显示文字"hello"。另外,在WM_PAINT消息处理中,除了GetDC,还有另一种更方便的从窗口获取hDC的方法,那就是BeginPaint和EndPaint。
LRESULT MainWndProc(HWND hWnd, UINT message, WPARAM uParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hDC;
RECT rcClient;
switch(message)
{
case WM_PAINT:
hDC = BeginPaint(hWnd,&ps);
GetClientRect(hWnd,&rcClent);
DrawText(hDC,L"Hello",5,
&rcClient,DT_CENTER|DT_VCENTER);
EndPaint(hWnd,&ps);
break;
case WM_LBUTTONDOWN:
DestroyWindow(hWnd);
break;
default:
DefWindowProc(hWnd, message, uParam, lParam);
}
return 0;
}
这些代码中GetClientRect用来获得整个客户区域的矩形范围,DrawText用来在屏幕上这个举行范围的中心画出文字"hello"。
当然我们也可以在别的地方进行绘制,你可以再试试下面这些代码。
LRESULT MainWndProc(HWND hWnd, UINT message, WPARAM uParam, LPARAM lParam)
{
HDC hDC;
int nXpos;
int nYpos;
switch(message)
{
case WM_LBUTTONDOWN:
nXpos = LOWORD(lParam);
nYpos = HIWORD(lParam);
hDC = GetDC(hWnd);
MoveToEx(hDC,nXpos-4,nYpos-4,NULL);
LineTo(hDC,nXpos+4,nYpos+4);
MoveToEx(hDC,nXpos+4,nYpos-4,NULL);
LineTo(hDC,nXPos-4,nYpos+4);
ReleaseDC(hWnd,hDC);
break;
case WM_KEYDOWN:
DestroyWindow(hWnd);
break;
default:
DefWindowProc(hWnd, message, uParam, lParam);
}
return 0;
}
上面这种直接在屏幕(设备文本)上绘图的方法,就叫做ON SCREEN。在下面,我将给你演示为着这种方法不适合运用在游戏程序中。让我们看看下面这些长一些的代码。
static HBRUSH hbrRed;
static HPEN hpeBlue;
static void _drw_object(HDC hDC, int nX, int nY)
{
HGDIOBJ hOldPen, hOldBrush;
hOldPen = SelectObject(hDC,hpeBlue);
hOldBrush = SelectObject(hDC,hbrRed);
Ellipse(hDC,nX-20,nY-20,nX+20,nY+20);
SelectObject(hDC,hOldBrush);
SelectObject(hDC,hOldPen);
}
LRESULT MainWndProc(HWND hWnd, UINT message, WPARAM uParam, LPARAM lParam)
{
HDC hDC;
switch(message)
{
case WM_LBUTTONDOWN:
hDC = GetDC(hWnd);
_drw_object(hDC,LOWORD(lParam),HIWORD(lParam));
ReleaseDC(hWnd,hDC);
break;
case WM_MOUSEMOVE;
hDC = GetDC(hWnd);
PatBlt(hDC,0,0,240,320,WHITENESS);
_drw_object(hDC,LOWORD(lParam),HIWORD(lParam));
ReleaseDC(hWnd,hDC);
break;
case WM_LBUTTONUP:
hDC = GetDC(hWnd);
PatBlt(hDC,0,0,240,320,WHITENESS);
ReleaseDC(hWnd,hDC);
break;
case WM_CREATE:
hbrRed = CreateSolidBrush(RGB(255,0,0));
hpeBlue = CreatePen(0,0,RGB(0,0,255);
break;
case WM_DESTROY:
DeleteObject(hbrRed);
DeleteObject(hpeBlue);
PostQuitMessage(0);
break;
case WM_KEYDOWN:
DestroyWindow(hWnd);
break;
default:
DefWindowProc(hWnd, message, uParam, lParam);
}
return 0;
}
现在,试试用笔点击屏幕并在拖动看看。
计算机将首先将屏幕填充为白色,将原本的圆形物体擦除,然后在新的位置画上。因为填充屏幕要画掉很多的时间,因此我们将看到旧有图形被长时间的擦除,然后新图形在别的位置出现。这个过程就在我们的眼睛中形成了闪烁。如果有多个需要绘制的物体,这种闪烁将更加明显。
离屏技术
离屏技术的优点就恰恰是避免屏幕的闪烁。它的方法就是:创建一个隐藏的屏幕,一个虚拟的屏幕,或者是在可显区域外的屏幕。然后,我们将所要画的任何东西都先画在这个屏幕上。在这个期间,真正的屏幕是不会变化的。当绘制结束后,在再将整个虚拟屏幕上的内容拷贝到真正的屏幕,因为这个时间很短,内容变化不大时,几乎看不到任何闪烁。
现在,就让我们看看它是如何在Pocket PC上实现的?有三个重要的步骤:
1. 创建离屏的虚拟屏幕。
2. 在离屏虚拟屏幕上绘图。
3. 将离屏虚拟屏幕的内容拷贝到真正的屏幕。
static HBRUSH hbrRed;
static HPEN hpeBlue;
static HDC hOffscreenDC;
static HBITMAP hOffscreenBuffer;
static int nOffscreenCX, nOffscreenCY;
//创建离屏表面
void InitOffscreen(int nWidht, int nHeight)
{
HDC hDesktopDC;
//获取桌面的设备文本
hDesktopDC = GetDC(0);
//创建和桌面相同的设备文本,也就是虚拟屏幕的设备文本
hOffscreenDC = CreateCompatibleDC(hDesktopDC);
//创建和桌面相同的位图(缓冲内存)
hOffscreenBuffer = CreateCompatibleBitmap(hDesktopDC,nWidth,nHeight);
//将内存缓冲选入虚拟屏幕的设备文本
SelectObject(hOffscreenDC,hOffscreenBuffer);
nOffscreenCX = nWidth;
nOffscreenCY = nHeight;
//释放桌面的设备文本
ReleaseDC(0,hDesktopDC);
//将整个屏幕画为白色
PatBlt(hOffscreenDC,0,0,nWidth,nHeight,WHITENESS);
}
//释放离屏表面
void DeinitOffsceen(void)
{
//释放虚拟屏幕的设备文本
DeleteDC(hOffscreenDC);
//删除虚拟屏幕的内存对象
DeleteObject(hOffscreenBuffer);
}
//更新屏幕(将离屏虚拟屏幕的内容拷贝到真正的屏幕)
void UpdateDisplay(HWND hWnd)
{
HDC hDC;
hDC = GetDC(hWnd);
//将虚拟屏幕位块传送到窗口hDC的屏幕上
BitBlt(hDC,0,0,nOffscreenCX,
nOffscreenCY,h0ffscreenDC,0,0,
SCRCPY);
ReleaseDC(hWnd,hDC);
}
//绘制圆形物体
static void _drw_object(HDC hDC, int nX, int nY)
{
HGDIOBJ hOldPen, hOldBrush;
hOldPen = SelectObject(hDC,hpeBlue);
hOldBrush = SelectObject(hDC,hbrRed);
Ellipse(hDC,nX-20,nY-20,nX+20,nY+20);
SelectObject(hDC,hOldBrush);
SelectObject(hDC,hOldPen);
}
//窗口过程
LRESULT MainWndProc(HWND hWnd, UINT message, WPARAM uParam, LPARAM lParam)
{
switch(message)
{
case WM_LBUTTONDOWN:
_drw_object(hOffscreenDC,LOWORD(lParam),HIWORD(lParam));
UpdateDisplay(hWnd);
break;
case WM_MOUSEMOVE;
PatBlt(hOffscreenDC,0,0,240,320,WHITENESS);
_drw_object(hOffscreenDC,LOWORD(lParam),HIWORD(lParam));
UpdateDisplay(hWnd);
break;
case WM_LBUTTONUP:
PatBlt(hOffscreenDC,0,0,240,320,WHITENESS);
UpdateDisplay(hWnd);
break;
case WM_CREATE:
hbrRed = CreateSolidBrush(RGB(255,0,0));
hpeBlue = CreatePen(0,0,RGB(0,0,255);
InitOffscreen(240,320);
break;
case WM_DESTROY:
DeleteObject(hbrRed);
DeleteObject(hpeBlue);
DeinitOffscreen();
PostQuitMessage(0);
break;
case WM_KEYDOWN:
DestroyWindow(hWnd);
break;
default:
DefWindowProc(hWnd, message, uParam, lParam);
}
return 0;
}
这些代码显示了一个以GDI为基础的离屏表面,它是怎么工作的呢?
让我们解释这些代码,InitOffscreen函数中,使用简单的GDIs和APIs——CreateCompatibleDC和CreateCompatibleBitmap创建一个虚拟屏幕。
DC,也就是设备文本,是一种调用设备例程(或者是设备驱动例程)的方法。因为我已经在前面的Windows的GDIs部分大概介绍了它,这里我们来学习更多有关它的其他方面。设备文本也有三个主要的类型:它们是显示设备文本、打印设备文本、和内存设备文本。如果我们在打印文本上画线,设备文本将会调用打印机驱动程序中的画线程序来完成这一过程。如果是在显示文本上画线,调用的自然也就是显示驱动程序中的例程。如:
hDC = CreateDC("Printer",0,0,0); // 创建缺省的打印文本
hDC = CreateDC("Display",0,0,0); // 创建的显示文本
hDC = GetDC(hWnd); // 获得窗口客户区的显示文本
hDC = GetWindowDC(hWnd); // 获得整个窗口的显示文本(包含非客户区)
换句话说,GDIs可以理解为是一个通过DC选择器来实现的硬件设备的通路。这个通路可以让你以共同的方法控制图形设备,包含一些非原始的设备。
要了解更多有关Windows图形设备接口(GDI)的和设备驱动的概念,请参考MSDN。
而什么是内存设备文本呢?可以这么说,它就是虚拟屏幕!CompatibleDC函数创建一个和源设备文本一致的新的设备文本,但它只存在于内存中,并不关联设备。下一步,我们需要分配一个内存块,也就是用来储存图象的屏幕缓冲区。Windows的位图对象就是实现这个的一种方法。我们可以用CreateCompatibleBitmap来创建一个和屏幕相同象素位数,相同颜色格式、相同调色板数目的位图,通过SelectObject将它选入内存设备文本。这样,一个离屏的虚拟屏幕就建成了。
之后,内存设备文本就可以象普通的显示设备文本一样使用了,我们可以将它用在各种GDIs和APIs中,在它上面做图,将它画到其他设备文本上,等等。如,在UpdateDisplay中,我们就用位块传送函数BitBlt将它拷贝到了窗口的客户区域。这个函数在各种设备文本上以相同的形式,非常快速的复制以矩形区域为单位的数据。
在例子程序中,WM_CREATE消息只是在我们的主窗口在内存中被创建时,发送到我们的窗口函数,在整个程序的生存周期内只有一次,因此适合做一些初始化的工作。而WM_DESTROY消息则在窗口别销毁时被发送,可以放入对应内存释放的工作。而WM_MOUSEMOVE
、WM_LBUTTONDOWN和WM_LBUTTONUP是鼠标事件消息。我们将在笔尖接触屏幕、笔尖移过屏幕和笔尖离开屏幕时分别收到它们。
游戏中的大多数物体不是矩形,但是一个位图却是矩形,因此我们必须找到一个方法来绘制物体真正的形状而不是矩形。
图片
如果我们在游戏编程中谈及图片,通常指的是光栅图象或位图。我会假定你已经了解有关的知识,但下面仍然给出少许解释。
位图或者光栅图象是一种以数字格式储存照片或图片的方法。它将照片每行每列的象素转化为二进制数据的矩阵。矩阵每个单元格的数据代表图片显示的一个象素。因此,一个位图必然是矩形的。如:
00 01 10 FF FE 12 15 11
11 12 11 16 18 90 11 C0
00 00 01 02 03 04 05 06
11 12 11 15 16 D0 ED DD
00 01 10 FF FE 12 15 11
11 12 11 16 18 90 11 C0
00 00 01 02 03 04 05 06
11 12 11 15 16 D0 ED DD
象素的格式和单元格的大小是依赖于它所采用的颜色坐标体系。我们知道位图有几种不同的文件格式。但是它们都是基于一些相同的概念。
这里,我只是告诉你“位图是矩形的”,因为我们这个章节使用Windows的位图。但是,我并不想深入讨论位图的内部格式。
精灵
通常,有两种流行的技术来完成精灵的绘制工作。关键色和alpha 通道。关键色技术是一种非常著名的精灵绘制方法,因为它非常简单而且容易理解。现在,让我们多了解一些这两种技术。
现在,让我们看看下面这个飞碟的图片:
此主题相关图片如下:
请注意图片中围绕着飞碟的白色部分,如果我们将10个飞碟图片都画到屏幕上一个接近的区域。结果就是下面这个样子,图片的白色部分也会覆盖飞碟的图象,而不是我们所需要的正确结果:
此主题相关图片如下:
关键色是指将图片绘制到图形表面时,忽视图片中所指定的颜色。这有些象蓝屏技术(就是在电视和电影制作中使用蓝色的背景,在后期合成时将所有蓝色的内容替换成其他的背景视频或虚拟场景)。但是,我们使用的这个技术更简单一些,因为它并不判断相近的颜色,只是将相同的颜色忽略。
我们将关键色填充到图片中非物体的区域。在下面标记为紫红色RGB(255,0,255)。
此主题相关图片如下:
上图中,紫红色的区域都不是飞碟的部分,因此我们采用它做关键色。当将图片中的每个象素绘制到屏幕上时,我们忽略关键颜色颜色RGB(255,0,255)。代码如下:
for(nRow=0; nRow
1 BITMAP yourfile.bmp
第一行是包含windows头文件,这里面包含所有必须的预定义符号。第二行分成三个部分:资源ID,资源类型和资源文件名。将这个文件保存为后缀为.rc的文件,然后加入整个工程(方法是在工程环境窗口的工程名字上点右键,选择Add to Project,然后选择你刚才保存的rc文件)。完成这一切后,你就可以在你的程序中使用LoadBitmap函数装载位图了。代码如下:
HBITMAP hBmp;
hBmp = LoadBitmap(hInstance,MAKEINTRESOURCE(1));
Having used the bitmap, you can remove it from memory by using the DeleteObject(...) function.
DeleteObject(hBmp);
许多开发者喜欢设计具有逼真的动作效果游戏。 如此一来,仅有一些漂亮的静态图像完全提不起他们的兴趣。这个章节我们将学到基本制作动画的技术。
动画
在这个部分,我们将探讨电脑动画,一系列的图象连续运动从而在显示器上模拟出运动的效果。关于电脑图像可以有很多种方法实现,这完全取决于你采用的编程语言和环境。一种实现动画效果的方法是,先画一个图像在屏幕上,然后擦掉它,然后在画上一个和上一幅略有不同的图像,或者改变一下图像的位置。一组图像按照一定的时序连续的显示,并且后一幅必须完全覆盖前面的一幅,这样给人在视觉上造成连续运动的效果。为了使人看上去没有停顿或跳跃的感觉,图象必须至少以每秒14帧/秒的速度进行交替显示。这是游戏程序动画的标准。有时为了节省内存和CPU资源,也可以将它设定成6-10帧/秒。我们要知道高质量的动画可以达到14帧/秒-30帧/秒。通常电影卡通片采用的是14帧/秒,但它每个帧都被印两次。(这是什么意思,我也不知道,但这句以我的水平只能这样译了。原文:but each frame is printed twice)
在Pocket PC上实现
要想产生一个简单的动画,我们至少需要2张不同的图片。显示这个动画效果的简单方法是,按照下面列出的步骤来做:
A =
B =
1、将画面以外的部分清除。
2、将图片A放进显示画面以外,使人们看不到它。
3、更新显示区域,实际的将图片显示出来。
4、推迟一些时间让用户看到图片。如果我们需要一个10fps动画,延期时间是1000/10=100毫秒。
5、重复以上步骤, 但是在第2步有些改变,我们将图片B放到显示区外面。
如果我们想要表现出比较复杂的动作,例如一个人的行走动作,那为了动画效果的成功,应该是大约使用10帧/秒或更多。如果我们要使用10帧/秒来产生我们的动画效果,意味着我们必须有10份图象文件。 这时我们便遇到了文件管理的问题,我们如何将所需的文件数量减少呢?还好我们可以将所需要的图片联在一起,合成一个图像文件,就象“妮雅行走”的图片那样。见下图:
上面的图片的尺寸是184 x 57个象素。 我们能做一个简单的动画, 让妮雅走起来,通过设定偏移我们可以得到4个部分的图象,每个部分的尺寸是46 x 57个象素。 然后从左边开始我们依次画每个部分到屏幕正确位置。
现在, 让我们试试这段代码。
// sample.rc
#include "windows.h"
100 BITMAP ninja.bmp
// replace to startup.c
LRESULT MainWndProc(HWND hWnd, UINT message, WPARAM uParam, LPARAM lParam)
{
HDC hDC, hMemDC;
HBITMAP hBitmap;
int nCnt;
switch(message)
{
case WM_LBUTTONDOWN:
hDC = GetDC(hWnd);
hBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(100));
hMemDC = CreateCompatibleDC(hDC);
SelectObject(hMemDC, hBitmap);
ReleaseDC(hWnd,hDC);
for(nCnt=0; nCnt<4; nCnt++)
{
PatBlt(hOffscreebDC, 0, 0, 240, 320, WHITENESS);
TransparentImage(hOffscreenDC,nCnt*4,0,46,57,hMemDC,
nCnt*46,0,46,57, RGB(255,0,255));
Sleep(100);
UpdateDisplay(hWnd);
}
DeleteDC(hMemDC);
DeleteObject(hBitmap);
break;
case WM_KEYDOWN:
DestroyWindow(hWnd);
break;
case WM_CREATE:
InitOffscreen(240, 320);
break;
case WM_DESTROY:
DeinitOffscreen();
PostQuitMessage(0);
break;
default:
DefWindowProc(hWnd, message, uParam, lParam);
}
return 0;
}
你将看见妮雅从你的屏幕上慢慢的向你走来。
在下一步, 我们想控制妮雅在屏幕上的位置。 好,我将调用一个精灵或游戏对象来控制它在屏幕上的位置。我们需要保存当前的位置信息。在Win32 SDK中,我们可以使用POINT数据类型。POINT类型由2个成员组成。x和y。类型的声明见下;
typedef _tagPOINT
{
int x;
int y;
} POINT;
这个类型是专门用做控制位置的。
下一数据类型是专门用来对付尺寸的数据类型。它和POINT类似,但成员是cx和cy而非x和y。
typedef _tagSIZE
{
int cx;
int cy;
} SIZE;
我们将使用这两个数据类型在我们的游戏中控制我们的精灵对象的位置和尺寸。我们还需要一个变量,来做为计数器。它用来控制让精灵的动起来。现在,我们将试着做一个屏幕保护。我们放置10妮雅在屏幕上,并且让他在屏幕上从左边走到右边。另外它开始位置是随机产生的。
生产计算机软件的第一步是计划。 我的计划在下面:
1、使用全局变量来存放位置信息和图象资源。
2、用一个循环产生10个妮雅。
3、将画面以外的部分清除。
4、移动精灵到指定位置。
5、更新计数器。
6、画出对象在指定位置
7、如果位置超出屏幕,那重新随机产生新的位置。
8、当循环完成后更新当前屏幕
9、延时50毫秒,让人们看清显示出来的图象。
10、再次开始循环
11、销毁已经装载的位图
好,下面是代码。
// sample.rc
#include "windows.h"
100 BITMAP ninja.bmp
// replace to startup.c
#DEFINE TRASNCOLOR RGB(255,0,255)
sattic HBITMAP hObjBitmap;
static POINT ObjPosition[10];
static int nFrameCounter[10];
static SIZE ObjSize;
static void FirstInit(void)
{
int nCount;
InitOffscreen(240, 320);
hObjBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(100));
ObjSize.cx = 40;
ObjSize.cy = 57;
for(nCount=0; nCount<10; nCount++)
{
ObjPosition[nCount].x = -(Random() % 100);
ObjPosition[nCount].y = Random() % (320 - ObjSize.cy);
nFrameCounter[nCount] = 0;
}
}
static void Deinit(void)
{
DeinitOffscreen();
DeleteObject(hObjBitmap);
}
static void RenderFrame(HWND hWnd)
{
HDC hDC, hMemDC;
int nCnt, x, y, frameno;
hDC = GetDC(hWnd);
hMemDC = CreateCompatibleDC(hDC);
SelectObject(hMemDC, hObjBitmap);
ReleaseDC(hWnd,hDC);
PatBlt(hOffscreebDC, 0, 0, 240, 320, WHITENESS);
for(nCnt=0; nCnt<10; nCnt++)
{
x = ObjPosition[nCnt].x;
y = ObjPosition[nCnt].y;
frameno = nFrameCounter[nCnt] * ObjSize.cx;
TransparentImage(hOffscreenDC,x,y,ObjSize.cx,ObjSize.cy,hMemDC,
frameno,0,ObjSize.cx,ObjSize.cy, TRASNCOLOR);
ObjPosition[nCnt].x += 4;
if(ObjPosition[nCnt].x + ObjSize[nCnt] > 240)
ObjPosition[nCount].x = -(Random() % 100);
nFrameCounter[nCnt]++;
if(nFrameCounter[nCnt] > 4)
nFrameCounter[nCnt] = 0;
}
UpdateDisplay(hWnd);
DeleteDC(hMemDC);
}
LRESULT MainWndProc(HWND hWnd, UINT message, WPARAM uParam, LPARAM lParam)
{
switch(message)
{
case WM_TIMER:
RenerFrame(hWnd);
break;
case WM_KEYDOWN:
DestroyWindow(hWnd);
break;
case WM_CREATE:
FirstInit();
SetTimer(hWnd,1,50,0);
break;
case WM_DESTROY:
Deinit();
PostQuitMessage(0);
break;
default:
DefWindowProc(hWnd, message, uParam, lParam);
}
return 0;
}
导读
大多数动作游戏运行时,我们看见游戏在不停的移动着。如果在DOS操作系统下,程序员很容易可以做到这点,但是如果在Win32这个多支持任务的操作系统环境,超过一个以上的应用程序好象正在在同一个时间同时运行。 这时你的游戏只是其中的一个,另外的程序虽然可能没有显示出来,但它们运行在后台。 这将引起一个问题,就是我们需要和其他的程序进程分享CPU时间。
自己运行
在先前的章节,在最后例子源码中你可能已经发现了一个API函数SetTimer(..)。一个典型的Win32程序有一个主窗口函数,和一个过程处理函数WndProc,用来处理Windows操作系统发送来的消息。如果没有新消息进来,我们的代码便什么也不做,好象休眠了一样。如何使程序能够自动的运行,而不需要人工的干涉呢?建立一个无限循环确实可以达到这个目的,但是你会失去和操作系统之间的通讯。因为只要你还在循环体中,你便不可能处理下一条来自消息队列的消息。那怎么办,我们可以使用SetTimer(...)这个API函数,他可以适时的向WndProc发送请求,让操作系统招呼一下我们。*_-
在你调用SetTimer(hwnd,wid,n,0)以后只是一时间。 操作系统将每[n] 毫秒发送一个WM_TIMER消息到你的窗口过程函数。现在,让我们试试这代码
// replace to startup.c
LRESULT MainWndProc(HWND hWnd, UINT message, WPARAM uParam, LPARAM lParam)
{
switch(message)
{
case WM_TIMER:
MessageBeep(0);
break;
case WM_KEYDOWN:
DestroyWindow(hWnd);
break;
case WM_CREATE:
SetTimer(hWnd,1,500,0);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
DefWindowProc(hWnd, message, uParam, lParam);
}
return 0;
}
这个程序的效果是:每500毫秒便会发出哔哔声。
什么是可分享的时间?
现在, 我们能让使用定时器使程序工作了。在游戏应用程序中,定时器是非常重要的,因为如果没有这种方法,想要让我们的程序运行要复杂的多。
好让我们进一步的学习有关可分享的时间。这是一个什么样的机制呢?我们来看看。由操作系统发送WM_TIMER 消息到我们的WndProc开始。消息将会在我们规定了的时间间隔再次发送送SetTimer(...)。如果间隔是500毫秒。 那意味我们需要在下次发送之前完成我们的工作。如果不能完成,那我们只好等下一次了。我们将失去500毫秒。
在两次时间的间隔中我们需要做游戏需要的大量的工作,例如:处理输入和游戏人工智能,移动对象,碰撞检查等等。如果你设计游戏的运动速率是8帧/秒,所以你有最多125毫秒(1000/ 8 = 125毫秒)的时间处理每个帧。不过不必担心125毫秒会不会够用,因为我们的PDA运行速率是100-200 MHz。它能立刻处理超过一个百万指令。
请注意,定时器和回调函数象迈步一样不停的转换。在处理以后一个定时器消息后,需要回到系统。在这意味着我们暂时放弃了应用程序的控制权。直到消息再次到达,我们的代码才会再次运行。
导读
在游戏程序中的一个重点是背景的滚动。大多数的动作游戏,射击游戏都使用背景滚动的技术。相对于我们游戏宽广的背景空间(例如太空)来说,我们的屏幕实在是太小了。
背景移动技术
我们有若干种方法移动我们的背景。 例如,侧滚(横滚),上下滚(竖滚),等轴滚动,向4个方向滚动等等。每种方法都有超过一种的实现技术。以前,计算机速度很低,要想在上面运行一些游戏,使它们有一个可以滚动的背景,可能需要专用硬件设备。现在,最低配置的PDA的速度大约是30MHz,而Pocket PC的速度达到了160-206 MHz。 因此,我们能通过软件来生产背景移动的效果,而不用额外的硬件。
大多数的滚动(也是经典的)是向下滚动。一些经典的太空射击游戏大多使用这种方法的,象Galaka,隼等等。 现在,我们将开始学习它。
虽然我们看到,硬件技术的发展使以前的机器和现在相比不可同日而语。机器早就摆脱了1MB或者更少的存储空间的限制。但如果你放一张拥有256种颜色,大小为240X320象素的图象(非-移动)到机器上。它大约需要占用75KB的内存。显然太多了。 因此游戏程序员们需要一种合适的技术可以使它执行效率得到提高。
一个经典的空间背景
这个典型例子使用的技术是用黑颜色填满屏幕。因此我们不需要任何额外的资源。它看起来像你在太空里。然后画出一些星星,从屏幕的顶部飞到屏幕的底部(也可以从右向左)。 我们怎么制做一个逼真的星空呢?首先我们可以用一些白色的或者亮颜色的象素代表星星。近处的星星应该比远处的星星更加明亮些。而且也应该移动的更快。
好我们来看一下我们的算法:
1、设定一个结构变量用来表示星星
2、初始化星星的数据库
3、用黑颜色添充画面
4、画星星(仅画一个象素) 从数据库到画面以外
5、更新星星位置(增加[y]值)
6、更新当前的屏幕
7、重复的从第3步开始循环
<下面是一个星空的源码>
#include
//*** TODO: insert off screen code here ****
//*** The sample code for stars ***
#define MAXSTAR 100
typdef struct
{
int x, y;
int t;
} STAR;
static STAR *stars;
void InitStars(void)
{
int cnt;
stars = (STAR*)malloc(sizeof(STAR)*MAXSTAR);
for(cnt=0; cnt 320)
{
stars[cnt].x = Random() % 240;
stars[cnt].y = 0;
stars[cnt].t = Random() % 255;
}
}
}
LRESULT MainWndProc(HWND hWnd, UINT message, WPARAM uParam, LPARAM lParam)
{
switch(message)
{
case WM_WMTIMER:
DrawStars();
UpdateDisplay(hWnd);
break;
case WM_CREATE:
InitOffscreen(240,320);
InitStars();
break;
case WM_DESTROY:
DeinitStars();
DeinitOffscreen();
PostQuitMessage(0);
break;
case WM_KEYDOWN:
DestroyWindow(hWnd);
break;
default:
DefWindowProc(hWnd, message, uParam, lParam);
}
return 0;
}
让我们看看它是如何工作的?
一颗星星由位置(x,y)与距离(t)组成的变量表示。初始的位置可能在屏幕的任何位置(随机的)。距离(深度) 是被设置成0-255之间。我们通过深度计算星星移动的速度和亮度。在上面给出的例子源码中,我们共画了100颗星。这已经足够模拟出令人激动的无垠的太空了。还有当一颗星星移动出屏幕时,我们会使它重新回到屏幕,并重新为它分配新位置和新距离。
现在,让我们把一个飞碟精灵放到屏幕的底部。在屏幕上显示出的效果好象我们的飞碟正在无垠的太空中飞行。
<下面是我们的第二段源码>
HBTIMAP hUFO;
....
LRESULT MainWndProc(HWND hWnd, UINT message, WPARAM uParam, LPARAM lParam)
{
switch(message)
{
case WM_WMTIMER:
DrawStars();
TransparentImage(hOffscreenDC,100,300,24,20,hUFO,...);
UpdateDisplay(hWnd);
break;
case WM_CREATE:
InitOffscreen(240,320);
InitStars();
hUFO = LoadBitmap(hInst,(TCHAR*)1);
break;
case WM_DESTROY:
DeleteObject(hUFO);
DeinitStars();
DeinitOffscreen();
PostQuitMessage(0);
break;
case WM_KEYDOWN:
DestroyWindow(hWnd);
break;
default:
DefWindowProc(hWnd, message, uParam, lParam);
}
return 0;
}
我们还能改进这一技术,来使它看上去更加的逼真。例如,让一部分星星闪烁,画一个彗星,用一个可以产生动画效果的星星的精灵替代象素表示的星星。
这些技巧可以被用于侧滚但是这样应用好象缺乏空间的感觉。另外显得和其他游戏式样格格不入。
好了,还是让我们学习些其他的技巧吧....
贴图
另一种流行技术是使用一张贴图。一旦我们掌握了这项技术,我们就能够设计大部分的2维游戏背景。现在, 让我们看一看下面的贴图或块字符。他们都有一样的边(边长)。
<图1 - 贴图块>
贴图的这一特征可以使它能够被加入到另外的一个块壮区域中。我们能让贴图在上面生产一个游戏的背景。象illustrated bellow(我想是一个游戏)一样。
<图2 - 地图的例子>
但是我们能怎么实现它?什么是我们需要实现的?答案是贴图和一个地图数据。好,按照你在上面看到的贴图,把他们放进一幅单个的位图文件,它看起来像...
<图3-贴图的位图文件>
我们这样放置他们是为了减少位图文件的数量。如果不这样做我们将需要超过10个位图文件。为了能够区别单个的贴图文件,我们把他们划分成行和列。
地图数据是什么? 地图数据意味着用数据的手段来表示地图。 在一般的情况下,我们总是使用文本文件描述一张地图。让我们看上面位图,它使用了3行8列。我们将用字母A代表第一行第一列,B代表第一行第二列... 直到最后一块,就象下面显示的样子;
<图4 - 地图数据>
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
W
X
Y
好了我们用字符“A”代表了第一个单独的贴图,B是第二个,C,D,等等代表另外的贴图方块。使用这种方法,我们可以用任意的文本编辑器设计我们的地图。
因此,样品地图<图2>的地图数字可以这样表示;
<图5- 图2的地图数据>
"AAAAAAAAAAA"
"KRRLAOWPAAA"
"ICCJAMBAAAA"
"SQQTAXUYAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA"
现在,我们需要一个解码程序来解释地图数据,使得将最后的结果输出到我们的屏幕。
<我们的第三段源码>
void RenderTileMap(HDC hDC, BYTE *pMapData, int nMapCX, int nMapCY)
{
int a, b, c, srcX, srcY, dstX, dstY;
int nTileSizeCX, nTileSizeCY;
nTileSiezCX = 30;
nTileSizeCY = 30;
dstY = 0;
for(a=0; a
static BYTE MapData[] =
"AAAAAAAAAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA"
"AAAAAAAAAAA";
....
case WM_TIMER:
nStartPosition += nOffset;
nStartPosition = max(0,min(nMapCX-8));
RenderTileMap(hOffscreenDC,MapData+nStartPosition,11,9);
UpdateDisplay(hWnd)
break;
case WM_KEYDOWN:
if((WORD)uParam == VK_LEFT)
nOffset = -1;
else
nOffset = 1;
break;
case WM_KEYUP:
noffset = 0;
break;
....
程序从地图的最左方启动,当到最右方时则滚动屏幕,它会不停的循环直到你停止程序。
这个技术能有效的改进你的游戏。它能被用于许多游戏。虽然比起第一种技术它需要更多的内存,但它绝对是值得一用的技术。
全景
全景或视差是一种很流行的的技术,被许多游戏使用。当计算机有了更多的内存时这个技术被开发存储更大的图片。让我们看下面的图片。图片的从右到左都是没有任何缺陷的,并且图片非常的精细。
<图6>
现在, 让我们试试这代码。
<第五段源码>
static int nRunning;
static HBITMAP hBitmap1;
static HDC hDCBitmap;
case WM_CREATE:
hBitmap1 = LoadBitmap(...);
hDCBitmap = CreateComaptibleDC(GetDC(0));
SelectObject(hDCBitmap,hBitmap1);
nRunning = 0;
....
break;
case WM_TIMER:
if(++nRunning > nBitmapWidth)
nRunning = 0;
n1 = 320 - nRunning;
PatBlt(hOffscreenDC,0,0,240,320,BLACKNESS);
BitBit(hOffscreenDC,0,320-55,min(n1,240),55,hDCBitmap,nRunning,0,SRCCOPY);
if(n1 < 240)
BitBit(hOffscreenDC,n1,320-55,240-n1,55,hDCBitmap,0,0,SRCCOPY);
UpdateDisplay(hWnd);
break;
....
如果你试了它你将会注意到,背景的移动是光滑的,漂亮的,并且和使用贴图比更容易实现。而且你可以以象素为单位移动背景。好,我们在背景上加上一个精灵看看。 试试下面的代码;
<源码6> 请从原文网站下载,这里就不提供了。
--> Clouds.bmp.zip
--> Mountain.bmp.zip
--> Road.bmp.zip
--> Panorama.demo.zip
...NYI
它怎么工作?你能看到我们有3张平行的背景图片。先出现一张云照片它在最后面,是一个静止的图象。山脉图片是一个精灵。它被画在云照片上。最后一个是一条道路的图片。它放在最顶上。使用不同的移动速度,我们能生产非常有趣的游戏。
我们把它们融合到一起使用怎么样!!完全正确,并没说一个游戏中使用一种技术。为了做到领先一步,我们往往需要使用超过一种技术来生产更有趣的游戏。另外,计算机技术的发展回带来更新的开发技术。也许我们今天讨论的技术并不适合现在的PDA设备,但掌握它们肯定是不会错的。
还有其他的技术吗?当然,还有有很多有趣实用技术,但在这里讨论它们显得太复杂了,所以我并没有在这个初级教程中涉及它们。
导读
或许,所有的动作类游戏都需要实现对碰撞检查。换句话说,一个游戏不能没有相撞检查或碰撞测试相对应的策略。
检查相撞有若干种方法,发生碰撞或两个或者多个的精灵对象发生了重叠,则每个游戏会依据自己的规则给出相匹配的方法。例如,一个射击的游戏(就一个小的精灵) 需要检查是否有子弹或者武器撞到了另外的对象。 如果我们的对象很小并且正在高速的移动。我们可以使用简单的形状覆盖方法,来进行判断是否碰撞。我们只需要简单的判断是否在一个矩形区域发生了重叠。这样做不会造成视觉上的不舒服,因为人的眼睛是很容易被欺骗的。但是如果游戏是象空手道(我喜欢武将争霸;-))那种类型,那么我们需要更进行详细的检查了。因为 它的精灵很复杂而且形状较大。我们需要把角色的身体划分解成很多小的单元,手,头,腿,脚,身体,or a punch(雷神更喜欢双截棍,哈哈)。还有通常它们的移动速度不是太快。因此我们需要另外的更好一些的方法来使游戏更加有趣。
基本的方法
推箱子游戏: 推箱子游戏使用的技术曾经是很流行的经典的技巧,尤其是对于所有的初学者它比较容易理解。 它长时间的被用于小游戏,象Pac人(吃豆,我喜欢),经典的炸弹人,Sokoban(雷神不知)等等。主要的思路就是把游戏中的每一个事件都放到一个数组矩阵中。
<图1-游戏推箱子>
这种方法的主要的策略是使用一个二维数组表示游戏中的所有事物。每个数组元素永远占用一个字节(0-255)。数组元素保存地图的状况。里面都是什么呢?我们用Pac-man游戏为例说明一下,我们预先规定1代表通道,2代表墙,3代表幽灵,4代表Pac-man。我们的数组在游戏中的数据看起来就象下面这样
<图2-地图表示>
00 00 00 00 01 01 01 01 01 00 00 00 00 00 00 00 00 00 00
00 00 00 00 01 00 00 02 01 00 00 00 00 00 00 00 00 00 00
00 00 00 00 01 02 00 00 01 00 00 00 00 00 00 00 00 00 00
00 00 01 01 01 03 00 00 01 01 00 00 00 00 00 00 00 00 00
00 00 01 00 00 02 02 00 00 01 00 00 00 00 00 00 00 00 00
01 01 01 00 01 00 00 00 00 01 00 00 00 01 01 01 01 01 01
01 00 00 00 01 00 00 00 00 01 01 01 01 01 00 00 03 03 01
01 00 02 00 00 02 00 00 00 00 00 00 00 00 00 00 03 03 01
01 01 01 01 01 00 01 01 01 00 00 01 00 01 01 00 03 03 01
00 00 00 00 01 00 00 00 00 00 00 01 01 01 01 01 01 01 01
00 00 00 00 01 01 01 01 01 01 01 01 00 01 00 00 00 00 00
这种方法只适合一些经典的小游戏,因为它实际上并不对真实对象形状相撞进行检查。它只是检查每个块的状态而已。此外,游戏对象和块的尺寸应该是一样的,精灵的身体也应该尽可能的接近方形的形状。检查的算法是容易实现的,只需要检查当前的块将移动的下一块的值。给个例子Pac-man: 如果下一块的值1,说明那是一个通道,我们将原来的值是4(代表Pac-man)的块改成1,把刚才得到的值1改成4就行了。如果下一块是2意味着那是墙,我们不能移动到那里,如果是3意味着我们的pac-man英雄被幽灵抓住了。
这种方法非常的快并且它只需要很少的处理时间。如果你想为速度不快的机器开发游戏,那这是一个不错的选择。
遮罩图形: 它是从贴图方法发展而来的。关键技术是增加贴图的分辨率并且减小每块贴图的尺寸。这样做的代价是需要更多的内存。在屏幕上显示的效果实际上是遮罩图形和原来图片经过处理后的结果,见下图;
INCLUDEPICTURE "../../Documents%20and%20Settings/li/My%20Documents/天极Yesky%20-%20全球中文IT第一门户%20-%20软件%20-%20在Pocket%20PC上编写游戏之八1.files/122965.gif" \* MERGEFORMATINET
<图4-图形与遮罩图形>
让我们理解起来更容易些: 我们可以使用一张图片作为遮罩图形,并且使用不同的颜色区别出我们的游戏中的不同对象。 例如,在一个射击的游戏中,背景被黑颜色代表,红颜色代表弹药,蓝颜色代表游戏者,绿颜色代表电脑的边界, 黄颜色代表地图上的障碍物。当我们改变对象在显示屏幕上的位置时,我们需要同时更新它在遮罩图形中相对应的位置。
<少许5地图和遮罩图形>
好现在我们来看看如何检查碰撞? 我们需要按顺序和层次画出对象;首先是电脑部分,然后是游戏玩家的部分,最后是子弹。在进行碰撞检查之前,我们可以使用遮罩图形先获得对象的象素和颜色值。 例如,我们更新子弹,需要从遮罩图形得到子弹的形状包含的所有象素并且可以获得原来的颜色值。如果它与背景颜色一样,那意味着没有事情发生。如果它是红或蓝色的,那意味着子弹碰撞到了游戏或电脑对象。根据这些,我们就可以进行碰撞检测,判断出哪个对象被子弹击中。
...........................
我们已经讲述了两种碰撞检测的方法,他们都使用的是光栅技术。让我们想象一下,如果一个游戏有一张巨大无比的地图场景,如果我们还使用上面的策略,那我们就需要一张同样巨大无比的遮罩图形。结果是需要非常多的内存。显然这不是我们希望的,怎么办?让我们在进一步学习利用向量技术来进行碰撞检测。
PtInRect(...)
PtInRect(...) API。 这是Win32 SDK下面的一个标准的API。它可以计算出一个点(x,y)是否存在于一个矩形区域(x1,y1,x2,y2)中,它的返回值是真或假。
<图 6 点在矩形中?>
Point P(x,y) will be in Rect(x1,y1,x2,y2) area when
x >= x1 && x < = x2 && y>= y1 && y < = y2
这种方法始终检查一个点是否在对象的范围内。在我的纸牌游戏中,使用到这个方法。可以通过它知道,我的笔尖点中的是所有长方形扑克牌中的那一张。
一个使用PtInRect函数的游戏例子。
游戏开始后不停的随机产生一个任意大小的长方形,并显示在屏幕上,玩家需要使用笔去点击这些长方形,如果经碰撞检测证明点中,则长方形消失。如果不能及时的消灭最上面的长方形,它将会被新产生的遮盖住,当屏幕上的长方形数量达到32个时,你便GAME OVER了。
请到原文网站下载这个游戏的EVC工程文件...
IntersectRect(...)
这个API可以使用在标准Windows下。作用是判断两个长方形之间是否有重叠的区域。它也返回真或假显示重叠的状况。长方形数据类型是的一个Win32 SDK声明了的标准的数据类型,使用四个整数来描述一个长方形区域。 它们是left, top, right, bottom。类型声明如下:
type _tagRECT
{
int left, top, right, bottom;
} RECT;
<图7 两个矩形是否有重叠?>
我一直使用这种很典型的方法来快速的进行常规的碰撞检查。
检测距离的方法。前面的方法都是用来检查一个点是否在一个规定的矩形范围中。如果你的游戏中有两个圆形对象。并且你想知道对象A到对象B有多远,你该怎样做才能得到他们之间的距离呢?如果这个距离小于两对象的半径之和,说明这两个对象是重叠的。下面的图形象我们说明了判断两个对象之间的距离是很容易的,我们的算法是: c2 = a2 + b2.
<图8-距离检测>
A = p1.x - p2-x;
B = p1.y - p2.y
C = sqrt(A*A+B*B);
假定R1是蓝圆的半径,R2是红圆的半径,如果C小于R1和R2的和,说明两个圆是重叠在一起的。
让我们想一下星际航行的游戏,用一个圆表示行星,用一个圆表示太空船,然后碰撞检查,在两个对象之间进行简单地计算距离。
<图9 Asteroid游戏>
高级方法 - 精灵的重叠判断。
现在的2D游戏的特色是有许多形状差别很大的精灵。为了节约计算时间,我们需要一个超越第一种方法的解决
方案
气瓶 现场处置方案 .pdf气瓶 现场处置方案 .doc见习基地管理方案.doc关于群访事件的化解方案建筑工地扬尘治理专项方案下载
,应用到每对象类型。在我的射击游戏中,我用一个特殊方法来检查子弹和其他的对象是否发生碰撞。因为,子弹是很小的并且快速移动的。我同样也用特殊的方法来检查两个精灵对象之间是否发生碰撞。因为,他们又太大了。
基本思路:首先我用了一个对象把精灵对象的边界进行封装。然后使用快速的边界重叠的方法来进行碰撞检查。如果对象边界是不重叠的,则说明对象没有被击中。如果边界发生重叠,说明对象被击中了。见下图。我们需要使用一种高明的检查方法,计算所有重叠象素。
1、如果对象边界正在重叠
2、是,计算重叠的区域, 转到步骤4
3、没有,没重叠,退出判断
4、在重叠的长方形中计算真实的重叠形状
5、计算重叠的象素
6、返回重叠的象素的数字
<图10 精灵击中测试>
导读
如何发出声音?我们首先需要了解Pocket PC机器的硬件体系结构。我们有双声道设备(立体声模式)用来播放WAVE音频。不过有的设备只有一个声道(单音的模式)。如果我们有双声道,那么声音电路能生产的音频范围可以从8KHz到44HKz。如果每条声道能分别地工作,我们便能产生简单的动态的声音。不幸地是技术上这是不可能的。
我们使用API函数MessageBeep(...)可以制造出声音。它听上去与Windows的典型的系统报警声音一样。它可以被应用到一些小的简单游戏中,这是在游戏中产生声音或音响效果的最容易和最安全的方法。同时还可以减小你的游戏的文件大小。