最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

Windows Mobile手机游戏(基础教程)

IT圈 admin 85浏览 0评论

2024年9月12日发(作者:敏新筠)

用GAPI开发Windows Mobile手机游戏(基础教程)

操纵1

判断是否是标准显示设备 .............................................................................................................................3

开始绘制像素...............................................................................................................................................4

GAPI高效贴图............................................................................................................................................4

Gapi键盘消息.............................................................................................................................................8

游戏的振动感...............................................................................................................................................9

获得振动设备属性........................................................................................................................................9

开始第一手机游戏历程 ..................................................................................................................................... 11

第三方开发库介绍............................................................................................................................................. 13

打开显示设备............................................................................................................................................. 13

取回主界面和后背缓冲 .............................................................................................................................. 15

失败的界面 ................................................................................................................................................ 17

结论.................................................................................................................................................................. 18

作者:傅曦

目前mobile phone 游戏API简称GAPI为手机上的游戏开发者提供了强有力的高效率的编程接口,当然

GAPI不仅仅使用在游戏方面,需要高效率图形显示处理的地方都可以使用GAPI。

GAPI是基于动态连接库方式,应用程序直接调用动态库里的函数,一般GAPI库的文件名为,目前

mobile phone里都提供了文件。

一个典型的游戏或者应用程序使用下列GAPI函数:

OpenDisplay (fullscreenflag)

打开GAPI显示功能。

OpenInput

打开直接响应硬件键盘输入消息功能

GetDisplayProperties

获得VFB详细结构信息

GetDefaultKeys

获得缺省的键值

操纵GAPI

开始一个游戏编写,首先要打开GAPI显示功能,获得控制视频显示缓存的控制权限。可以调用

GXOpenDisplay(HWND hwnd, DWORD dwFlags)

hwnd参数是游戏程序的窗口句柄,dwFlags定义了显示模式,宏GX_FULLSCREEN定义全屏模式,就能

对设备的全屏区域进行控制。返回值1说明打开成功,0是失败。

虽然都是使用mobile phone系统但是不同系列的产品可能使用不同的显示设备,那么对于不同的显示设备

就可能有不同的显示性能参数,不同的分辨率率,不同的色深,不同的颜色显示能力。当在编写一个为mobile

phone 系列运行的游戏程序时不得不考虑这些问题,以使程序能适应在不同的显示环境下达到程序所希望的显

示效果。

如何得到这些相关显示信息?可以调用下面函数:

GXDLL_API GXDisplayProperties GXGetDisplayProperties ();

它能得到显示设备的所有相关细节信息,这些都是在开发基于GAPI游戏时需要的。所有信息被返回到

GXDisplayProperties结构中,其结构如下:

struct GXDisplayProperties {

DWORD cxWidth;

DWORD cyHeight;

long cbxPitch;

long cbyPitch;

long cBPP;

DWORD ffFormat;

};

这个结构提供了显示设备的信息,也就是当前视频缓存区域的参数指标。

cxWidth和cyHeight是显示设备的宽和高的像素值,提供了显示设备横、纵能显示的像素个数;cBPP是

每个像素点需要的位数,总是等于2的n次方。cbxPitch和cbyPitch提供了相邻两个像素间从内存数据上相差

的字节数,cbxPitch表示的是左右两个像素间的差值,当cBPP大于等于8时,cbxPitch表示相差的字节个数,

当cBPP小于8时,cbxPitch已经不能真实的反映出相差的字节数,事实上必须自己计算得到相邻的地址:

比如:

cBPP = 4;

Leftpointaddr = pb + (((current_x+1) * cBPP) >> 3)

+ (current_y * cbyPitch)

cbyPitch表示的上下方向间两个像素的差值,计算时通过加减cbyPitch来的到上下方向的像素点的地址:

downpointaddr = currentpointaddr + cbyPitch;

ffFormat参数说明显示设备对色深的处理方式及显示的格式:

当ffFormat 等于 KfLandscape 说明当前显示方式是横屏方式,即原点(0,0)变成了左下角。

ffFormat 等于 KfPalette 说明色彩显示是基于调色板方式。

ffFormat 等于 KfDirect 说明色彩显示是直接映射,不引用调色板。

ffFormat 等于 KfDirectInverted 说明颜色显示是反转的。

ffFormat 等于 kfDirect555 、kfDirect565、kfDirect888 说明映射颜色显示时,数字表示红绿蓝所占的位

信息。

计算每一个像素坐标地址方法如下(x,y):

unsigned char * pb;

if (cBPP < 8) {

address = pb + ((x * cBPP) >> 3) + (y * cbyPitch)

}

else

{

address = pb + (x * cbxPitch) + (y * cbyPitch);

}

判断是否是标准显示设备

可以使用函数GXIsDisplayDRAMBuffer (),返回值为TRUE说明是非标准显示设备,返回值为FLASE说明

是标准显示设备。当是非标准显示设备时,需要使用函数GXSetViewport来定义显示屏幕的区域,在标准显示

设备上使用GXSetViewport是无效的。

GXDLL_API int GXSetViewport (

DWORD dwTop,

DWORD dwHeight,

DWORD dwReserved1,

DWORD dwReserved2)

dwTop定义了屏幕显示区域的y坐标,dwHeight显示区域的高度,dwReserved1、dwReserved被保留,

必须需设置为0。

开始绘制像素

现在就可以准备对缓存区进行操作绘制图形,通过GXBeginDraw得到缓存区的首地址:

void * GXBeginDraw ();

函数返回值就是需要的首地址,如果是NULL说明显示缓存区得不到。然后就可以进行一些列像素的操作,

操作完毕后需要调用GXEndDraw 结束一次操作:

int GXEndDraw ();

返回1说明调用成功,0说明错误。

提交绘制的信息,已使变化的画面生效。当程序失去焦点时必须调用GXSuspend ()挂起所有GAPI的操作,

