zhu1234 2008-4-7 22:00
Pocket PC的游戏编程指导教程(共十章) (转)
第一章:工具 在Pocket PC上编写游戏首先需要的就是微软的“eMbedded Visual Tools 3.0”。它是可以免费下载的(省略),但文件大小超过300M,所以请选择适当的时候下载(用28.8K的Modom需要大约30个小时)。你也可以在软件零售商店买到它的CD-ROM版本。请访问微软的网站来获取更多的信息。 eMbedded Visual Tools包含C和C++的编译器,链接器,C和Windows的标准库,标准的包含文件,集成开发环境,模拟器,帮助文件和开发工具。其中最大的就是这个开发包的基础知识库——MSDN。推荐在Windows NT或Windows 2000上安装使用。因为模拟器无法在除此之外的操作系统上工作。 因为Windows CE被设计成可以支持不同种类的CPU,如StrongARM、MIPS、SH3、SH4和X86。它也支持不同的设备,例如手持设备,口袋设备或者是移动电话。但是在将来,我们或许会在住宅电话、电视、微波炉等设备上见到它,当然,其他设备也都有可能。 我们可以把EVC分成三个重要的部分:IDE(集成开发环境),各种CPU平台的开发包和ActiveSync。 这个EVC的集成开发环境有些类似Microsoft Visual Studios的集成开发环境。你还需要安装各种平台的开发包和支持每个平台的特定文件(微软也提供这些平台的构建开发包,供你开发你自己的Windows CE设备)。安装程序将询问你是否安装其他开发平台,如HPC、PsPC和PPC。如果你不打算开发其他设备的软件,你可以不安装它们。当然,这个方法可以使你用这个工具开发其他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的资源脚本文件格式。 未完,待续... [ Last edited by amlj on 2004-8-21 at 12:46 PM ] 2004-8-20 03:43 PM #1 特别推荐: amlj 布口袋 UID 16662 精华 0 积分 8 帖子 196 技术 6 资源 5 信用 0 金币 325 阅读权限 10 注册 2004-3-4 来自 成都 状态 离线 Pocket PC的游戏编程指导教程 第三章:离屏技术(也就是我们常说的屏幕缓冲区) 导读 在我们学习离屏(OFF SCREEN)技术之前,让我们看看ON SCREEN是什么?为什么没有任何游戏程序使用这种方法?此外,还有另外一个基础问题,我们怎么在窗口的客户区绘制文字、图片或者是图画? 通常,在屏幕上显示任何东西的方法是使用GDIs(图形设备接口)和APIs(应用程序接口)。Windows的窗口区域被划分成两个重要部分:客户区和非客户区(比如菜单、标题栏和边界框)。大多数窗口是可以移动的。因此它所显示的内容也跟着窗口自己的左上角一起关联移动。GDIs和APIs帮助我们管理这种关联。 Windows的GDI是用来对所有硬件设备提供一种硬件无关支持的程序。因为各个厂家的硬件技术是不同的,所以用这些相同的代码是无法获得硬件的最大性能。实现它们的目的只是保证支持。然而,很多有些开发者想要获得硬件设备的最大性能,他们不用GDIs和APIs,而是直接访问硬件。当然这些方法也可以工作,但却依赖于使用的硬件,它们可能无法在全部的设备上工作。 Windows CE的显示技术又如何呢?有人都能改变显示适配器吗?当然不能,因为它是一种嵌入式系统。通常硬件厂商不会在系统中包含优化代码。然而,这种显示速度已经能够应付某些游戏类型了。 Windows GDIs 设备正文的句柄,通常表示为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是鼠标事件消息。我们将在笔尖接触屏幕、笔尖移过屏幕和笔尖离开屏幕时分别收到它们。