哪里学游戏大连开发区哪里好玩软件技术好一些?

今天第一次登陆,加一个积分
MM用户登录
请输入手机号码:
请选择登录模式:
随机短信 固定密码 (
秒后还没有收到,请重新获取。
请获取短信密码:
请输入短信密码:
请输入固定密码:
记住我
开发者登录
帐户名:
密 码:
记住我
很详细的 c游戏软件开发教程不可不看
(白河愁)
当前离线
8278 帖子
79 精华
0 阅读权限
60 来自
广东-广州市 在线时间
11 小时 注册时间
2009-8-13 最后登录
2009-11-9 精通武艺
8278 积分
310 学分
0 来自
广东-广州市 字体大小:
发表于 2009-9-10 09:46
很详细的 c游戏软件开发教程不可不看
在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上编写游戏之二
这一章主要的目标是使你的程序能够在机器上运行。当然,我们需要遵守操作系统规则。那么我们怎样创建一个应用程序呢?
  这里有一段很短的代码,它可以在你的Pocket PC上运行。
#include "windows.h"
LRESULT MainWndProc(HWND hWnd, UINT me age, WPARAM uParam, LPARAM lParam)
 if(me age == WM_LBUTTONDOWN)
  DestroyWindow(hWnd);
  return 0;
 return DefWindowProc(hWnd, me age, uParam, lParam);
int WinMain(HINSTANCE hI tance, HINSTANCE hPrevI tance,
LPTSTR lpCmdLine, int nCmdShow)
 WNDCLASS wc;
 memset(&rc,0,sizeof(wc));
 wc.style = CS_HREDRAW|CS_VREDRAW;
 wc.lpfnWndProc = (WNDPROC) MainWndProc;
 wc.hI tance = hI tance;
 wc.hbrBackground = GetStockObject(WHITE_BRUSH);
 wc.l zCla Name = L"mya ";
 if(RegisterCla (&wc))
  HWND hWndMai   if(hWndMain = CreateWindow(L"mya ",L"Hello",WS_VISIBLE,0,0,240,320,0,0,hI tance,0))
  {
   MSG msg;
   ShowWindow(hWndMain, SW_MAXIMIZE);
   UpdateWindow(hWndMain);
   while(GetMe age(&msg,hWndMain,0,0))
    Di atchMe age(&msg);
    return msg.wParam;
  }
 return 0;
  好了,它可以运行了,它将显示一个空白的屏幕,并且在你点击屏幕后结束。如果你是刚刚学习编程,而且从来没有使用过EVC或是VC,你可以按照下面步骤操作:
  执行EVC
  选择FILE菜单的NEW...项
  选择WCE A lication并且点选你所需要的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结构,通过使用RegisterCla 这个系统API(应用程序接口),来将这些信息告诉操作系统。如果操作系统接受你的信息,这个函数返回一个非零值。
  WNDCLASS结构中有三个参数比较重要,hI tance、l zCla Name和lpfnWndProc. “hI tance”是一个用来识别你应用程序的唯一的数字(或是进程ID).你将会从操作系统中得到这个值。我们将在程序的几个地方用到它。“l zCla Name”是当我们创建窗口时用来识别应用程序自己的唯一的字符串。最后的一个是“lpfnWndProc”。它是一个用来获得系统通知消息的函数名称。
  其他的成员可以被设定为零,而且不会有任何错误。但是我们通常通过hbrBackground来设定窗口的背景颜色。如果这个成员不设定(也就是赋值为零),系统将不会填充窗口的客户区,而是给你发送WM_ERASEBKGND消息,然后由你自己来填充背静。
  下面的步骤是创建主窗口。这里的“窗口”是指一个与其他运行中的应用程序共享着的显示区域。因为你的应用程序需要这个区域来显示正文(菜单条、标题栏、客户区等等)。它也被用来获得鼠标动作(鼠标移动、鼠标点击等等)。窗口是拥护和程序交互的途径,CreateWindow函数创建一个窗口(一个应用程序可以拥有一个或多个窗口)。这个窗口需要联系到刚才用RegisterCla 注册的数据。它的第一个参数必须和WNDCLASS的l zCla Name成员一致。
  如果函数成功了,它返回一个窗口的句柄(HANDLE),变量类型是HWND。这是一个唯一的32bit (双字)值,用来识别所创建的窗口。如果失败,它返回空(就是零值)。
  创建的窗口并没有立即显示到屏幕上。这里有两个函数——ShowWindow和UpdateWindow,供你将窗口显示到屏幕上。通常Pocket PC的应用程序在它们运行时占据整个屏幕(因为Pocket PC的屏幕实在是太小了)。我总是使用代码以获得最大的屏幕显示区域。
  接下来,程序进入一个循环。就是这个循环保证了多任务系统的工作。所有的系统消息通过Di atchMe age被发送到住窗口过程MainWndProc。循环将在窗口被DestroyWindow销毁或被系统终结后退出。退出循环后,程序被系统杀掉。你的应用程序信息也被自动移除。
  有没有其他简单的方法让一个程序在Pocket PC上执行?当然有,那就是利用现有的窗口类来开始你的应用程序,因为你不需要注册一个新的窗口类。这样的代码将比本文介绍这个小。通常就是一个基于对话框的应用程序。我不打算讲这个方法,因为,这样将失去一些控制应用程序的方法,而且需要花更多的时间来学习Win32的资源脚本文件格式。
在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 me age, WPARAM uParam, LPARAM lParam)
PAINTSTRUCT HDC hDC;
RECT rcClient;
switch(me age)
case WM_PAINT:
hDC = BeginPaint(hWnd, );
GetClientRect(hWnd,&rcClent);
DrawText(hDC,L"Hello",5,
&rcClient,DT_CENTER|DT_VCENTER);
EndPaint(hWnd, );
case WM_LBUTTONDOWN:
DestroyWindow(hWnd);
default:
DefWindowProc(hWnd, me age, uParam, lParam);
return 0;
  这些代码中GetClientRect用来获得整个客户区域的矩形范围,DrawText用来在屏幕上这个举行范围的中心画出文字"hello"。
  当然我们也可以在别的地方进行绘制,你可以再试试下面这些代码。