把屏幕控制交给其他程序,当接收到获得焦点信息时,程序必须调用GXResume ()使得程序继续运行GAPI函

数。

当退出程序时,必须释放GAPI资源,可以调用:

int GXCloseDisplay ();

GAPI高效贴图

在开发一些图像处理或游戏时我们可以使用GDI制作出满意的产品,但是开发复杂高速的图形显示或高效

率的动态游戏时,往往对GDI的显示效率不高而感到沮丧,虽然可以使用双缓存等技术,但是GDI层接口毕竟

效率低,无法满足要求。

GAPI对显示缓存区的直接操作,使显示效率大大提高,所以在目前mobile phone上当需要处理高速贴图

时GAPI就当之无愧了。

虽然GAPI高效强大,提供了对显示缓存区直接的读写权限,但是基于如此低级的功能函数,在编写一个

稍微复杂的程序时,就会花费大量的时间和精力在处理对显示缓存区的操作,因为此缓存区并不像GDI提供的

绘制缓存区对图片显示一样操作容易,为了显示一幅bmp图就需要编写好几页的代码,这是非常令人厌倦的事。

在这里主要介绍一下一个使用GAPI编写的第三方贴图类STGapiBuffer。STGapiBuffer提供了类似于GDI

方式的接口操作简单,很容易就构造出了需要显示的缓存内容,最后只要简单的把它们拷贝进显示缓存区,就

可以显示出来了。

只要简单的把STGapiBuffer.h 和 加入到工程里面就可以方面的使用了。

为了需要绘制一个jpg图片,首先需要把图片加载入此类里,并创建适合STGapiBuffer处理的数据,使用

CreateNativeBitmap函数,需要如下操作:

HBITMAP hBitmap = SHLoadDIBitmap(_T(""));

g_pNativeBitmap = g_NativeBitmap(hBitmap);

::DeleteObject(hBitmap);

接下去需要为绘制到哪里设置目标对象,可以是另一个STGapiBuffer的缓存区,也可以是显示的显示设备

缓存区,调用函数SetBuffer,代码如下:

g_fer(pDisplayBuffer);

最好可以使用CSTGapiBuffer::BitBlt来把需要的数据绘制到缓存区里,类似于GDI函数BitBlt,代码如下:

g_(0, 0, 100, 100, g_pNativeBitmap);

这样就把一幅图片显示到了屏幕上。

CSTGapiBuffer还提供了绘制透明图片的函数,在绘图时经常会遇到这样的情况,使用

CSTGapiBuffer::MaskedBlt方便的绘制指定透明色的图案。当然我在用GDI绘图时经常使用CreateMemoryDC

创建一个临时内存DC来绘制,CSTGapiBuffer也提供了类似的功能的函数CSTGapiBuffer::

CreateMemoryBuffer

CSTGapiBuffer类使用示例(部分代码):

CNativeBitmap* pAsteroidBitmap = NULL;

CNativeBitmap* pAsteroidMask = NULL;

CSTGapiBuffer gapiBufferBackground; // 背景

CSTGapiBuffer gapiBufferMemory;

CSTGapiBuffer gapiBufferScreen;

HBITMAP hBackground = ::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BACKGROUND));

CNativeBitmap * pBackgroundBitmap =

NativeBitmap(hBackground);

::DeleteObject(hBackground);

MemoryBuffer();

(0, 20, dwDispWidth, dwDispHeight,

pBackgroundBitmap);

delete pBackgroundBitmap;

pBackgroundBitmap = NULL;

////////////////////////////////////

HBITMAP hAsteroid = ::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_ASTEROID));

pAsteroidBitmap = NativeBitmap(hAsteroid);

::DeleteObject(hAsteroid);

HBITMAP hAsteroidMask = ::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_ASTEROID_MASK));

pAsteroidMask = NativeBitmap(hAsteroidMask);

::DeleteObject(hAsteroidMask);

/////////////////////////////////////////

//

dwTransparentColor = iveColor(RGB(249, 57, 198));

// create an offscreen buffer

MemoryBuffer();

(&gapiBufferBackground);

///////////////////////////////////////////////////

//

arentBltEx(0, nOffset%dwDispHeight,

50, 60, pAsteroidBitmap, dwTransparentColor);

arentBltEx(100, (nOffset*2)%dwDispHeight,

50, 60, pAsteroidBitmap, dwTransparentColor);

arentBltEx(nOffset%dwDispWidth,

50, 50, 60, pAsteroidBitmap, dwTransparentColor);

arentBltEx((nOffset*2)%dwDispWidth,

150, 50, 60, pAsteroidBitmap, dwTransparentColor);

Blt(dwDispWidth-(nOffset*2)%dwDispWidth,

(nOffset*8)%dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);

Blt((nOffset%dwDispWidth*5), (nOffset*3)%

dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);

Blt((nOffset*3)%dwDispWidth, (nOffset*4)%

dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);

Blt((nOffset%dwDispWidth*2), dwDispHeight-

(nOffset*5)%dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);

RECT rc = { 0, 20, dwDispWidth/3, 10 };

FillRect(&rc, RGB(255, 0, 0));

= dwDispWidth/3;

= 2*dwDispWidth/3;

FillRect(&rc, RGB(0, 255, 0));

= 2*dwDispWidth/3;

= dwDispWidth;

FillRect(&rc, RGB(0, 0, 255));

void* pBuffer = GXBeginDraw();

fer(pBuffer);

(&gapiBufferMemory);

GXEndDraw();

Gapi键盘消息

使用GXOpenInput()函数获得键盘的控制权,调用GXGetDefaultKeys(GX_NORMALKEYS)函数来获得默认

键盘的消息映射。然后在Windows消息处理函数中我们就能收到由GAPI发送过来的键盘消息,当你按下某一

个键,程序会收到WM_KEYDOWN,wParam参数包含GAPI映射的这个键的消息,通过与得到的GXKeyList

结构中的字段定义来判断当前收到的键是不是定义的功能键。

