2.2 我来了
按照不同的显示模式和要求我们设置好了画笔以后,就该轮到主脚登场了,于是下一步我们就要让我们这个游戏的主脚闷炮和他的小伙伴们(NPC)上场了.事实上我们要完成的就是贴图,由于完成了Directdraw的初始化,贴图就相当简单了.在讲贴图以前我们先一定要介绍一个在游戏编程里面占有很重要地位的两个函数:图像传送函数IDirectDrawSurface7::Bltfast( )
先说图像传送函数.
IDirectDrawSurface7::Bltfast( )
IDirectDrawSurface7::Blt( )和IDirectDrawSurface7::Bltfast( ) 函数都可以进行图像传送。Blt( )函数功能很强大,可对图像进行缩放、旋转、镜象等操作。不过平常我们用简单但够用的Bltfast( )就可以了.
它的原形是:
HRESULT BltFast(
DWORD dwX,
DWORD dwY,
LPDIRECTDRAWSURFACE lpDDSrcSurface,
LPRECT lpSrcRect,
DWORD dwTrans
);
下面将逐一介绍这几个参数:
(1)dwX和dwY
图像将被传送到目标页面何处。
(2)lpDDSrcSurface
图像传送操作的源页面。目标页面就是调用此方法的页面。
(3)lpSrcRect
一个 RECT (Rectangle,即矩形)结构的地址,指明源页面上将被传送的区域。如果该参数是 NULL, 整个源页面将被使用。 RECT结构在DirectDraw中非常常用,最好在程序中定义一个RECT类型的全局变量,如rect,再象这样写一个函数:
void MakeRect (int left, int top, int right, int bottom)
{
rect.bottom = bottom;
rect.left = left;
rect.right = right;
rect.top = top;
}
用时对它的left、top、right、bottom参数分别赋予矩形的左上角的x和y坐标、右下角的x和y坐标。
(4)dwTrans
指定传送类型。有如下几种:
DDBLTFAST_NOCOLORKEY
指定进行一次普通的复制,不带透明成分。
DDBLTFAST_SRCCOLORKEY
指定进行一次带透明色的图像传送,使用源页面的透明色。
DDBLTFAST_WAIT
如果图像传送器正忙,不断重试直到图像传送器准备好并传送好时才返回。一般都使用这个参数。
这几种类型很长又经常用,最好这样定义两个全局变量:
DWORD SrcKey = DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT
DWORD NoKey = DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT
举一些例子。如果我们想把lpDDSBack上的所有内容传到lpDDSBuffer上作为背景,则使用:
lpDDSBuffer -> BltFast(0,0, lpDDSBack, NULL,NoKey);
如果我们想将lpDDSSpirit上(20,30)到(50,60)的一个人物放到lpDDSBuffer上,且左上角在lpDDSBuffer的(400,300)处,要使用透明色,则使用:
MakeRect (20,30,50,60);
LpDDSBuffer -> BltFast(400,300,lpDDSSpirit,&rect,SrcKey);
请注意,DirectDraw的BLT函数只要源矩形或被传送到目标页面后的图像有一点在页面外,例如MakeRect(100,200,500,400)后将其BLT到一个640x480的页面的(400,200)处,就什么都不会BLT!解决问题最好的办法是自己写一个新的BLT,剪裁一下(有的书介绍用Clipper来剪裁,速度比这种方法慢)。
我们还必须在初始化的时候加载所有要显示的人物图片的指针和装载进图片.也就是说在初始化里面我们必须加入以下部分(而具体到底要加载什么人物图片就是通过上面介绍的那个函数就可以完成了).
要将位图(*.bmp)调入页面是非常简单的,对于普通的真彩(24位)图像我们只需用DDReLoadBitmap(页面,"图像名.bmp")即可调入图像。这个函数在ddutil.cpp中,你需要把它和ddutil.h拷贝到你的程序的目录下,将其加入你的Project并在主程序的开头#include "ddutil.h"(在Chapter IV.zip中你可以找到我修改过以适应DirectDraw7的ddutil.h和ddutil.cpp)。注意,DDReLoadBitmap( )函数会自动缩放图像以适应页面的大小。调入图片后,还要设置图片的透明色,不然图片不会正常显示.
DDBLTFX ddBltFx;//创建页面对象
ddBltFx.dwSize=sizeof(DDBLTFX);//申请空间
ddBltFx.dwFillColor=0;
lpDDSPrimary->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);//创建主页面
lpDDSBuffer->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
lpDDSMap->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
lpDDSOut->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
lpDDSOther->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
lpDDSRoom->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
lpDDSEnd->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
lpDDSSprite->Blt(NULL,NULL,NULL,DDBLT_WAIT|DDBLT_COLORFILL,&ddBltFx);
DDReLoadBitmap(lpDDSMap,"inn.BMP");//装载背景图片
DDReLoadBitmap(lpDDSOut,"city.BMP");
DDReLoadBitmap(lpDDSRoom,"Room.bmp");
DDReLoadBitmap(lpDDSEnd,"end.bmp");
DDReLoadBitmap(lpDDSSprite,"Sprite.BMP");//装载人物图片
DDSetColorKey(lpDDSMap,RGB(0,255,0));//设置地图透明色
DDSetColorKey(lpDDSOut,RGB(0,255,0));
DDSetColorKey(lpDDSRoom,RGB(0,255,0));
DDSetColorKey(lpDDSOther,RGB(0,255,0));
DDSetColorKey(lpDDSEnd,RGB(0,255,0));
DDSetColorKey(lpDDSSprite,RGB(8,33,82));
MakeRect(0,0,640,480);
lpDDSOther->BltFast(0,0,lpDDSMap,&r,NoKey);
MakeRect(32,64);
lpDDSOther->BltFast(275,175,lpDDSSprite,&r,SrcKey);
MakeRect(672,256);
lpDDSOther->BltFast(223,313,lpDDSSprite,&r,SrcKey);
MakeRect(608,96);
lpDDSOther->BltFast(445,320,lpDDSSprite,&r,SrcKey);2.3 让我动吧
经过我们的一番努力,我们的男主脚终于在我们的世界诞生了,可是他还是不能行动啊,那多么不方便啊,不能移动怎么去冒险啊,所以接下来我们就说说怎么样让他动起来.
先还是说说人物移动的原理吧: 后台缓存和换页对形成无闪烁的动画至关重要。举一个例子,要显示一个物体在一张图片上运动,我们需要在a时刻先画物体,在b时刻把一开始被物体遮住的背景画好,最后在c时刻把物体画在新位置上。但这些操作需要一定时间,如果我们直接改主页面,那么b时刻用户就会看到画面上没有物体,但a和c时刻画面上又有物体,用户就会觉得画面有些闪烁。如何解决这个问题呢?
DirectDraw 中的换页就可以帮我们这个忙。首先,我们得设置好一个换页链结构,它由一组 DirectDraw 页面组成,每一个页面都可以被轮流换页至显示屏幕。当前正好位于显示屏幕的页面叫主页面。等待换页至屏幕的页面叫后台缓存。应用程序在后台缓存上进行绘图操作,然后将此页面换页成为主页面,原来的后台缓存就显示在屏幕上了,而原来的主页面就成为了后台缓存,在一般情况下我们只需改变后台缓存的内容。所以,我们在完成了b、c两个步骤后再换页即可避免闪烁现象。换页所使用的的函数是IDirectDrawSurface7::Flip( ) 它的原形是:
HRESULT Flip(LPDIRECTDRAWSURFACE lpDDSurface, DWORD dwFlags);
下面介绍它的参数:
(1)lpDDSurface
换页链中另一个页面的 IDirectDrawSurface7接口的地址,代表换页操作的目标页面。这个页面必须是换页链中的一员。该参数缺省值是 NULL, 在这种情况下, DirectDraw 从换页链中按照前后隶属关系依次换页。
(2)dwFlags
换页的标志选项,常用DDFLIP_WAIT,同BltFast中的DDBLTFAST_WAIT差不多。
一般我们这样即可换页:
lpDDSPrimary->Flip(NULL,DDFLIP_WAIT);
这部分内容可以在我的Publicfan.cpp里面找到,里面我自定义了一个页面置换函数void Flip().
再说了换页函数后并不是人物就可以动了,只是达成了人物移动的一个最基本条件,然后我们还要从逻辑上真正去实现人物的移动.逻辑上人物的移动其实很好理解,当人物向左走的时候,贴图的y坐标不变,而x坐标递减;向右移东的时候,则反之.于是我写了一个man.cpp,里面自定义一个人物移动的函数
Void man::move(int where)
switch(where)
{
case 0: //人物向左移动
if(man1.tx<0)
man1.tx=0;
if(man1.tx>640)
man1.tx=640;
if(man1.ty<0)
man1.ty=0;
if(man1.ty>480)
man1.ty=480;
MakeRect(544,352);
man1.tx-=10;
break;
case 1: //人物向下移动
if(man1.tx<0)
man1.tx=0;
if(man1.tx>640)
man1.tx=640;
if(man1.ty<0)
man1.ty=0;
if(man1.ty>480)
man1.ty=480;
MakeRect(544,320);
man1.ty+=10;
break;
case 2: //人物向右移动
if(man1.tx<0)
man1.tx=0;
if(man1.tx>640)
man1.tx=640;
if(man1.ty<0)
man1.ty=0;
if(man1.ty>480)
man1.ty=480;
MakeRect(544,288);
man1.tx+=10;
break;
case 3: //人物向上移动
if(man1.tx<0)
man1.tx=0;
if(man1.tx>640)
man1.tx=640;
if(man1.ty<0)
man1.ty=0;
if(man1.ty>480)
man1.ty=480;
MakeRect(544,256);
man1.ty-=10;
break;
case 4: //人物朝向不同选择贴不同的face
if(man1.tx<0)
man1.tx=0;
if(man1.tx>640)
man1.tx=640;
if(man1.ty<0)
man1.ty=0;
if(man1.ty>480)
man1.ty=480;
if(face==up)
{
MakeRect(544,256);
}
if(face==down)
{
MakeRect(544,320);
}
if(face==left)
{
MakeRect(544,352);
}
if(face==right)
{
MakeRect(544,288);
}
break;
}
}
四个case语句代表了选择四个不同方向的移动,case里面就有坐标递加或者递减的操作,来改变人物的移动坐标,最后一个case解决了人物朝向不同方向移动的时候,贴出不同的face.最后把前面我们说到的消息循环结合起来就可以了.
case VK_UP: //按下键盘上键,触发人物向上移动
enable=1;
break;
case VK_DOWN: //按下键盘上键,触发人物向下移动
enable=2;
break;
case VK_LEFT: //按左键盘上键,触发人物向左移动
enable=3;
break;
case VK_RIGHT: //按右键盘上键,触发人物向右移动
enable=4;
break;
按下键盘上不同的方向键的时候人物就向响应的方向移动.到此,我们的主脚就可以在屏幕内按照我们的要求随便移动了.