LRESULT MainWndProc(HWND hWnd, UINT me age, WPARAM uParam, LPARAM lParam)
HDC hDC;
int nXpo int nYpo switch(me age)
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);
case WM_KEYDOWN:
DestroyWindow(hWnd);
default:
DefWindowProc(hWnd, me age, 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);
Elli e(hDC,nX-20,nY-20,nX+20,nY+20);
SelectObject(hDC,hOldBrush);
SelectObject(hDC,hOldPen);
LRESULT MainWndProc(HWND hWnd, UINT me age, WPARAM uParam, LPARAM lParam)
HDC hDC;
switch(me age)
case WM_LBUTTONDOWN:
hDC = GetDC(hWnd);
_drw_object(hDC,LOWORD(lParam),HIWORD(lParam));
ReleaseDC(hWnd,hDC);
case WM_MOUSEMOVE;
hDC = GetDC(hWnd);
PatBlt(hDC,0,0,240,320,WHITENESS);
_drw_object(hDC,LOWORD(lParam),HIWORD(lParam));
ReleaseDC(hWnd,hDC);
case WM_LBUTTONUP:
hDC = GetDC(hWnd);
PatBlt(hDC,0,0,240,320,WHITENESS);
ReleaseDC(hWnd,hDC);
case WM_CREATE:
hbrRed = CreateSolidBrush(RGB(255,0,0));
hpeBlue = CreatePen(0,0,RGB(0,0,255);
case WM_DESTROY:
DeleteObject(hbrRed);
DeleteObject(hpeBlue);
PostQuitMe age(0);
case WM_KEYDOWN:
DestroyWindow(hWnd);
default:
DefWindowProc(hWnd, me age, 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 UpdateDi lay(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);
 Elli e(hDC,nX-20,nY-20,nX+20,nY+20);
 SelectObject(hDC,hOldBrush);
 SelectObject(hDC,hOldPen);
//窗口过程
LRESULT MainWndProc(HWND hWnd, UINT me age, WPARAM uParam, LPARAM lParam)
 switch(me age)
  case WM_LBUTTONDOWN:
   _drw_object(hOffscreenDC,LOWORD(lParam),HIWORD(lParam));
   UpdateDi lay(hWnd);
   break;
  case WM_MOUSEMOVE;
   PatBlt(hOffscreenDC,0,0,240,320,WHITENESS);
   _drw_object(hOffscreenDC,LOWORD(lParam),HIWORD(lParam));
   UpdateDi lay(hWnd);
  break;
  case WM_LBUTTONUP:
   PatBlt(hOffscreenDC,0,0,240,320,WHITENESS);
   UpdateDi lay(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();
   PostQuitMe age(0);
   break;
  case WM_KEYDOWN:
   DestroyWindow(hWnd);
   break;
  default:
   DefWindowProc(hWnd, me age, uParam, lParam);
 return 0;
  这些代码显示了一个以GDI为基础的离屏表面,它是怎么工作的呢?
  让我们解释这些代码,InitOffscreen函数中,使用简单的GDIs和APIs——CreateCompatibleDC和CreateCompatibleBitmap创建一个虚拟屏幕。
  DC,也就是设备文本,是一种调用设备例程(或者是设备驱动例程)的方法。因为我已经在前面的Windows的GDIs部分大概介绍了它,这里我们来学习更多有关它的其他方面。设备文本也有三个主要的类型:它们是显示设备文本、打印设备文本、和内存设备文本。如果我们在打印文本上画线,设备文本将会调用打印机驱动程序中的画线程序来完成这一过程。如果是在显示文本上画线,调用的自然也就是显示驱动程序中的例程。如:
hDC = CreateDC("Printer",0,0,0); // 创建缺省的打印文本
hDC = CreateDC("Di lay",0,0,0); // 创建的显示文本
hDC = GetDC(hWnd); // 获得窗口客户区的显示文本
hDC = GetWindowDC(hWnd); // 获得整个窗口的显示文本(包含非客户区)
  换句话说,GDIs可以理解为是一个通过DC选择器来实现的硬件设备的通路。这个通路可以让你以共同的方法控制图形设备,包含一些非原始的设备。
  要了解更多有关Windows图形设备接口(GDI)的和设备驱动的概念,请参考MSDN。
  而什么是内存设备文本呢?可以这么说,它就是虚拟屏幕!CompatibleDC函数创建一个和源设备文本一致的新的设备文本,但它只存在于内存中,并不关联设备。下一步,我们需要分配一个内存块,也就是用来储存图象的屏幕缓冲区。Windows的位图对象就是实现这个的一种方法。我们可以用CreateCompatibleBitmap来创建一个和屏幕相同象素位数,相同颜色格式、相同调色板数目的位图,通过SelectObject将它选入内存设备文本。这样,一个离屏的虚拟屏幕就建成了。
  之后,内存设备文本就可以象普通的显示设备文本一样使用了,我们可以将它用在各种GDIs和APIs中,在它上面做图,将它画到其他设备文本上,等等。如,在UpdateDi lay中,我们就用位块传送函数BitBlt将它拷贝到了窗口的客户区域。这个函数在各种设备文本上以相同的形式,非常快速的复制以矩形区域为单位的数据。
  在例子程序中,WM_CREATE消息只是在我们的主窗口在内存中被创建时,发送到我们的窗口函数,在整个程序的生存周期内只有一次,因此适合做一些初始化的工作。而WM_DESTROY消息则在窗口别销毁时被发送,可以放入对应内存释放的工作。而WM_MOUSEMOVE、WM_LBUTTONDOWN和WM_LBUTTONUP是鼠标事件消息。我们将在笔尖接触屏幕、笔尖移过屏幕和笔尖离开屏幕时分别收到它们。
在Pocket PC上编写游戏之四
游戏中的大多数物体不是矩形,但是一个位图却是矩形,因此我们必须找到一个方法来绘制物体真正的形状而不是矩形。
  如果我们在游戏编程中谈及图片,通常指的是光栅图象或位图。我会假定你已经了解有关的知识,但下面仍然给出少许解释。
  位图或者光栅图象是一种以数字格式储存照片或图片的方法。它将照片每行每列的象素转化为二进制数据的矩阵。矩阵每个单元格的数据代表图片显示的一个象素。因此,一个位图必然是矩形的。如:
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 for(nCol=0; nCol dwPixel = GetPixel(hMemDC,nCol,nRow);
if(dwPixel != dwTra arentColor)
SetPixel(hDC,nPosX+nCol,nPosY+nRow,dwPixel);
  Alpha通道或掩码图方式使用另外的方法来区分图片中物体和非物体的部分。它使用一个另外的黑白位图,通常被称为掩码位图。掩码位图中白色的象素表示物体的部分。下图则是原图和它的掩码位图。
  此主题相关图片如下:
  这种方法的优点是,它同样适用与Alpha混合方式。这种方式用不同的灰度值代表图片象素和目的象素的混合程度,而白色恰好代表图片象素完全取代屏幕象素,黑色代表完全忽略图片象素。我不打算在这里介绍这种方式,如果你更倾向于GKS(图形内核系统)的概念,你可用这样的两个位图按用下面的步骤完成精灵的绘制:
  1 一个带掩码位图的图片
  2 反转掩码位图
  3 将掩码位图中的象素与目的表面的象素按位与
  4 将原始图片中的象素与目的表面的象素按位或
  用Win32的代码描述如下:
void PutSprite(HDC hDC, int nPosX, int nPosY, int nCX, int nCY, HBITMAP hObj,HBITMAP hMask)
HDC hMemDC, hMaskDC;
hMemDC = CreateCompatibleDC(hDC);
hMaskDC = CreateCompatibleDC(hDC);
SelectObject(hMemDC,hObj);
SelectObject(hMaskDC,hMask);
BitBlt(hMemDC,0,0,nCX,nCY,
hMask,0,0,SRCAND);
PatBlt(hMaskDC,0,0,nCX,nCY,DSTINVERT);
BitBlt(hDC,nPosX,nPosY,nCX,nCY,
hMaskDC,0,0,SRCAND);
BitBlt(hDC,nPosX,nPosY,nCX,nCY,
hMemDC,0,0,SRCPAINT);
DeleteDC(hMemDC);
DeleteDC(hMaskDC);
  Windows中的位图资源
  我们可以在我们的可执行文件中储存位图,方法就是用内部资源。使用这个方法,可以很容易的使用位图,当然你要先创建资源描述的源文件。否则,你将需要从外部文件装载位图。
  想要更多的了解这个资源描述的文件格式,可以查看Win32的程序员手册。下面是一个简单的例子,如果你需要在可执行文件中储存一个位图,你可以新建一个文件,写入如下代码:
#include
1 BITMAP yourfile.bmp
  第一行是包含windows头文件,这里面包含所有必须的预定义符号。第二行分成三个部分:资源ID,资源类型和资源文件名。将这个文件保存为后缀为.rc的文件,然后加入整个工程(方法是在工程环境窗口的工程名字上点右键,选择Add to Project,然后选择你刚才保存的rc文件)。完成这一切后,你就可以在你的程序中使用LoadBitmap函数装载位图了。代码如下:
HBITMAP hBm hBmp = LoadBitmap(hI tance,MAKEI***ESOURCE(1));
Having used the bitmap, you can remove it from memory by using the DeleteObject(...) function.
DeleteObject(hBmp);
——在Pocket PC上的实现——
  十分幸运的是,Windows CE2.0及其以上版本提供一个API函数Tra arentImage,这个函数的用法有些类似StretchBlt,但是使用它时,你可以指定位块传送的透明色,也就是我们上面所说的关键色,这个函数会将位图绘制到目的DC(设备正文)上,同时忽略指定的颜色。代码如下:
// 资源描述文件sample.rc
#include
100 BITMAP ufo.bmp
// 新的startup.c
LRESULT MainWndProc(HWND hWnd,
UINT me age, WPARAM uParam, LPARAM lParam)
PAINTSTRUCT HDC hDC, hMemDC;
HBITMAP hBitma int nCnt;
switch(me age)
case WM_PAINT:
hDC = BeginPaint(hWnd, );
hBitmap = LoadBitmap(g_hI t,MAKEI***ESOURCE(100));
hMemDC = CreateCompatibleDC(hDC);
SelectObject(hMemDC,hBitmap);
for(nCnt=10; nCnt<100; nCnt+=5)
Tra arentImage
(hDC,10+nCnt,10
+nCnt,41,38,hMemDC,0,0,41,38,
RGB(255,0,255));
DeleteDC(hMemDC);
DeleteObject(hBitmap);
EndPaint(hWnd, );
case WM_LBUTTONDOWN:
DestroyWindow(hWnd);
default:
DefWindowProc(hWnd, me age, uParam, lParam);
return 0;
  性能测试?我们当然想知道我们程序的性能,它的性能在我的Pocke PC上是足够用于动作游戏?检测性能的代码十分简单。首先保存开始的时间,将图片画10000次,然后记下结束的时间。两次时间的差将会告诉我们它的性能。代码如下:
DWORD dwTimeStart, dwTimeEnd;
TCHAR szMsg[128];
dwTimeStart = GetTickCount(); // 以返回系统自启动后到现在经过的毫秒数
.... do painting here ...
dwTimeEnd = GetTickCount();
w rintf(szMsg, L"TM = %i",
dwTimeEnd - dwTimeStart);
Me ageBox(0, szMsg, L"Time used", MB_OK);
在Pocket PC上编写游戏之五
  许多开发者喜欢设计具有逼真的动作效果游戏。 如此一来,仅有一些漂亮的静态图像完全提不起他们的兴趣。这个章节我们将学到基本制作动画的技术。
  在这个部分,我们将探讨电脑动画,一系列的图象连续运动从而在显示器上模拟出运动的效果。关于电脑图像可以有很多种方法实现,这完全取决于你采用的编程语言和环境。一种实现动画效果的方法是,先画一个图像在屏幕上,然后擦掉它,然后在画上一个和上一幅略有不同的图像,或者改变一下图像的位置。一组图像按照一定的时序连续的显示,并且后一幅必须完全覆盖前面的一幅,这样给人在视觉上造成连续运动的效果。为了使人看上去没有停顿或跳跃的感觉,图象必须至少以每秒14帧/秒的速度进行交替显示。这是游戏程序动画的标准。有时为了节省内存和CPU资源,也可以将它设定成6-10帧/秒。我们要知道高质量的动画可以达到14帧/秒-30帧/秒。通常电影卡通片采用的是14帧/秒,但它每个帧都被印两次。(这是什么意思,我也不知道,但这句以我的水平只能这样译了。原文:but each frame is printed twice)
在Pocket PC上实现
  要想产生一个简单的动画,我们至少需要2张不同的图片。显示这个动画效果的简单方法是,按照下面列出的步骤来做:
    A =
    B =
  1、将画面以外的部分清除。
  2、将图片A放进显示画面以外,使人们看不到它。
  3、更新显示区域,实际的将图片显示出来。
  4、推迟一些时间让用户看到图片。如果我们需要一个10f 动画,延期时间是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 me age, WPARAM uParam, LPARAM lParam)
HDC hDC, hMemDC;
HBITMAP hBitma int nCnt;
switch(me age)
case WM_LBUTTONDOWN:
hDC = GetDC(hWnd);
hBitmap = LoadBitmap(g_hI t, MAKEI***ESOURCE(100));
hMemDC = CreateCompatibleDC(hDC);
SelectObject(hMemDC, hBitmap);
ReleaseDC(hWnd,hDC);
for(nCnt=0; nCnt<4; nCnt++)
PatBlt(hOffscreebDC, 0, 0, 240, 320, WHITENESS);
Tra arentImage(hOffscreenDC,nCnt*4,0,46,57,hMemDC,
nCnt*46,0,46,57, RGB(255,0,255));
Sleep(100);
UpdateDi lay(hWnd);
DeleteDC(hMemDC);
DeleteObject(hBitmap);
case WM_KEYDOWN:
DestroyWindow(hWnd);
case WM_CREATE:
InitOffscreen(240, 320);
case WM_DESTROY:
DeinitOffscreen();
PostQuitMe age(0);
default:
DefWindowProc(hWnd, me age, uParam, lParam);
return 0;
  你将看见妮雅从你的屏幕上慢慢的向你走来。
  在下一步, 我们想控制妮雅在屏幕上的位置。 好,我将调用一个精灵或游戏对象来控制它在屏幕上的位置。我们需要保存当前的位置信息。在Win32 SDK中,我们可以使用POINT数据类型。POINT类型由2个成员组成。x和y。类型的声明见下;
typedef _tagPOINT
} 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 hObjBitma static POINT ObjPosition[10];
static int nFrameCounter[10];
static SIZE ObjSize;
static void FirstInit(void)
int nCount;
InitOffscreen(240, 320);
hObjBitmap = LoadBitmap(g_hI t, MAKEI***ESOURCE(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
ObjPosition[nCount].x = -(Random() % 100);
nFrameCounter[nCnt]++;
if(nFrameCounter[nCnt] > 4)
nFrameCounter[nCnt] = 0;
UpdateDi lay(hWnd);
DeleteDC(hMemDC);
LRESULT MainWndProc(HWND hWnd, UINT me age, WPARAM uParam, LPARAM lParam)
switch(me age)
case WM_TIMER:
RenerFrame(hWnd);
case WM_KEYDOWN:
DestroyWindow(hWnd);
case WM_CREATE:
FirstInit();
SetTimer(hWnd,1,50,0);
case WM_DESTROY:
Deinit();
PostQuitMe age(0);
default:
DefWindowProc(hWnd, me age, uParam, lParam);
return 0;
在Pocket PC上编写游戏之六
  大多数动作游戏运行时,我们看见游戏在不停的移动着。如果在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 me age, WPARAM uParam, LPARAM lParam)
 switch(me age)
  case WM_TIMER:
   Me ageBeep(0);
   break;
  case WM_KEYDOWN:
   DestroyWindow(hWnd);
   break;
  case WM_CREATE:
   SetTimer(hWnd,1,500,0);
   break;
  case WM_DESTROY:
   PostQuitMe age(0);
   break;
  default:
   DefWindowProc(hWnd, me age, uParam, lParam);
 return 0;
  这个程序的效果是:每500毫秒便会发出哔哔声。
  什么是可分享的时间?
  现在, 我们能让使用定时器使程序工作了。在游戏应用程序中,定时器是非常重要的,因为如果没有这种方法,想要让我们的程序运行要复杂的多。
  好让我们进一步的学习有关可分享的时间。这是一个什么样的机制呢?我们来看看。由操作系统发送WM_TIMER 消息到我们的WndProc开始。消息将会在我们规定了的时间间隔再次发送送SetTimer(...)。如果间隔是500毫秒。 那意味我们需要在下次发送之前完成我们的工作。如果不能完成,那我们只好等下一次了。我们将失去500毫秒。
  在两次时间的间隔中我们需要做游戏需要的大量的工作,例如:处理输入和游戏人工智能,移动对象,碰撞检查等等。如果你设计游戏的运动速率是8帧/秒,所以你有最多125毫秒(1000/ 8 = 125毫秒)的时间处理每个帧。不过不必担心125毫秒会不会够用,因为我们的PDA运行速率是100-200 MHz。它能立刻处理超过一个百万指令。
  请注意,定时器和回调函数象迈步一样不停的转换。在处理以后一个定时器消息后,需要回到系统。在这意味着我们暂时放弃了应用程序的控制权。直到消息再次到达,我们的代码才会再次运行
在Pocket PC上编写游戏之七
  在游戏程序中的一个重点是背景的滚动。大多数的动作游戏,射击游戏都使用背景滚动的技术。相对于我们游戏宽广的背景空间(例如太空)来说,我们的屏幕实在是太小了。
背景移动技术
  我们有若干种方法移动我们的背景。 例如,侧滚(横滚),上下滚(竖滚),等轴滚动,向4个方向滚动等等。每种方法都有超过一种的实现技术。以前,计算机速度很低,要想在上面运行一些游戏,使它们有一个可以滚动的背景,可能需要专用硬件设备。现在,最低配置的PDA的速度大约是30MHz,而Pocket PC的速度达到了160-206 MHz。 因此,我们能通过软件来生产背景移动的效果,而不用额外的硬件。
  大多数的滚动(也是经典的)是向下滚动。一些经典的太空射击游戏大多使用这种方法的,象Galaka,隼等等。 现在,我们将开始学习它。
  虽然我们看到,硬件技术的发展使以前的机器和现在相比不可同日而语。机器早就摆脱了1MB或者更少的存储空间的限制。但如果你放一张拥有256种颜色,大小为240X320象素的图象(非-移动)到机器上。它大约需要占用75KB的内存。显然太多了。 因此游戏程序员们需要一种合适的技术可以使它执行效率得到提高。
  一个经典的空间背景
  这个典型例子使用的技术是用黑颜色填满屏幕。因此我们不需要任何额外的资源。它看起来像你在太空里。然后画出一些星星,从屏幕的顶部飞到屏幕的底部(也可以从右向左)。 我们怎么制做一个逼真的星空呢?首先我们可以用一些白色的或者亮颜色的象素代表星星。近处的星星应该比远处的星星更加明亮些。而且也应该移动的更快。
  好我们来看一下我们的算法:
  1、设定一个结构变量用来表示星星
  2、初始化星星的数据库
  3、用黑颜色添充画面
  4、画星星(仅画一个象素) 从数据库到画面以外
  5、更新星星位置(增加[y]值)
  6、更新当前的屏幕
  7、重复的从第3步开始循环
#include
//*** TODO: i ert off screen code here ****
//*** The sample code for stars ***
#define MAXSTAR 100
typdef struct
int x, y;
} STAR;
static STAR *star void InitStars(void)
int cnt;
stars = (STAR*)malloc(sizeof(STAR)*MAXSTAR);
for(cnt=0; cnt stars[cnt].x = Random() % 240;
stars[cnt].y = Random() % 320;
stars[cnt].t = Random() % 255;
void DeinitStars(void)
free(stars);
void DrawStars(void)
int cnt, eed, bright;
PatBlt(hOffscreenDC,0,0,240,320,BLACKNESS);
for(cnt=0; cnt
stars[cnt].x = Random() % 240;
stars[cnt].y = 0;
stars[cnt].t = Random() % 255;
LRESULT MainWndProc(HWND hWnd, UINT me age, WPARAM uParam, LPARAM lParam)
switch(me age)
case WM_WMTIMER:
DrawStars();
UpdateDi lay(hWnd);
case WM_CREATE:
InitOffscreen(240,320);
InitStars();
case WM_DESTROY:
DeinitStars();
DeinitOffscreen();
PostQuitMe age(0);
case WM_KEYDOWN:
DestroyWindow(hWnd);
default:
DefWindowProc(hWnd, me age, uParam, lParam);
return 0;
  让我们看看它是如何工作的?
  一颗星星由位置(x,y)与距离(t)组成的变量表示。初始的位置可能在屏幕的任何位置(随机的)。距离(深度) 是被设置成0-255之间。我们通过深度计算星星移动的速度和亮度。在上面给出的例子源码中,我们共画了100颗星。这已经足够模拟出令人激动的无垠的太空了。还有当一颗星星移动出屏幕时,我们会使它重新回到屏幕,并重新为它分配新位置和新距离。
  现在,让我们把一个飞碟精灵放到屏幕的底部。在屏幕上显示出的效果好象我们的飞碟正在无垠的太空中飞行。
HBTIMAP hUFO;
LRESULT MainWndProc(HWND hWnd, UINT me age, WPARAM uParam, LPARAM lParam)
switch(me age)
case WM_WMTIMER:
DrawStars();
Tra arentImage(hOffscreenDC,100,300,24,20,hUFO,...);
UpdateDi lay(hWnd);
case WM_CREATE:
InitOffscreen(240,320);
InitStars();
hUFO = LoadBitmap(hI t,(TCHAR*)1);
case WM_DESTROY:
DeleteObject(hUFO);
DeinitStars();
DeinitOffscreen();
PostQuitMe age(0);
case WM_KEYDOWN:
DestroyWindow(hWnd);
default:
DefWindowProc(hWnd, me age, uParam, lParam);
return 0;
  我们还能改进这一技术,来使它看上去更加的逼真。例如,让一部分星星闪烁,画一个彗星,用一个可以产生动画效果的星星的精灵替代象素表示的星星。
  这些技巧可以被用于侧滚但是这样应用好象缺乏空间的感觉。另外显得和其他游戏式样格格不入。
好了,还是让我们学习些其他的技巧吧....
  另一种流行技术是使用一张贴图。一旦我们掌握了这项技术,我们就能够设计大部分的2维游戏背景。现在, 让我们看一看下面的贴图或块字符。他们都有一样的边(边长)。
  贴图的这一特征可以使它能够被加入到另外的一个块壮区域中。我们能让贴图在上面生产一个游戏的背景。象illustrated bellow(我想是一个游戏)一样。
  但是我们能怎么实现它?什么是我们需要实现的?***是贴图和一个地图数据。好,按照你在上面看到的贴图,把他们放进一幅单个的位图文件,它看起来像...
  我们这样放置他们是为了减少位图文件的数量。如果不这样做我们将需要超过10个位图文件。为了能够区别单个的贴图文件,我们把他们划分成行和列。
  地图数据是什么? 地图数据意味着用数据的手段来表示地图。 在一般的情况下,我们总是使用文本文件描述一张地图。让我们看上面位图,它使用了3行8列。我们将用字母A代表第一行第一列,B代表第一行第二列... 直到最后一块,就象下面显示的样子;
  好了我们用字符“A”代表了第一个单独的贴图,B是第二个,C,D,等等代表另外的贴图方块。使用这种方法,我们可以用任意的文本编辑器设计我们的地图。
  因此,样品地图
的地图数字可以这样表示;
"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 dstX = 0;
for(b=0; b c = pMapData[a*nMapCx+b] - 'A';
srcX = (c % nMapCX) * nTileSizeCX;
srcY = (c / nMapCX) * nTileSizeCY;
BitBlt(hDC,dstX,dstY,nTileSizeCX,nTileSizeCY,hDCBitmap,srcX,srcY,SRCCOPY)
dstX += nTileSizeCX;
dstY += nTileSizeCy;
  如果我们想让它移动。很简单,我们的屏幕大小是240x320,但我们将使用240x240显示游戏。如果我们有一张30x30象素贴图。我们需要地图数据用8x8块贴图画满整个屏幕。如果我们想使他可以移动出屏幕的边界,只需要地图数据的宽与比屏幕宽些。在这个例子中我们将地图数据设置成了9行11列。
  用一个整数变量来控制移动。我们声明一个[nStartPosition]变量存放地图的出发点。我们不断的增加或减少这个变量,使屏幕看上去在左右移动。 让我们试用这代码
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);
UpdateDi lay(hWnd)
case WM_KEYDOWN:
if((WORD)uParam == VK_LEFT)
nOffset = -1;
nOffset = 1;
case WM_KEYUP:
noffset = 0;
  程序从地图的最左方启动,当到最右方时则滚动屏幕,它会不停的循环直到你停止程序。
  这个技术能有效的改进你的游戏。它能被用于许多游戏。虽然比起第一种技术它需要更多的内存,但它绝对是值得一用的技术。
  全景或视差是一种很流行的的技术,被许多游戏使用。当计算机有了更多的内存时这个技术被开发存储更大的图片。让我们看下面的图片。图片的从右到左都是没有任何缺陷的,并且图片非常的精细。
  现在, 让我们试试这代码。
static int nRu ing;
static HBITMAP hBitmap1;
static HDC hDCBitma case WM_CREATE:
hBitmap1 = LoadBitmap(...);
hDCBitmap = CreateComaptibleDC(GetDC(0));
SelectObject(hDCBitmap,hBitmap1);
nRu ing = 0;
case WM_TIMER:
if(++nRu ing > nBitmapWidth)
nRu ing = 0;
n1 = 320 - nRu ing;
PatBlt(hOffscreenDC,0,0,240,320,BLACKNESS);
BitBit(hOffscreenDC,0,320-55,min(n1,240),55,hDCBitmap,nRu ing,0,SRCCOPY);
if(n1 < 240)
BitBit(hOffscreenDC,n1,320-55,240-n1,55,hDCBitmap,0,0,SRCCOPY);
UpdateDi lay(hWnd);
  如果你试了它你将会注意到,背景的移动是光滑的,漂亮的,并且和使用贴图比更容易实现。而且你可以以象素为单位移动背景。好,我们在背景上加上一个精灵看看。 试试下面的代码;
请从原文网站下载,这里就不提供了。
--> Clouds.bmp.zip
--> Mountain.bmp.zip
--> Road.bmp.zip
--> Panorama.demo.zip
  它怎么工作?你能看到我们有3张平行的背景图片。先出现一张云照片它在最后面,是一个静止的图象。山脉图片是一个精灵。它被画在云照片上。最后一个是一条道路的图片。它放在最顶上。使用不同的移动速度,我们能生产非常有趣的游戏。
  我们把它们融合到一起使用怎么样!!完全正确,并没说一个游戏中使用一种技术。为了做到领先一步,我们往往需要使用超过一种技术来生产更有趣的游戏。另外,计算机技术的发展回带来更新的开发技术。也许我们今天讨论的技术并不适合现在的PDA设备,但掌握它们肯定是不会错的。
  还有其他的技术吗?当然,还有有很多有趣实用技术,但在这里讨论它们显得太复杂了,所以我并没有在这个初级教程中涉及它们。
在Pocket PC上编写游戏之八
  或许,所有的动作类游戏都需要实现对碰撞检查。换句话说,一个游戏不能没有相撞检查或碰撞测试相对应的策略。
检查相撞有若干种方法,发生碰撞或两个或者多个的精灵对象发生了重叠,则每个游戏会依据自己的规则给出相匹配的方法。例如,一个射击的游戏(就一个小的精灵) 需要检查是否有子弹或者武器撞到了另外的对象。 如果我们的对象很小并且正在高速的移动。我们可以使用简单的形状覆盖方法,来进行判断是否碰撞。我们只需要简单的判断是否在一个矩形区域发生了重叠。这样做不会造成视觉上的不舒服,因为人的眼睛是很容易被欺骗的。但是如果游戏是象空手道(我喜欢武将争霸;-))那种类型,那么我们需要更进行详细的检查了。因为 它的精灵很复杂而且形状较大。我们需要把角色的身体划***成很多小的单元,手,头,腿,脚,身体,or a punch(雷神更喜欢双截棍,哈哈)。还有通常它们的移动速度不是太快。因此我们需要另外的更好一些的方法来使游戏更加有趣。
  基本的方法
  推箱子游戏: 推箱子游戏使用的技术曾经是很流行的经典的技巧,尤其是对于所有的初学者它比较容易理解。 它长时间的被用于小游戏,象Pac人(吃豆,我喜欢),经典的炸弹人,Sokoban(雷神不知)等等。主要的思路就是把游戏中的每一个事件都放到一个数组矩阵中。
  这种方法的主要的策略是使用一个二维数组表示游戏中的所有事物。每个数组元素永远占用一个字节(0-255)。数组元素保存地图的状况。里面都是什么呢?我们用Pac-man游戏为例说明一下,我们预先规定1代表通道,2代表墙,3代表幽灵,4代表Pac-man。我们的数组在游戏中的数据看起来就象下面这样
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英雄被幽灵抓住了。
  这种方法非常的快并且它只需要很少的处理时间。如果你想为速度不快的机器开发游戏,那这是一个不错的选择。
  遮罩图形: 它是从贴图方法发展而来的。关键技术是增加贴图的分辨率并且减小每块贴图的尺寸。这样做的代价是需要更多的内存。在屏幕上显示的效果实际上是遮罩图形和原来图片经过处理后的结果,见下图;
  让我们理解起来更容易些: 我们可以使用一张图片作为遮罩图形,并且使用不同的颜色区别出我们的游戏中的不同对象。 例如,在一个射击的游戏中,背景被黑颜色代表,红颜色代表弹药,蓝颜色代表游戏者,绿颜色代表电脑的边界, 黄颜色代表地图上的障碍物。当我们改变对象在显示屏幕上的位置时,我们需要同时更新它在遮罩图形中相对应的位置。
  好现在我们来看看如何检查碰撞? 我们需要按顺序和层次画出对象;首先是电脑部分,然后是游戏玩家的部分,最后是子弹。在进行碰撞检查之前,我们可以使用遮罩图形先获得对象的象素和颜色值。 例如,我们更新子弹,需要从遮罩图形得到子弹的形状包含的所有象素并且可以获得原来的颜色值。如果它与背景颜色一样,那意味着没有事情发生。如果它是红或蓝色的,那意味着子弹碰撞到了游戏或电脑对象。根据这些,我们就可以进行碰撞检测,判断出哪个对象被子弹击中。
...........................
  我们已经讲述了两种碰撞检测的方法,他们都使用的是光栅技术。让我们想象一下,如果一个游戏有一张巨大无比的地图场景,如果我们还使用上面的策略,那我们就需要一张同样巨大无比的遮罩图形。结果是需要非常多的内存。显然这不是我们希望的,怎么办?让我们在进一步学习利用向量技术来进行碰撞检测。
PtInRect(...)
  PtInRect(...) API。 这是Win32 SDK下面的一个标准的API。它可以计算出一个点(x,y)是否存在于一个矩形区域(x1,y1,x2,y2)中,它的返回值是真或假。
Point P(x,y) will be in Rect(x1,y1,x2,y2) area when
x >= x1 x
= 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;
  我一直使用这种很典型的方法来快速的进行常规的碰撞检查。
  检测距离的方法。前面的方法都是用来检查一个点是否在一个规定的矩形范围中。如果你的游戏中有两个圆形对象。并且你想知道对象A到对象B有多远,你该怎样做才能得到他们之间的距离呢?如果这个距离小于两对象的半径之和,说明这两个对象是重叠的。下面的图形象我们说明了判断两个对象之间的距离是很容易的,我们的算法是: c2 = a2 + b2.
  A = p1.x - p2-x;
  B = p1.y - p2.y
  C = sqrt(A*A+B*B);
  假定R1是蓝圆的半径,R2是红圆的半径,如果C小于R1和R2的和,说明两个圆是重叠在一起的。
  让我们想一下星际航行的游戏,用一个圆表示行星,用一个圆表示太空船,然后碰撞检查,在两个对象之间进行简单地计算距离。
  高级方法 - 精灵的重叠判断。
  现在的2D游戏的特色是有许多形状差别很大的精灵。为了节约计算时间,我们需要一个超越第一种方法的解决方案,应用到每对象类型。在我的射击游戏中,我用一个特殊方法来检查子弹和其他的对象是否发生碰撞。因为,子弹是很小的并且快速移动的。我同样也用特殊的方法来检查两个精灵对象之间是否发生碰撞。因为,他们又太大了。
  基本思路:首先我用了一个对象把精灵对象的边界进行封装。然后使用快速的边界重叠的方法来进行碰撞检查。如果对象边界是不重叠的,则说明对象没有被击中。如果边界发生重叠,说明对象被击中了。见下图。我们需要使用一种高明的检查方法,计算所有重叠象素。
  1、如果对象边界正在重叠
  2、是,计算重叠的区域, 转到步骤4
  3、没有,没重叠,退出判断
  4、在重叠的长方形中计算真实的重叠形状
  5、计算重叠的象素
  6、返回重叠的象素的数字
在Pocket PC上编写游戏之九
如何发出声音?我们首先需要了解Pocket PC机器的硬件体系结构。我们有双声道设备(立体声模式)用来播放W***E音频。不过有的设备只有一个声道(单音的模式)。如果我们有双声道,那么声音电路能生产的音频范围可以从8KHz到44HKz。如果每条声道能分别地工作,我们便能产生简单的动态的声音。不幸地是技术上这是不可能的。
我们使用API函数Me ageBeep(...)可以制造出声音。它听上去与Windows的典型的系统报警声音一样。它可以被应用到一些小的简单游戏中,这是在游戏中产生声音或音响效果的最容易和最安全的方法。同时还可以减小你的游戏的文件大小。
  在Pocket PC上使游戏发出声音的另外的一个容易的方法是使用API函数PlaySound(...)。它可以用来录制和播放音频。因此你可以使用它给你的游戏增加声音,但你需要有外部的音频文件资源。音频文件的扩展名是“W***”。 它还可以被用来实现录制数字的音频好象录音机软件。你可以从许多地方找到合适的声音资源。在Windows目录,在Pocket PC系统中都有预装的一些声音。我们也能使用他们,象闹钟声、错误提示声等等。
  PlaySound(...) 应用程序接口可以播放来自外部的Wave文件和内在的wave资源,系统会将他们放到内存中(运行时刻产生)。 它有一些播放模式的的选择,例如,循环播放或顺序播放。
   dPlaySnd(...) 是一个PlaySound(...)的子集。它少了一些播放选择但是因此有获得了速度。它也可以通过外部的wave文件向游戏提供声音,并且播放的声音文件在Win.ini文件中进行了注册。
  使用标准的API播放声音有一个主要的问题;当一个新声音播放时,前一个会停止。这在一般的游戏程序中可能算不上问题。可一个好的游戏,需要动态的产生声音,什么时候需要,什么时候便产生,声音出现完全取决于游戏的进行状态。我们怎样达到这种效果呢?
  使用pg d.dll
  我们可以使用Windows多媒体扩展系统(MMSYSTEM)来解决低级的播放音频API带来的问题。方法很简单,我们利用混频器引擎技术实现实时的数字混频。因为代码太复杂了。关于这个我不准备多将。但我们提供一个免费混频的引擎pg d.dll,你可以从网站下载栏目下载它。我们同时提供使用它的免费许可证。
  在使用这个库之前,你需要调用pg_WaveInit(…) 应用程序接口函数。它将接管设备的声音系统。因此你将不能使用任何标准的声音API例如 dPlaySound(…)。
  当你正在运行着一个应用时,你希望切换任务(使用另外一个应用程序),你的程序将失去焦点,但是并没有停止,仍然在运行。此时系统已经被pg_WaveInit(…)钩住,你将没有办法实现在新的应用程序使用wav。怎么办呢?我们可以这样做当你的应用失去焦点或停止活动时,Windows将自动地发送WM_ACTIVATE消息到应用程序窗口。wParam将显示你的应用程序是处在停止还是激活的状态。然后可以用pg_WaveActivate(…) 解决这个问题。
  Ex:
WM_ACTIVATE:
pg_WaveActivate((BOOL)wParam);
  pg d.dll的作用是对插槽/声道进行分配。当游戏需要的声音时,就指定插槽数。然后将声音发出到硬件输出设备。装载声音可以到任何插槽,pg_WaveLoad(…)函数被用来处理这个任务。如果你装载新声音到任何已经存在的插槽,先前的声音将自动地被删除并且被新的代替。
  Ex:
pg_WaveLoad(g_hI t, 1000, 0); // load wave to slot 0
pg_WavePlay(0,FALSE); // start to play wave from slot 0
  有两个函数可以用来管理声音插槽, 他们是:
  Ex:
pg_WaveSetCha elValume(…)
pg_WaveStop(…)
  音量控制: 有三种音量控制类型; 主音量,所有的音乐音量,单独声道的音量。
  最后在退出你前应用前,你需要手工解下W***E设备。如果你不这样做,声音将不能生产直到你已经重新启动你的机器。有些时候你的程序需要在退出之前就调用pg_WaveClose (…)函数来解下设备,这也许会产生意想不到的事情或者在你的程序中产生一个错误。你同样需要重新启动机器来再次产生声音。
  这个反初始化操作最好的位置是在退出应用程序之前。
  Ex:
while(GetMe age(....))
Tra lateMe age(&msg);
Di atchMe age(&msg);
pg_WaveClose();
在Pocket PC上编写游戏之十
现在有一些标准类型的数字音乐。例如,W***,MP3,MIDI,MOD等等。每种音乐在技术上只有些细微的差别。MP3和W***是其实是相似的,除了使用不同的记录和重放方法外就只在压缩技术略有区别。MIDI和MOD更是除了音源不一样以外完全一样。
众所周知有两种音乐技术可以制作数字音乐。着两种方法是采样和MIDI。
  MIDI(Musical I trument Ditaigal Interface)是一个缩略词表示乐器数字接口。在乐器和计算机之间它充当一个接口。在MIDI文件内, 包含了音乐序列的代码(音符,效果,节拍等等)。音序器将发送代码到声音模块生产音乐和声音。所以播放MIDI我们需要两个主要的部件,音序器和声音模块。音序器能用软件实现,声音模块通常是硬件。不是很清楚有没有Windows CE将此设备嵌入。一般来说如果你想在你的Windws CE设备上播放MIDI,你需要添加专用的硬件设备。
  采样技术是非常有趣的。如果我们记录声音(例如狗叫) 在8KHz,当我们在其他的频率下播放它,例如在6KHz。我们将听见比原始声音低一些的声音。 相反的如果我们在10KHz频率下播放它,我们将得到高一些音调的声音。因此如果从钢琴录制了一段C音符,我们几乎将播放出任何想要的音符。
  MOD(声道)音乐出自Amiga系统。它也使用采样技术。它带给我们的主要是,它是一种能使用纯软件播放音乐文件。在以前只有Amiga有四声道硬件音频设备。所以MOD是唯一的有四个轨道的音乐,它能同时播放四个音符。现在我们应用软件混频技术,依靠处理器的高速度我们可以将许多声道混合成一个声音。 我们可以拥有超过四个音轨,最多可以达到128轨。一个典型的MOD声音文件的组成是音效(乐器)和音乐符号(歌曲)。
  数字音乐支持将采样和MIDI这两种基本的重要的音乐标准技术联合使用。但是非常遗憾的是他们忘记了Windows CE系统。对Windows CE系统来说这些声音文件太大并且太复杂了。
  现在, 让我们回到20年以前。你还能想起下面的代码吗;
Play "L1O1C1E2DGA";
  没错,它们是MBASIC音乐代码式样。用它为一个简单的游戏产声音乐是很简单的方法。我们不需要外部的资源。我们将试着做一个小程序来模拟MBASIC音乐。下面是对这个技术的简单描述;
  1、编写一个规则,用来产生音调和频率。
  2、写一个函数用来转换音乐编码流。
  3、使用定时器的控制节拍。
  现在,让我们看下面的代码。
<souce lising one - MBASIC music decoder>
static BYTE *pMusicStream;
static int nMusicPo static nNotePeroid;
static nNoteOcte static int chtoi(char ch)
 return ch - '0';
void PlayMusic(void)
 switch(pMusicStream[nMusicPos])
  case 'L': // tempo
   nTempo = chtoi(pMusicStream[nMusicPos+1]);
   nMusicPos++;
   break;
  case 'O': // octep
   nOctep = chtoi(pMusicStream[nMusicPos+1]);
   nMusicPos++;
   break;
  case 'C': // note
  case 'D':
  case 'E':
  case 'F':
  case 'G':
  case 'A':
  case 'B':
   note_no = pMusicStream[nMusicPos] - 'C';
   if(isdigit(pMusicStream[nMusicPos+1]))
   {
    tempo1 = chtoi(pMusicStream[nMusicPos+1]);
    Tone(note_no,nOctep,nTempo*tempo1);
    nMusicPos++;
   }
   else
    Tone(note_no,nOctep,nTempo);
    break;
 nMusicPos++;
  我们甚至可以把它应用到我们的游戏中。看到了吧音乐可以这样容易的被实现。
  在前的章节,我已经提及关于我们的动态产生声音的函数库。这个库使用一些低级的音频API使我们能够获得有效的音乐和动态的音响效果。 更多的详细介绍放在我们的网页上.
  你可以播放一首歌曲作为背景音乐。你可以使用pg_LoadMod(…)函数装载音乐并暂时保存音乐。你可以使用pg_PlayMod(…)函数播放音乐。当你装载了新的音乐先前的一个将自动地卸载由新的代替。使用pg_ModStop(…)函数你能停止播放。
  用什么办法创建MOD格式的音乐呢?***是你需要一个声道音乐作曲软件。这类软件通常都是功能很强大的,但也是不容易使用的。:/
  解决这个问题,我们能用采样技术从MBASIC音乐流生产音乐。混合的代码是太复杂的,这样,我们将把这些特征加到我们的声音引擎。现在,下面是它的实现..... 在音乐控制方面的更深层的函数是...
  pg_LoadI trument(...) - 装载声音到内存
  pg_LoadMusicData(...) - 保存音乐流到游戏缓冲区
  pg_PlayMusic(...) - 开始播放背景音乐
  pg_StopMusic(...) - 停止播放音乐 |买手机,上口袋! |
| Windows Phone
Android
Pocket PC游戏开发初级教程
Pocket PC游戏开发初级教程
当前离线
66431 帖子
5955 精华
17 积分
373 技术
45 资源
0 口袋币
9530 贡献
0 阅读权限
100 性别
男 在线时间
2521 小时 注册时间
2004-11-19 最后登录
2011-7-14 版主
66431 帖子
5955 精华
17 积分
373 技术
45 资源
0 口袋币
9530 注册时间
2004-11-19 字体大小:
发表于 2006-08-13 23:22:09
Pocket PC游戏开发初级教程
给ATBT投一票 谢谢
开发工具
 在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的所有知识,因为我在试图指导你达到另外一点,那就是让你学会开发你自己的游戏程序。
ATBTNB@163.COM
当前离线
66431 帖子
5955 精华
17 积分
373 技术
45 资源
0 口袋币
9530 贡献
0 阅读权限
100 性别
男 在线时间
2521 小时 注册时间
2004-11-19 最后登录
2011-7-14 版主
66431 帖子
5955 精华
17 积分
373 技术
45 资源
0 口袋币
9530 注册时间
2004-11-19 发表于 2006-08-13 23:24:03
其实做一个程序员要学很多专业知识,有些知识既枯燥又繁杂,让人头疼,所以我们就用大家喜爱的小游戏来串起我们的课程。愿你学的愉快。
  这一篇我们将用大家熟知的一种游戏——麻将,来给大家示范一下数据结构这门课的用处。由于笔者的水平有限,文章不可能太深入,旨在抛砖引玉。同时有不正确的地方也恳请各位大虾给于指正。
  书归正传,我们先来分析一下麻将游戏的整个流程。
  首先,当玩家开局时要在内存中分出136个整型数组空间,来存放136张麻将牌。然后,用136个整型数来代表136张牌,并将个张牌随机分配到前面分出的136个整型数组空间。这样,整个麻将牌就洗好了。
  接着,我们可以用1到6的随机数代表榖子,并随着数的变化而显示不同点数的图片。以时间或次数来决定停止的时刻,最后得到的数字就是玩家所掷出的点数,之后就可以用这个数来确定发牌的位置。
  然后,就是发牌了。在这里我们可以在建立两个长度为13的整型数组空间,以分别存储两个玩家手中的牌。
  接下来就是最有意思的地方了,也就是玩牌的过程了。先比较对家打出的牌是否可以吃、碰、杠、胡,都没有,就从剩下的牌中一张张的分到每个该摸牌的玩家手中,并计算是否为胡牌,如果是,就提示玩家并让玩家决定是否胡牌;如果不是,就等玩家出牌后,根据是否是同一种牌并按大小顺序排列好。重复这一过程,直到有人胡牌,游戏结束。
  游戏的流程我们分析完了,怎么实现呢?这也是我们这篇教程的重点。一定好打起精神呀!
  我们先来看看游戏的第一个流程是怎样实现的。在文章的开始,我谈到了数组(就是具有同一数据类型的,并且位置连续的数据存储空间),用c语言我们可以这样实现。
int pai[136];// 注意c语言中数组是从0~135
  那么怎么洗牌呢?通常的思路是用rand()来产生1~136之间的整型数,并按顺序放入我们刚才分配的用来存放麻将牌的数组空间中。为了不出现重复的数值,可以用产生的新数和前面已放入数组的数比较,如果重复就在从新分配,不重复就放入数组中。
  下面是实现这一过程的代码。
Void xipai()
 int tem  temp=(int ) 135*rand()/32565+1;
 for (int i=o;i136;i++)//按顺序比较136个数组空间
  if(i=o)
   pai
=tem //第一个数直接放入数组
  else
   for(int j=o ;ji;j++) //和前面放入数组的数比较
   {
    if( pai[j]==temp) //有重复的
     xipai(); //从新取数比较
    else
     pai
=tem //没重复的,放入数组
   }//end for
 }//end for
}//end xipai   在上面的代码中,我们使用了递归的方法,可以说它的效率是比较低的。一方面适应为递归本身对空间的要求,另一方面,就在于这个算法中,要进行多次的比较。举个较极端的例子,如果我们每次取得一个随机整型数都是在前面已经存放过的,那么就要一直从新取数并比较。当然这种可能性极少,但当数组存放有一半以上的数后,这种事情发生的可能性就会成指数的增长。
  这里我再介绍一种新的方法。前一种算法的弊处在于可能会有重复的数,而进行多次的取数和比较。我们换个思路,就是随机产生0到135的数组位置,按顺序把1~136的数按得到的数存放到相应的数组位置。有人可能会说,难道位置就不会有重复吗?别急,我们继续往下看。
  我们知道,c语言中对于一个int(整型)数,默认的初始化值是0,所以,我们只要在按顺序比较0到135个数组空间,那些值为0,就可以将数放入其中。这样就保证了不会出现重复的数,而且效率也比前一种方法要高。(大家可以思考一下为什么?我也会在后面给以解答)
  下面是这种算法的源代码:
void xipai()
 for (int i=1 ; i=136; i++) //这是要放入的136个整型数
  int a= (int)135 * rand()/32767+1; //随机产生位置
  if(pai[a]==0) //判断此位置是否为空
   pai[a]=i; //空,放入其中
  else //不空
   for(int j=0;j=135;j++) //比较所有数组空间
   {
    if(pai[j]==0) //找到空闲的位置
     pai[j]=i; //放入其中
    }//end for
  }// end if
 } //end for
}//end xipai
ATBTNB@163.COM
当前离线
66431 帖子
5955 精华
17 积分
373 技术
45 资源
0 口袋币
9530 贡献
0 阅读权限
100 性别
男 在线时间
2521 小时 注册时间
2004-11-19 最后登录
2011-7-14 版主
66431 帖子
5955 精华
17 积分
373 技术
45 资源
0 口袋币
9530 注册时间
2004-11-19 发表于 2006-08-13 23:25:30
移动开发 屏幕缓冲区
在我们学习离屏(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 me age, WPARAM uParam, LPARAM lParam)
PAINTSTRUCT HDC hDC;
RECT rcClient;
switch(me age)
case WM_PAINT:
hDC = BeginPaint(hWnd,&am );
GetClientRect(hWnd,&am rcClent);
DrawText(hDC,LHello,5,
&am rcClient,DT_CENTER|DT_VCENTER);
EndPaint(hWnd,&am );
case WM_LBUTTONDOWN:
DestroyWindow(hWnd);
default:
DefWindowProc(hWnd, me age, uParam, lParam);
return 0;
  这些代码中GetClientRect用来获得整个客户区域的矩形范围,DrawText用来在屏幕上这个举行范围的中心画出文字hello。
  当然我们也可以在别的地方进行绘制,你可以再试试下面这些代码。