GXKeyList结构如下:

struct GXKeyList {

short vkUp;

POINT ptUp;

Short vkDown;

POINT ptDown;

Short vkLeft;

POINT ptLeft;

Short vkRight;

POINT ptRight;

Short vkA;

POINT ptA;

Short vkB;

POINT ptB;

Short vkC;

POINT ptC;

Short vkStart;

POINT ptStart;

};

游戏的振动感

在游戏中提供振动效果,是让玩家非常兴奋的事情,可以提升游戏的吸引程度。Mobile Phone SDK 提供

了振动效果的API,允许你控制振动,类似与控制声音一样简单。

获得振动设备属性

要获知手机是否支持振动,振动的性能,当前的设置情况,可以调用下面的函数:

int VibrateGetDeviceCaps(VIBRATEDEVICECAPS vdc);

VIBRATEDEVICECAPS是个枚举类型,结构如下

typedef enum {

VDC_AMPLITUDE,

VDC_FREQUENCY,

VDC_LAST

} VIBRATEDEVICECAPS

VDC_AMPLITUDE 查询振动设备所能支持的振幅大小

VDC_FREQUENCY 查询振动设备所能支持的振动频率大小

VDC_LAST 查询振动设备所能支持的振幅大小

如果函数成功,它将返回数字0到7,数字0说明设备没有提供振动功能,1说明设备具有振动功能,并

且可以使用,但是仅仅具有打开关闭震动,无法对振动进行调节,2到7说明了设备提供了不同等级的振动功

能,数字越大提供的调节能力越强。当设备具有不同等级振动能力时,我就可以通过VIBRATENOTE结构做详

细设置。

怎么才能开始真正的使用振动功能呢?mobile phone SDK提供Vibrate函数:

HRESULT Vibrate(

DWORD cvn,

const VIBRATENOTE * rgvn,

BOOL fRepeat,

DWORD dwTimeout

);

它能提供不同振幅,不同频率,并可以调节需要振动时间。cvn参数是第二个参数rgvn数组的维数,rgvn

是指向一组VIBRATENOTE结构的指针。

VIBRATENOTE结构如下:

typedef struct {

WORD wDuration;

BYTE bAmplitude;

BYTE bFrequency;

} VIBRATENOTE

wDuration说明了震动持续的时间,bAmplitude定义了振动的振幅大小,允许设置0-7级,如果等于0xff

系统使用缺省值作为参数,bFrequency定义了振动的频率高低,允许设置0-7级,如果0xff系统使用缺省值作

为参数。

当你需要停止当然的振动时,可以调用VibrateStop()函数,返回S_OK说明成功调用,E_FAIL说明调用失

败。

下面是代码示例:

int caps = -1;

caps = VibrateGetDeviceCaps(VDC_AMPLITUDE);

if(caps<=0)

return FALSE; //振幅返回失败,说明不支持振动功能

HRESULT hr = Vibrate(0, NULL, TRUE, INFINITE); //设定为无时间限制

if(hr == E_FAIL)

{

MessageBox(NULL,L"E_FAIL",L"",MB_OK);

}

else if(hr == E_NOTIMPL)

{

MessageBox(NULL,L"E_NOTIMPL",L"",MB_OK);

}

Sleep(1000); //振动所花时间

VibrateStop();

开始第一手机游戏历程

在这里使用GAPI模拟一个贪食者游戏,它非常简单。主要注重怎么具体使用GAPI,在使用中怎么对视频

缓存区操作演示,并不去美化外表。

初始化GAPI库,在InitInstance函数里我们对GAPI的显示和输入进行了初始化。

if (GXOpenDisplay( hWnd, GX_FULLSCREEN) == 0)

return FALSE;

gx_displayprop = GXGetDisplayProperties();

if (gx_ != 16)

{

// Only dealing with 16 bit color in this code

GXCloseDisplay();

MessageBox(hWnd,L"Sorry, only supporting 16bit color",L"Sorry!", MB_OK);

return FALSE;

}

framebuf = (unsigned short*) malloc(sizeof(short)*gx_h*gx_ht);

if(framebuf==NULL)

return FALSE;

ClearScreen(framebuf,0xff,0xff,0xff);

GXOpenInput();

// Get default buttons for up/down etc.

gx_keylist = GXGetDefaultKeys(GX_NORMALKEYS);

初始化工作完成后,我们就需要对游戏的内容进行必要准备工作。我们首先初始化贪食者对象,我们为它

建立蛇头和它的身体。为了使贪食者不停的游动,必须有一个事件触发。在这个我们使用定时器,以100ms

的间隔发送消息,这样将得到一秒10帧的效果,这足以满足普通游戏的效果。

为了对定时器发送过来的消息进行处理,我们调用Run函数

void Run(HWND hwnd)

{

if(1 == JudgeDeath(framebuf))

{

KillTimer(hwnd,1);

RunVibrate(1000);

MessageBox(hwnd,_T("Snake has died!"),_T("died"),

MB_OK | MB_ICONINFORMATION);

SendMessage(hwnd,WM_PAINT,0,0);

InvalidateRect(hwnd,NULL,TRUE);

}

ChangeDirection();

SortAll();

RedrawSnake();

}

JudgeDeath每次都会判断是否已经死亡(蛇的任何部位有重叠),一旦满足死亡的条件,就取消定时器,

以便中止蛇的游动。ChangeDirection判断方向是否发生了改变。在这里我对蛇部位的重叠利用了两个象素是否

相同的颜色,如果颜色一直说明重叠发生。

void Get16Pixel(unsigned short *buffer,int x, int y,int *r, int *g, int *b)

