Windows的GDI组件进行位图渲染

直接正文了.

我们之前介绍过是用WIN GUI进行简单图象的绘制,画基本的点,线,面以及笔刷,画笔的设置,而实际上,我们知道游戏中很少会让程序员使用画笔去自行绘制,多是美工制作好的位图,我们拿来进行游戏的制作,今天我们就位图的显示进行说明.

但由于WIN GUI仅仅可可以进行2D图象的绘制,对3D支持不够,所以我并不准备在这里下大工夫,有兴趣致力于2D游戏开发的朋友可以更多的去学习MFC相关资料,在这里我仅仅是进行简单的说明.

首先我们看一段代码

case WM_PAINT:
   hdc = BeginPaint(hWnd, &ps);
   memDC = CreateCompatibleDC(hdc);//创建内存DC
   hBitmap = (HBITMAP)LoadImage(hInst,"background.bmp" ,IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);//读取图片,设置类型为支持高色位图
   SelectObject(memDC,hBitmap);//选入新位图入内存DC
   GetObject(hBitmap, sizeof(BITMAP), &bmp);
   BitBlt(hdc,0,0,bmp.bmWidth,bmp.bmHeight,memDC,0,0,SRCCOPY);
   ReleaseDC(hWnd,memDC);
   EndPaint(hWnd, &ps);
   break;

从代码中我们可以看到几点需要我们注意:

  1. 首先,我们要明白设备支持的位图分为是有区分的,有支持16色32色256色真彩等,我们在设置一些参数的时候要注意到设备是否支持相应的位图格式.

  2. 我们进行设备贴图时,应该明白我们是先将位图放置到屏幕后背缓存中进行绘制,之后才将整块内存图象显示在屏幕之上的,而我们通常设置的屏幕刷新率也是根据这个进行设置,假如60赫兹,就代表每秒时间内,我们的后台缓存区和屏幕显示的前台缓存区相互替换了60次,我们肉眼视觉残余就会认为屏幕是连续的而非一桢一桢的图片替换的,这是一个假象,我们的后台缓存区图片数据准备好了之后,会根据需要替代屏幕上所见的前台缓冲区,我们就可以看到了绘制的图片,但是假如现在出现了一个问题,我们的后台缓冲区并未绘制完毕,系统就要求我们将其拿出替换前台的画面,会造成什么现象?呵呵,这就是后台我会详细说明的垂直同步.

  3. 最后记得释放DC设备,这是很重要的,以免我们创建的DC设备长期占用我们的内存.

我们做游戏的时候,地图很少是一张张完整的位图,你可以想象一个游戏需要多少张地图啊?实际上我们是将地图分割为一张张小元件进行拼接的方法来实现地图的制作的,这里不理解的朋友,我推荐大家下载一个RPG Maker XP,将很利于大家去了解游戏制作的一些流程.

绘制地图时我们使用 void CImage::TileBmp(LPCSTR filename,int column,int row)函数,其中第一个参数是BMP文件名,第二个参数是平铺的列数,第三个参数是平铺的行数. 我们下面给出代码例子:

void CImage::TileBmp(LPCSTR filename,int column,int row)
{
   HDC memDC;
   BITMAP bmp;
   HBITMAP hBitmap,hBitmapOld;
   memDC = CreateCompatibleDC(this->hDC);//创建内存DC
   hBitmap = (HBITMAP)LoadImage(NULL,_T(filename) ,IMAGE_BITMAP, 0, 0,
      LR_LOADFROMFILE|LR_CREATEDIBSECTION);//高色位图也没关系
   hBitmapOld = (HBITMAP)SelectObject(memDC,hBitmap);//选入新位图入内存DC
   GetObject(hBitmap, sizeof(BITMAP), &bmp);
   for(int i=0;i<row;i++)
for(int j=0;j<column;j++)
   BitBlt(this->hDC,j*bmp.bmWidth,i*bmp.bmHeight,bmp.bmWidth,
     bmp.bmHeight,memDC,0,0,SRCCOPY);//将内存DC中的数据复制到显示DC
   SelectObject(memDC,hBitmapOld);
   ReleaseDC(this->hWnd,memDC);
}

当然,当我们获得地图后,我们需要一个或多个角色在地图上移动,这时你可能会说,直接利用上面的帖图函数不就行了么?把角色图帖到地图上,再控制它的坐标进行移动.

这样做当然没错,但是问题出现了,使用过PS的朋友应该知道,无论我们如何进行调整,使用PhotoShop制作出来的图片都是一个矩形,而实际上美工给我们的角色图片也是一个矩形,我们总不能让一个矩形在地图上移动吧,这就要求我们将主角图片中多余的矩形边框去掉,仅帖上一个角色所占的不规则图形.因为游戏中的前景形象(通常称之为精灵)需要透明,因此必须指定精灵位图中的透明色,实现扣像的效果。纯色在计算机中以RGB方式表示时最为简单,我将使用白色RGB(255,255,255)做为例子.