LRESULT MainWndProc(HWND hWnd, UINT me age, WPARAM uParam, LPARAM lParam)
HDC hDC;
int nXpo int nYpo switch(me age)
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);
case WM_KEYDOWN:
DestroyWindow(hWnd);
default:
DefWindowProc(hWnd, me age, 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);
Elli e(hDC,nX-20,nY-20,nX+20,nY+20);
SelectObject(hDC,hOldBrush);
SelectObject(hDC,hOldPen);
LRESULT MainWndProc(HWND hWnd, UINT me age, WPARAM uParam, LPARAM lParam)
HDC hDC;
switch(me age)
case WM_LBUTTONDOWN:
hDC = GetDC(hWnd);
_drw_object(hDC,LOWORD(lParam),HIWORD(lParam));
ReleaseDC(hWnd,hDC);
case WM_MOUSEMOVE;
hDC = GetDC(hWnd);
PatBlt(hDC,0,0,240,320,WHITENESS);
_drw_object(hDC,LOWORD(lParam),HIWORD(lParam));
ReleaseDC(hWnd,hDC);
case WM_LBUTTONUP:
hDC = GetDC(hWnd);
PatBlt(hDC,0,0,240,320,WHITENESS);
ReleaseDC(hWnd,hDC);
case WM_CREATE:
hbrRed = CreateSolidBrush(RGB(255,0,0));
hpeBlue = CreatePen(0,0,RGB(0,0,255);
case WM_DESTROY:
DeleteObject(hbrRed);
DeleteObject(hpeBlue);
PostQuitMe age(0);
case WM_KEYDOWN:
DestroyWindow(hWnd);
default:
DefWindowProc(hWnd, me age, uParam, lParam);
return 0;
  现在,试试用笔点击屏幕并在拖动看看。
  计算机将首先将屏幕填充为白色,将原本的圆形物体擦除,然后在新的位置画上。因为填充屏幕要画掉很多的时间,因此我们将看到旧有图形被长时间的擦除,然后新图形在别的位置出现。这个过程就在我们的眼睛中形成了闪烁。如果有多个需要绘制的物体,这种闪烁将更加明显。
ATBTNB@163.COM
当前离线
66431 帖子
5955 精华
17 积分
373 技术
45 资源
0 口袋币
9530 贡献
0 阅读权限
100 性别
男 在线时间
2521 小时 注册时间
2004-11-19 最后登录
2011-7-14 版主
66431 帖子
5955 精华
17 积分
373 技术
45 资源
0 口袋币
9530 注册时间
2004-11-19 发表于 2006-08-13 23:26:45
游戏中的大多数物体不是矩形,但是一个位图却是矩形,因此我们必须找到一个方法来绘制物体真正的形状而不是矩形。
  图片
  如果我们在游戏编程中谈及图片,通常指的是光栅图象或位图。我会假定你已经了解有关的知识,但下面仍然给出少许解释。
  位图或者光栅图象是一种以数字格式储存照片或图片的方法。它将照片每行每列的象素转化为二进制数据的矩阵。矩阵每个单元格的数据代表图片显示的一个象素。因此,一个位图必然是矩形的。如:
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; nRowNHEIGHT am NBSP;NROW++)
for(nCol=0; nColNWIDTH am NBSP;NCOL++)
dwPixel = GetPixel(hMemDC,nCol,nRow);
if(dwPixel != dwTra arentColor)
SetPixel(hDC,nPosX+nCol,nPosY+nRow,dwPixel);
  Alpha通道或掩码图方式使用另外的方法来区分图片中物体和非物体的部分。它使用一个另外的黑白位图,通常被称为掩码位图。掩码位图中白色的象素表示物体的部分。下图则是原图和它的掩码位图。
  此主题相关图片如下:
  这种方法的优点是,它同样适用与Alpha混合方式。这种方式用不同的灰度值代表图片象素和目的象素的混合程度,而白色恰好代表图片象素完全取代屏幕象素,黑色代表完全忽略图片象素。我不打算在这里介绍这种方式,如果你更倾向于GKS(图形内核系统)的概念,你可用这样的两个位图按用下面的步骤完成精灵的绘制:
  1 一个带掩码位图的图片
  2 反转掩码位图
  3 将掩码位图中的象素与目的表面的象素按位与
  4 将原始图片中的象素与目的表面的象素按位或
  用Win32的代码描述如下:
void PutSprite(HDC hDC, int nPosX, int nPosY, int nCX, int nCY, HBITMAP hObj,HBITMAP hMask)
HDC hMemDC, hMaskDC;
hMemDC = CreateCompatibleDC(hDC);
hMaskDC = CreateCompatibleDC(hDC);
SelectObject(hMemDC,hObj);
SelectObject(hMaskDC,hMask);
BitBlt(hMemDC,0,0,nCX,nCY,
hMask,0,0,SRCAND);
PatBlt(hMaskDC,0,0,nCX,nCY,DSTINVERT);
BitBlt(hDC,nPosX,nPosY,nCX,nCY,
hMaskDC,0,0,SRCAND);
BitBlt(hDC,nPosX,nPosY,nCX,nCY,
hMemDC,0,0,SRCPAINT);
DeleteDC(hMemDC);
DeleteDC(hMaskDC);
}   Windows中的位图资源
  我们可以在我们的可执行文件中储存位图,方法就是用内部资源。使用这个方法,可以很容易的使用位图,当然你要先创建资源描述的源文件。否则,你将需要从外部文件装载位图。
  想要更多的了解这个资源描述的文件格式,可以查看Win32的程序员手册。下面是一个简单的例子,如果你需要在可执行文件中储存一个位图,你可以新建一个文件,写入如下代码:
#include windows.h
1 BITMAP yourfile.bmp
  第一行是包含windows头文件,这里面包含所有必须的预定义符号。第二行分成三个

参考资料

 

随机推荐