{

unsigned short *pixeladd;

int address = (x * gx_ch>>1)

+ (y * gx_ch>>1);

pixeladd = (buffer+address);

if (gx_at & kfDirect565)

{

unsigned short PixelCol;

PixelCol = (*pixeladd);

*r = (PixelCol & 0xf800) >> 11;

*g = (PixelCol & 0x07e0) >> 5;

*b = (PixelCol & 0x001f);

}

else//555

{

unsigned short PixelCol;

PixelCol = (*pixeladd);

*r = (PixelCol & 0x7c00) >> 11;

*g = (PixelCol & 0x03e0) >> 5;

*b = (PixelCol & 0x001f);

}

}

第三方开发库介绍

GapiDraw的设计与DirectDraw非常相似,而且将更加容易使用,极大限度的为掌上设备进行了优化。下

面是一些DriectDraw中的一般功能,以及如何在GapiDraw中实现这些功能。

打开显示设备

计算机的显示内存是一块包含了图像数据的内存区域。为了直接向这块区域进行写操作,DirectDraw和

GapiDraw都需要你创建一个指定的称之为主界面的界面。直接绘制到这个主界面来影响屏幕的可见内容。

创建主界面的第一步是打开显示器,设置一个显示模式。下面的步骤是使用DirectDraw创建主界面的最少

步骤。

DirectDraw

LPDIRECTDRAW lpDD;

HRESULT ddrval;

//创建主Direct Draw对象

ddrval = DirectDrawCreate(NULL, &lpDD, NULL);

if(ddrval != DD_OK)

{

return(false);

}

//设置合作级别以允许Direct Draw全屏运行

ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);

if(ddrval != DD_OK)

{

lpDD->Release();

return(false);

}

//设置显示模式为320x240x16

ddrval = lpDD->SetDisplayMode(320, 240, 16);

if(ddrval != DD_OK)

{

lpDD->Release();

return(false);

}

使用GapiDraw是比较容易的。因为界面是对象而不是COM接口,所以根本就不用手工进行释放。下面的

GapiDraw例子只使用了一条命令打开显示设备,设置默认显示模式。

GapiDraw

CGapiDisplay display;

HRESULT gdrval;

//使用标准Pocket PC240x320x16模式打开显示

gdrval = splay(hwnd, GDOPENDISPLAY_FULLSCREEN);

if(gdrval != GD_OK)

{

return(false);

}

取回主界面和后背缓冲

使用Direct Draw,你必须手工请求创建一个指定界面的主Direcr Draw对象,主界面主要用于直接在显示

器上绘制。在Direct Draw中只有一个界面接口用于双内存界面和显示。这可以简单地解释为过去使用的COM

模式中的子类化缺陷。下面的例子创建一个主界面,使用Direct Draw取回它的后背缓冲。

DirectDraw

LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw 主界面

LPDIRECTDRAWSURFACE lpDDSBack; // DirectDraw 后背缓冲

DDSURFACEDESC ddsd;

DDSCAPS ddscaps;

HRESULT ddrval;

memset(&ddsd, 0, sizeof(ddsd));

= sizeof(ddsd);

s = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

= DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;

BufferCount = 1;

//创建主界面

ddrval = lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL);

if(ddrval != DD_OK)

{

lpDD->Release();

return(false);

}

// Get the back buffer获得背后缓冲

= DDSCAPS_BACKBUFFER;

ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);

if(ddrval != DD_OK)

{

lpDDSPrimary->Release();

lpDD->Release();

return(false);

}

相反,使用GapiDraw 事情会更容易。一旦调用了CGapiSurface::OpenDisplay,CGapiDisplay 对象自动

地变成主界面。因为CGapiDisplay 是CGapiSurface的子类,所有的位图传输(blit)和绘制操作都已经是可用

的了。为了从CGapiDisplay得到后背缓冲,使用如下代码:

GapiDraw

CGapiSurface backbuffer; // GapiDraw 后背缓冲

HRESULT gdrval;

//得到后背缓冲

gdrval = kBuffer(&backbuffer);

if(gdrval != GD_OK)

{

return(false);

}

失败的界面

DirectDraw的界面通常储存在图像存储器中,可以在任何时候被覆盖(在用户切换程序或者启动另外一个

使用GDI的程序的情况下)。这是因为对界面的每个操作在任何时候都有可能失败,简单地说是因为界面数据

被覆盖。因此所有使用Direct Draw的操作必须每次检查界面是否失败,然后从失败处手工恢复和重新创建界

面。下面的例子说明了这一点。

DirectDraw

ddrval = lpDDSBack->Blt(&rcRectDest, lpDDSMySurf, &rcRectSrc, DDBLT_WAIT, NULL);

if(ddrval == DDERR_SURFACELOST)

{

//界面被覆盖,现在你必须手工恢复和重新创建所有的界面

}

ddrval = lpDDSPrimary->Flip(NULL, DDFLIP_WAIT);

if(ddrval == DDERR_SURFACELOST)

{

//界面被覆盖,现在你必须手工恢复和重新创建所有的界面

}

Pocket PC不使用图像存储器,所有界面数据被储存在RAM物理内存,只有调用CGapiDisplay::Flip时才

被复制到显示区域。如果Pocket PC设备访问了显示区域的缓冲,那么Pocket PC可能会移动它的后背缓冲的

位置。到目前为止,还没有设备被告之可以做到这点,但是它始终是最好的设计。可以使用下面的代码捕获在

GapiDraw中丢失的后背缓冲。

GapiDraw

//普通操作中的界面不能丢失

gdrval = (&rcRectDest, &mysurf, &rcRectSrc, 0, NULL);

gdrval = ();

if(gdrval == GDERR_BACKBUFFERLOST)

{

//显示缓冲被移动,刚好获得一个更新的后背缓冲

kBuffer(&backbuffer);

}

结论

上面提及的是GapiDraw和Direct Draw之间的主要不同。其它的特性,象blit、颜色值、矩形坐标等都是

有差异的。GapDraw也包含一个巨大的扩展特性,这些特性在Direct Draw不可用,象高级的blit影响、快速

旋转、从文件或内存装载位图图像、绘制工具、冲突掩码、界面交叉、线程定时器、位图字体支持以及更多。