void CImage::ShowSpirit(LPCSTR filename,int x,int y)
{
  HDC memDC,maskDC;
  HBITMAP hBitmap;
  HBITMAP hMaskBitmap;
  BITMAP bmp;
  memDC = CreateCompatibleDC(this->hDC);//创建内存DC
  maskDC = CreateCompatibleDC(this->hDC);//创建遮罩DC
  hBitmap = (HBITMAP)LoadImage(NULL,_T(filename) ,IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);//载入精灵位图
 GetObject(hBitmap, sizeof(BITMAP), &bmp);
 SelectObject(memDC,hBitmap);//选入精灵位图入内存DC
 
  hMaskBitmap = CreateBitmap(bmp.bmWidth,bmp.bmHeight,1,1,NULL);
  SelectObject(maskDC,hMaskBitmap);//选入遮罩位图入内存DC
  SetBkColor(maskDC,RGB(255,255,255));
 //将精灵位图从内存DC中复制到遮罩DC
  BitBlt(maskDC,0,0,bmp.bmWidth,bmp.bmHeight,memDC,0,0,SRCCOPY);
 //将内存DC中的图像“非”复制到显示DC
  BitBlt(this->hDC,x,y,bmp.bmWidth,bmp.bmHeight,memDC,0,0,SRCINVERT);
 //将遮罩DC中的图像“与”复制到显示DC
  BitBlt(this->hDC,x,y,bmp.bmWidth,bmp.bmHeight,maskDC,0,0,SRCAND);
  //将内存DC中的图像“非”复制到显示DC
  BitBlt(this->hDC,x,y,bmp.bmWidth,bmp.bmHeight,memDC,0,0,SRCINVERT);
  ReleaseDC(this->hWnd,memDC);
  ReleaseDC(this->hWnd,maskDC);
}

刚才我们已经可以正确显示我们的地图和精灵角色了,但游戏中我们还需要控制我们的角色进行移动,我们该怎么做呢?

之前我曾经说过,我们的Win运行过程实际上就是一个消息机制驱动的.无论我们是按下键盘还是移动鼠标,系统都会将其做为一个消息进行相应处理,我们当然可以利用这些消息对精灵进行控制.下面是一些常见的消息.

######键盘消息

              KEY_DOWN              按键按下
              KEY_UP                按键抬起

######鼠标消息 WM_LBUTTONDOWN 鼠标左键按下 WM_LBUTTONUP 鼠标左键抬起 WM_LBUTTONDBLCLK 鼠标左键双击

######系统 WM_QUIT 程序退出

######键盘

对消息WM_KEYDOWN的wParam进行识别,wParam中存储的是虚拟键码。 常用的虚拟键码:

  • VK_UP 方向键上
  • VK_CAPITAL Caps键
  • VK_DOWN 方向键下
  • VK_SPACE 空格键
  • VK_LEFT 方向键左
  • VK_INSERT Insert键
  • VK_RIGHT 方向键右
  • VK_DELETE Delete键
  • VK_CONTROL Ctrl键
  • VK_HOME Home键
  • VK_MENU Alt键
  • VK_END End键
  • VK_SHIFT Shift键
  • VK_PRIOR PageUp键
  • VK_ESCAPE Esc键
  • VK_NEXT PageDown键
  • VK_TAB Tab键
  • VK_NUMPAD0 小键盘0
  • VK_RETURN Enter键
  • VK_F1 F1键

######鼠标

对消息WM_LBUTTONDOWN的lParam进行识别,lParam的低位和高位中分别存储的是鼠标当前的x坐标和y坐标。用LOWORD(lParam)取x坐标,用HIWORD(lParam)取y坐标。

我们可以根据鼠标和键盘的相应消息不同,进行相应的处理.


今天我就说到这里了,确实讲述的非常粗糙,主要是因为这些部分对我们的3D游戏编程关系很小,大家可以做为简单的了解即可,下次起就正式开始以D3D为主进行介绍了,希望大家提起精神。

附加一句:若对3D游戏没兴趣的朋友,做2D游戏的话学习到这里就可以了,再学习一些Socket的知识以及数据库的调用,简单的网络游戏有已经可以做出来了,其实看起来很神秘的网络游戏制作,简单的说明也不过三部分,1,帖图,2,数据库的调用,3,网络功能的实现.呵呵,需要2D游戏相关代码和资料的朋友可以留言,我这里有些很不错的资料哦