2024年9月12日发(作者:敏新筠)

用GAPI开发Windows Mobile手机游戏(基础教程)

操纵1

判断是否是标准显示设备 .............................................................................................................................3

开始绘制像素...............................................................................................................................................4

GAPI高效贴图............................................................................................................................................4

Gapi键盘消息.............................................................................................................................................8

游戏的振动感...............................................................................................................................................9

获得振动设备属性........................................................................................................................................9

开始第一手机游戏历程 ..................................................................................................................................... 11

第三方开发库介绍............................................................................................................................................. 13

打开显示设备............................................................................................................................................. 13

取回主界面和后背缓冲 .............................................................................................................................. 15

失败的界面 ................................................................................................................................................ 17

结论.................................................................................................................................................................. 18

作者:傅曦

目前mobile phone 游戏API简称GAPI为手机上的游戏开发者提供了强有力的高效率的编程接口,当然

GAPI不仅仅使用在游戏方面,需要高效率图形显示处理的地方都可以使用GAPI。

GAPI是基于动态连接库方式,应用程序直接调用动态库里的函数,一般GAPI库的文件名为,目前

mobile phone里都提供了文件。

一个典型的游戏或者应用程序使用下列GAPI函数:

OpenDisplay (fullscreenflag)

打开GAPI显示功能。

OpenInput

打开直接响应硬件键盘输入消息功能

GetDisplayProperties

获得VFB详细结构信息

GetDefaultKeys

获得缺省的键值

操纵GAPI

开始一个游戏编写,首先要打开GAPI显示功能,获得控制视频显示缓存的控制权限。可以调用

GXOpenDisplay(HWND hwnd, DWORD dwFlags)

hwnd参数是游戏程序的窗口句柄,dwFlags定义了显示模式,宏GX_FULLSCREEN定义全屏模式,就能

对设备的全屏区域进行控制。返回值1说明打开成功,0是失败。

虽然都是使用mobile phone系统但是不同系列的产品可能使用不同的显示设备,那么对于不同的显示设备

就可能有不同的显示性能参数,不同的分辨率率,不同的色深,不同的颜色显示能力。当在编写一个为mobile

phone 系列运行的游戏程序时不得不考虑这些问题,以使程序能适应在不同的显示环境下达到程序所希望的显

示效果。

如何得到这些相关显示信息?可以调用下面函数:

GXDLL_API GXDisplayProperties GXGetDisplayProperties ();

它能得到显示设备的所有相关细节信息,这些都是在开发基于GAPI游戏时需要的。所有信息被返回到

GXDisplayProperties结构中,其结构如下:

struct GXDisplayProperties {

DWORD cxWidth;

DWORD cyHeight;

long cbxPitch;

long cbyPitch;

long cBPP;

DWORD ffFormat;

};

这个结构提供了显示设备的信息,也就是当前视频缓存区域的参数指标。

cxWidth和cyHeight是显示设备的宽和高的像素值,提供了显示设备横、纵能显示的像素个数;cBPP是

每个像素点需要的位数,总是等于2的n次方。cbxPitch和cbyPitch提供了相邻两个像素间从内存数据上相差

的字节数,cbxPitch表示的是左右两个像素间的差值,当cBPP大于等于8时,cbxPitch表示相差的字节个数,

当cBPP小于8时,cbxPitch已经不能真实的反映出相差的字节数,事实上必须自己计算得到相邻的地址:

比如:

cBPP = 4;

Leftpointaddr = pb + (((current_x+1) * cBPP) >> 3)

+ (current_y * cbyPitch)

cbyPitch表示的上下方向间两个像素的差值,计算时通过加减cbyPitch来的到上下方向的像素点的地址:

downpointaddr = currentpointaddr + cbyPitch;

ffFormat参数说明显示设备对色深的处理方式及显示的格式:

当ffFormat 等于 KfLandscape 说明当前显示方式是横屏方式,即原点(0,0)变成了左下角。

ffFormat 等于 KfPalette 说明色彩显示是基于调色板方式。

ffFormat 等于 KfDirect 说明色彩显示是直接映射,不引用调色板。

ffFormat 等于 KfDirectInverted 说明颜色显示是反转的。

ffFormat 等于 kfDirect555 、kfDirect565、kfDirect888 说明映射颜色显示时,数字表示红绿蓝所占的位

信息。

计算每一个像素坐标地址方法如下(x,y):

unsigned char * pb;

if (cBPP < 8) {

address = pb + ((x * cBPP) >> 3) + (y * cbyPitch)

}

else

{

address = pb + (x * cbxPitch) + (y * cbyPitch);

}

判断是否是标准显示设备

可以使用函数GXIsDisplayDRAMBuffer (),返回值为TRUE说明是非标准显示设备,返回值为FLASE说明

是标准显示设备。当是非标准显示设备时,需要使用函数GXSetViewport来定义显示屏幕的区域,在标准显示

设备上使用GXSetViewport是无效的。

GXDLL_API int GXSetViewport (

DWORD dwTop,

DWORD dwHeight,

DWORD dwReserved1,

DWORD dwReserved2)

dwTop定义了屏幕显示区域的y坐标,dwHeight显示区域的高度,dwReserved1、dwReserved被保留,

必须需设置为0。

开始绘制像素

现在就可以准备对缓存区进行操作绘制图形,通过GXBeginDraw得到缓存区的首地址:

void * GXBeginDraw ();

函数返回值就是需要的首地址,如果是NULL说明显示缓存区得不到。然后就可以进行一些列像素的操作,

操作完毕后需要调用GXEndDraw 结束一次操作:

int GXEndDraw ();

返回1说明调用成功,0说明错误。

提交绘制的信息,已使变化的画面生效。当程序失去焦点时必须调用GXSuspend ()挂起所有GAPI的操作,

把屏幕控制交给其他程序,当接收到获得焦点信息时,程序必须调用GXResume ()使得程序继续运行GAPI函

数。

当退出程序时,必须释放GAPI资源,可以调用:

int GXCloseDisplay ();

GAPI高效贴图

在开发一些图像处理或游戏时我们可以使用GDI制作出满意的产品,但是开发复杂高速的图形显示或高效

率的动态游戏时,往往对GDI的显示效率不高而感到沮丧,虽然可以使用双缓存等技术,但是GDI层接口毕竟

效率低,无法满足要求。

GAPI对显示缓存区的直接操作,使显示效率大大提高,所以在目前mobile phone上当需要处理高速贴图

时GAPI就当之无愧了。

虽然GAPI高效强大,提供了对显示缓存区直接的读写权限,但是基于如此低级的功能函数,在编写一个

稍微复杂的程序时,就会花费大量的时间和精力在处理对显示缓存区的操作,因为此缓存区并不像GDI提供的

绘制缓存区对图片显示一样操作容易,为了显示一幅bmp图就需要编写好几页的代码,这是非常令人厌倦的事。

在这里主要介绍一下一个使用GAPI编写的第三方贴图类STGapiBuffer。STGapiBuffer提供了类似于GDI

方式的接口操作简单,很容易就构造出了需要显示的缓存内容,最后只要简单的把它们拷贝进显示缓存区,就

可以显示出来了。

只要简单的把STGapiBuffer.h 和 加入到工程里面就可以方面的使用了。

为了需要绘制一个jpg图片,首先需要把图片加载入此类里,并创建适合STGapiBuffer处理的数据,使用

CreateNativeBitmap函数,需要如下操作:

HBITMAP hBitmap = SHLoadDIBitmap(_T(""));

g_pNativeBitmap = g_NativeBitmap(hBitmap);

::DeleteObject(hBitmap);

接下去需要为绘制到哪里设置目标对象,可以是另一个STGapiBuffer的缓存区,也可以是显示的显示设备

缓存区,调用函数SetBuffer,代码如下:

g_fer(pDisplayBuffer);

最好可以使用CSTGapiBuffer::BitBlt来把需要的数据绘制到缓存区里,类似于GDI函数BitBlt,代码如下:

g_(0, 0, 100, 100, g_pNativeBitmap);

这样就把一幅图片显示到了屏幕上。

CSTGapiBuffer还提供了绘制透明图片的函数,在绘图时经常会遇到这样的情况,使用

CSTGapiBuffer::MaskedBlt方便的绘制指定透明色的图案。当然我在用GDI绘图时经常使用CreateMemoryDC

创建一个临时内存DC来绘制,CSTGapiBuffer也提供了类似的功能的函数CSTGapiBuffer::

CreateMemoryBuffer

CSTGapiBuffer类使用示例(部分代码):

CNativeBitmap* pAsteroidBitmap = NULL;

CNativeBitmap* pAsteroidMask = NULL;

CSTGapiBuffer gapiBufferBackground; // 背景

CSTGapiBuffer gapiBufferMemory;

CSTGapiBuffer gapiBufferScreen;

HBITMAP hBackground = ::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BACKGROUND));

CNativeBitmap * pBackgroundBitmap =

NativeBitmap(hBackground);

::DeleteObject(hBackground);

MemoryBuffer();

(0, 20, dwDispWidth, dwDispHeight,

pBackgroundBitmap);

delete pBackgroundBitmap;

pBackgroundBitmap = NULL;

////////////////////////////////////

HBITMAP hAsteroid = ::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_ASTEROID));

pAsteroidBitmap = NativeBitmap(hAsteroid);

::DeleteObject(hAsteroid);

HBITMAP hAsteroidMask = ::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_ASTEROID_MASK));

pAsteroidMask = NativeBitmap(hAsteroidMask);

::DeleteObject(hAsteroidMask);

/////////////////////////////////////////

//

dwTransparentColor = iveColor(RGB(249, 57, 198));

// create an offscreen buffer

MemoryBuffer();

(&gapiBufferBackground);

///////////////////////////////////////////////////

//

arentBltEx(0, nOffset%dwDispHeight,

50, 60, pAsteroidBitmap, dwTransparentColor);

arentBltEx(100, (nOffset*2)%dwDispHeight,

50, 60, pAsteroidBitmap, dwTransparentColor);

arentBltEx(nOffset%dwDispWidth,

50, 50, 60, pAsteroidBitmap, dwTransparentColor);

arentBltEx((nOffset*2)%dwDispWidth,

150, 50, 60, pAsteroidBitmap, dwTransparentColor);

Blt(dwDispWidth-(nOffset*2)%dwDispWidth,

(nOffset*8)%dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);

Blt((nOffset%dwDispWidth*5), (nOffset*3)%

dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);

Blt((nOffset*3)%dwDispWidth, (nOffset*4)%

dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);

Blt((nOffset%dwDispWidth*2), dwDispHeight-

(nOffset*5)%dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);

RECT rc = { 0, 20, dwDispWidth/3, 10 };

FillRect(&rc, RGB(255, 0, 0));

= dwDispWidth/3;

= 2*dwDispWidth/3;

FillRect(&rc, RGB(0, 255, 0));

= 2*dwDispWidth/3;

= dwDispWidth;

FillRect(&rc, RGB(0, 0, 255));

void* pBuffer = GXBeginDraw();

fer(pBuffer);

(&gapiBufferMemory);

GXEndDraw();

Gapi键盘消息

使用GXOpenInput()函数获得键盘的控制权,调用GXGetDefaultKeys(GX_NORMALKEYS)函数来获得默认

键盘的消息映射。然后在Windows消息处理函数中我们就能收到由GAPI发送过来的键盘消息,当你按下某一

个键,程序会收到WM_KEYDOWN,wParam参数包含GAPI映射的这个键的消息,通过与得到的GXKeyList

结构中的字段定义来判断当前收到的键是不是定义的功能键。

GXKeyList结构如下:

struct GXKeyList {

short vkUp;

POINT ptUp;

Short vkDown;

POINT ptDown;

Short vkLeft;

POINT ptLeft;

Short vkRight;

POINT ptRight;

Short vkA;

POINT ptA;

Short vkB;

POINT ptB;

Short vkC;

POINT ptC;

Short vkStart;

POINT ptStart;

};

游戏的振动感

在游戏中提供振动效果,是让玩家非常兴奋的事情,可以提升游戏的吸引程度。Mobile Phone SDK 提供

了振动效果的API,允许你控制振动,类似与控制声音一样简单。

获得振动设备属性

要获知手机是否支持振动,振动的性能,当前的设置情况,可以调用下面的函数:

int VibrateGetDeviceCaps(VIBRATEDEVICECAPS vdc);

VIBRATEDEVICECAPS是个枚举类型,结构如下

typedef enum {

VDC_AMPLITUDE,

VDC_FREQUENCY,

VDC_LAST

} VIBRATEDEVICECAPS

VDC_AMPLITUDE 查询振动设备所能支持的振幅大小

VDC_FREQUENCY 查询振动设备所能支持的振动频率大小

VDC_LAST 查询振动设备所能支持的振幅大小

如果函数成功,它将返回数字0到7,数字0说明设备没有提供振动功能,1说明设备具有振动功能,并

且可以使用,但是仅仅具有打开关闭震动,无法对振动进行调节,2到7说明了设备提供了不同等级的振动功

能,数字越大提供的调节能力越强。当设备具有不同等级振动能力时,我就可以通过VIBRATENOTE结构做详

细设置。

怎么才能开始真正的使用振动功能呢?mobile phone SDK提供Vibrate函数:

HRESULT Vibrate(

DWORD cvn,

const VIBRATENOTE * rgvn,

BOOL fRepeat,

DWORD dwTimeout

);

它能提供不同振幅,不同频率,并可以调节需要振动时间。cvn参数是第二个参数rgvn数组的维数,rgvn

是指向一组VIBRATENOTE结构的指针。

VIBRATENOTE结构如下:

typedef struct {

WORD wDuration;

BYTE bAmplitude;

BYTE bFrequency;

} VIBRATENOTE

wDuration说明了震动持续的时间,bAmplitude定义了振动的振幅大小,允许设置0-7级,如果等于0xff

系统使用缺省值作为参数,bFrequency定义了振动的频率高低,允许设置0-7级,如果0xff系统使用缺省值作

为参数。

当你需要停止当然的振动时,可以调用VibrateStop()函数,返回S_OK说明成功调用,E_FAIL说明调用失

败。

下面是代码示例:

int caps = -1;

caps = VibrateGetDeviceCaps(VDC_AMPLITUDE);

if(caps<=0)

return FALSE; //振幅返回失败,说明不支持振动功能

HRESULT hr = Vibrate(0, NULL, TRUE, INFINITE); //设定为无时间限制

if(hr == E_FAIL)

{

MessageBox(NULL,L"E_FAIL",L"",MB_OK);

}

else if(hr == E_NOTIMPL)

{

MessageBox(NULL,L"E_NOTIMPL",L"",MB_OK);

}

Sleep(1000); //振动所花时间

VibrateStop();

开始第一手机游戏历程

在这里使用GAPI模拟一个贪食者游戏,它非常简单。主要注重怎么具体使用GAPI,在使用中怎么对视频

缓存区操作演示,并不去美化外表。

初始化GAPI库,在InitInstance函数里我们对GAPI的显示和输入进行了初始化。

if (GXOpenDisplay( hWnd, GX_FULLSCREEN) == 0)

return FALSE;

gx_displayprop = GXGetDisplayProperties();

if (gx_ != 16)

{

// Only dealing with 16 bit color in this code

GXCloseDisplay();

MessageBox(hWnd,L"Sorry, only supporting 16bit color",L"Sorry!", MB_OK);

return FALSE;

}

framebuf = (unsigned short*) malloc(sizeof(short)*gx_h*gx_ht);

if(framebuf==NULL)

return FALSE;

ClearScreen(framebuf,0xff,0xff,0xff);

GXOpenInput();

// Get default buttons for up/down etc.

gx_keylist = GXGetDefaultKeys(GX_NORMALKEYS);

初始化工作完成后,我们就需要对游戏的内容进行必要准备工作。我们首先初始化贪食者对象,我们为它

建立蛇头和它的身体。为了使贪食者不停的游动,必须有一个事件触发。在这个我们使用定时器,以100ms

的间隔发送消息,这样将得到一秒10帧的效果,这足以满足普通游戏的效果。

为了对定时器发送过来的消息进行处理,我们调用Run函数

void Run(HWND hwnd)

{

if(1 == JudgeDeath(framebuf))

{

KillTimer(hwnd,1);

RunVibrate(1000);

MessageBox(hwnd,_T("Snake has died!"),_T("died"),

MB_OK | MB_ICONINFORMATION);

SendMessage(hwnd,WM_PAINT,0,0);

InvalidateRect(hwnd,NULL,TRUE);

}

ChangeDirection();

SortAll();

RedrawSnake();

}

JudgeDeath每次都会判断是否已经死亡(蛇的任何部位有重叠),一旦满足死亡的条件,就取消定时器,

以便中止蛇的游动。ChangeDirection判断方向是否发生了改变。在这里我对蛇部位的重叠利用了两个象素是否

相同的颜色,如果颜色一直说明重叠发生。

void Get16Pixel(unsigned short *buffer,int x, int y,int *r, int *g, int *b)

{

unsigned short *pixeladd;

int address = (x * gx_ch>>1)

+ (y * gx_ch>>1);

pixeladd = (buffer+address);

if (gx_at & kfDirect565)

{

unsigned short PixelCol;

PixelCol = (*pixeladd);

*r = (PixelCol & 0xf800) >> 11;

*g = (PixelCol & 0x07e0) >> 5;

*b = (PixelCol & 0x001f);

}

else//555

{

unsigned short PixelCol;

PixelCol = (*pixeladd);

*r = (PixelCol & 0x7c00) >> 11;

*g = (PixelCol & 0x03e0) >> 5;

*b = (PixelCol & 0x001f);

}

}

第三方开发库介绍

GapiDraw的设计与DirectDraw非常相似,而且将更加容易使用,极大限度的为掌上设备进行了优化。下

面是一些DriectDraw中的一般功能,以及如何在GapiDraw中实现这些功能。

打开显示设备

计算机的显示内存是一块包含了图像数据的内存区域。为了直接向这块区域进行写操作,DirectDraw和

GapiDraw都需要你创建一个指定的称之为主界面的界面。直接绘制到这个主界面来影响屏幕的可见内容。

创建主界面的第一步是打开显示器,设置一个显示模式。下面的步骤是使用DirectDraw创建主界面的最少

步骤。

DirectDraw

LPDIRECTDRAW lpDD;

HRESULT ddrval;

//创建主Direct Draw对象

ddrval = DirectDrawCreate(NULL, &lpDD, NULL);

if(ddrval != DD_OK)

{

return(false);

}

//设置合作级别以允许Direct Draw全屏运行

ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);

if(ddrval != DD_OK)

{

lpDD->Release();

return(false);

}

//设置显示模式为320x240x16

ddrval = lpDD->SetDisplayMode(320, 240, 16);

if(ddrval != DD_OK)

{

lpDD->Release();

return(false);

}

使用GapiDraw是比较容易的。因为界面是对象而不是COM接口,所以根本就不用手工进行释放。下面的

GapiDraw例子只使用了一条命令打开显示设备,设置默认显示模式。

GapiDraw

CGapiDisplay display;

HRESULT gdrval;

//使用标准Pocket PC240x320x16模式打开显示

gdrval = splay(hwnd, GDOPENDISPLAY_FULLSCREEN);

if(gdrval != GD_OK)

{

return(false);

}

取回主界面和后背缓冲

使用Direct Draw,你必须手工请求创建一个指定界面的主Direcr Draw对象,主界面主要用于直接在显示

器上绘制。在Direct Draw中只有一个界面接口用于双内存界面和显示。这可以简单地解释为过去使用的COM

模式中的子类化缺陷。下面的例子创建一个主界面,使用Direct Draw取回它的后背缓冲。

DirectDraw

LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw 主界面

LPDIRECTDRAWSURFACE lpDDSBack; // DirectDraw 后背缓冲

DDSURFACEDESC ddsd;

DDSCAPS ddscaps;

HRESULT ddrval;

memset(&ddsd, 0, sizeof(ddsd));

= sizeof(ddsd);

s = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

= DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;

BufferCount = 1;

//创建主界面

ddrval = lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL);

if(ddrval != DD_OK)

{

lpDD->Release();

return(false);

}

// Get the back buffer获得背后缓冲

= DDSCAPS_BACKBUFFER;

ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);

if(ddrval != DD_OK)

{

lpDDSPrimary->Release();

lpDD->Release();

return(false);

}

相反,使用GapiDraw 事情会更容易。一旦调用了CGapiSurface::OpenDisplay,CGapiDisplay 对象自动

地变成主界面。因为CGapiDisplay 是CGapiSurface的子类,所有的位图传输(blit)和绘制操作都已经是可用

的了。为了从CGapiDisplay得到后背缓冲,使用如下代码:

GapiDraw

CGapiSurface backbuffer; // GapiDraw 后背缓冲

HRESULT gdrval;

//得到后背缓冲

gdrval = kBuffer(&backbuffer);

if(gdrval != GD_OK)

{

return(false);

}

失败的界面

DirectDraw的界面通常储存在图像存储器中,可以在任何时候被覆盖(在用户切换程序或者启动另外一个

使用GDI的程序的情况下)。这是因为对界面的每个操作在任何时候都有可能失败,简单地说是因为界面数据

被覆盖。因此所有使用Direct Draw的操作必须每次检查界面是否失败,然后从失败处手工恢复和重新创建界

面。下面的例子说明了这一点。

DirectDraw

ddrval = lpDDSBack->Blt(&rcRectDest, lpDDSMySurf, &rcRectSrc, DDBLT_WAIT, NULL);

if(ddrval == DDERR_SURFACELOST)

{

//界面被覆盖,现在你必须手工恢复和重新创建所有的界面

}

ddrval = lpDDSPrimary->Flip(NULL, DDFLIP_WAIT);

if(ddrval == DDERR_SURFACELOST)

{

//界面被覆盖,现在你必须手工恢复和重新创建所有的界面

}

Pocket PC不使用图像存储器,所有界面数据被储存在RAM物理内存,只有调用CGapiDisplay::Flip时才

被复制到显示区域。如果Pocket PC设备访问了显示区域的缓冲,那么Pocket PC可能会移动它的后背缓冲的

位置。到目前为止,还没有设备被告之可以做到这点,但是它始终是最好的设计。可以使用下面的代码捕获在

GapiDraw中丢失的后背缓冲。

GapiDraw

//普通操作中的界面不能丢失

gdrval = (&rcRectDest, &mysurf, &rcRectSrc, 0, NULL);

gdrval = ();

if(gdrval == GDERR_BACKBUFFERLOST)

{

//显示缓冲被移动,刚好获得一个更新的后背缓冲

kBuffer(&backbuffer);

}

结论

上面提及的是GapiDraw和Direct Draw之间的主要不同。其它的特性,象blit、颜色值、矩形坐标等都是

有差异的。GapDraw也包含一个巨大的扩展特性,这些特性在Direct Draw不可用,象高级的blit影响、快速

旋转、从文件或内存装载位图图像、绘制工具、冲突掩码、界面交叉、线程定时器、位图字体支持以及更多。

发布评论

评论列表 (0)

  1. 暂无评论