版本:VS2015 语言:C++
前段时间去白空轨了,感觉快燃尽了。没有看Windows的书,所以博客也没更,不过请组织放心,从现在开始,即使是节假日,我也会仔细钻研DirectX的。
今天是第七章的完结,当时作者写书比较老了,还一直用的8位图,而8位图牵扯到调色板,所以他就一直在那边纠结,我就简单的看了一下,给大家介绍的也都是VS中能够调试出来的程序。
好了,进入正式的学习。
玩cocos的玩家们应该对Sprite不陌生,Sprite简单的来说就是一张图片嘛,从磁盘中加载到内存中然后显示到屏幕上,十分方便。而这次我就要介绍的就是在DirectX Windows程序中加载图片。
首先准备一章图片,因为作者使用的是BMP格式的,所以大家一定要注意图片的格式,普通的图片是用不了的,而且现在我写的程序只能使用24位图,所以需要一个史前的工具:
链接:http://pan.baidu.com/s/1qXJsAJi 密码:icca
用这个工具画一张图,并保存成bmp格式(我的代码中尺寸要求是300*300的):
嗯,就是一棵树。
好了,图片有了,怎么加载到程序中呢?看代码:
#define BITMAP_ID 0x4D42
// 定义BMP数据结构
typedef struct BITMAP_FILE_TAG
{
BITMAPFILEHEADER bitmapfileheader; //BMP文件头部
BITMAPINFOHEADER bitmapinfoheader; //BMP信息头部
PALETTEENTRY palette[256]; //调色板(但是在我们的程序中没有作用)
UCHAR *buffer; //数据
}BITMAP_FILE, *BITMAP_FILE_PTR;
BITMAP_FILE_PTR picture1; //我们的图片
// 翻转bmp图片
int Flip_Bitmap(UCHAR *image, int bytes_per_line, int height)
{
UCHAR* buffer;
int index;
if (!(buffer = (UCHAR*)malloc(bytes_per_line * height)))
{
popMessage(TEXT("malloc ERROR"));
return 0;
}
memcpy(buffer, image, bytes_per_line * height);
for (index = 0; index < height; ++index)
{
memcpy(&image[((height - 1) - index)*bytes_per_line], &buffer[index * bytes_per_line], bytes_per_line);
}
free(buffer);
return 1;
}
// 读取bmp类型的图片
int Load_Bitmap_File(BITMAP_FILE_PTR bitmap, char* filename)
{
int file_handle; //文件打开处理的结果标志
OFSTRUCT file_data; //OF结构,即OpenFile函数打开后存入的数据结构
// 打开需要的图片
if (-1 == (file_handle = OpenFile(filename, &file_data, OF_READ)))
{
// 打开出错
popMessage(TEXT("OpenFile ERROR"));
return 0;
}
// 读取文件头部
_lread(file_handle, &bitmap->bitmapfileheader, sizeof(BITMAPFILEHEADER));
if (bitmap->bitmapfileheader.bfType != BITMAP_ID)
{
_lclose(file_handle);
popMessage(TEXT("THIS FILE IS NOT BMP"));
return 0;
}
// 读取文件信息头部
_lread(file_handle, &bitmap->bitmapinfoheader, sizeof(BITMAPINFOHEADER));
_llseek(file_handle, -(int)(bitmap->bitmapinfoheader.biSizeImage), SEEK_END);
if (bitmap->bitmapinfoheader.biBitCount == 24)
{
// 分配好内存
if (bitmap->buffer)
free(bitmap->buffer);
if (!(bitmap->buffer = (UCHAR*)malloc(bitmap->bitmapinfoheader.biSizeImage)))
{
_lclose(file_handle);
popMessage(TEXT("malloc ERROR"));
return 0;
}
// 添加进来
_lread(file_handle, bitmap->buffer, bitmap->bitmapinfoheader.biSizeImage);
}
else
{
// 其他情况报错
_lclose(file_handle);
popMessage(TEXT("COLOR DEPTH IS ERROR"));
return 0;
}
_lclose(file_handle);
// 最后记得把图片翻转回来
Flip_Bitmap(bitmap->buffer, bitmap->bitmapinfoheader.biWidth*(bitmap->bitmapinfoheader.biBitCount / 8), bitmap->bitmapinfoheader.biHeight);
return 1;
}
// 卸载对应的图片
int UnLoad_Bitmap_File(BITMAP_FILE_PTR bitmap)
{
if (bitmap->buffer)
{
free(bitmap->buffer);
bitmap->buffer = NULL;
}
return 1;
}
// 游戏初始化
int Game_Init(void* params = NULL)
{
// 基础设置,略,不清楚的玩家请参见之前的博客
// 载入24位图
picture1 = new BITMAP_FILE();
if (!Load_Bitmap_File(picture1, "tree.bmp"))
{
popMessage(TEXT("LOAD PICTURE ERROR"));
return 0;
}
return 1;
}
// 游戏结束
int Game_Shutdown(void* params = NULL)
{
// 释放初始化时创建的对象
UnLoad_Bitmap_File(picture1);
delete picture1;
//其他对象的释放,略
return 1;
}
// 游戏主循环
int Game_Main(void* params = NULL)
{
// 判断是否要退出
if (KEYDOWN(VK_ESCAPE))
PostMessage(main_window_handle, WM_CLOSE, 0, 0);
// 初始化主界面描述
DDRAW_INIT_STRUCT(ddsd);
if (FAILED(lpddsback->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL))) //有备用表面时用备用表面加锁
{
wsprintf(msg, TEXT("LOCK 出错了"));
popMessage(msg);
}
//画颜色
UINT *video_buffer = (UINT*)ddsd.lpSurface;
for (int x = 0; x < 640; ++x)
for (int y = 0; y < 480; ++y)
Plot_Pixel_Fast32_2(x, y, 255, 255, 255, 128, video_buffer, ddsd.lPitch);
// 载入图片
int pos_x = 170;
int pos_y = 180;
for (int x = pos_x; x < 300+ pos_x; ++x)
for (int y = pos_y; y < 300+pos_y; ++y)
Plot_Pixel_Fast32_2(x, y, picture1->buffer[(y-pos_y) * 300 * 3 + (x-pos_x)*3 + 2], picture1->buffer[(y - pos_y) * 300 * 3+ (x - pos_x) * 3 + 1], picture1->buffer[(y - pos_y) * 300 * 3 + (x - pos_x) * 3 + 0], 0, video_buffer, ddsd.lPitch);
if (FAILED(lpddsback->Unlock(NULL))) //解锁
{
wsprintf(msg, TEXT("UNLOCK 出错了"));
popMessage(msg);
}
while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT))); //切换界面,这边的while不是很懂,应该每次只会调用一次
return 1;
}
代码稍微有点长,而且是我只截取的相关部分,暂略的部分请看之前的博文,我就不再多贴代码了。
在这边需要注意的是_lread等方法,千万注意,不要调用成C语言io.h中的方法了,这边的_lread是Windows API的方法,看了书的同学要注意_lseek在现在版本中的方法名是_llseek,不要用错了,不然找不到方法。
在Load_Bitmap_File中完成文件的读取,读取到picture1这一个自定的结构的缓存中,然后在游戏循环中渲染该缓存,就OK了。结果如下:
再加朵云:
很好,非常的完美(不要问我为什么云是蓝的),除了树被云遮挡住了一部分,这主要是我们的图片是24位的,没有透明度,书上的做法是设定一个颜色为透明颜色,当遇到该颜色,Direct会自动将其设定为透明度0,但我不想给这一块的实例,这样的程序即使自己改改也是可以的。关键是如何读取32位图片,这才是大家关心的吧?
哈哈,暂时先不做实验,要弄的话,我想看看png是怎么加载的。
现在是不是感觉整个人都升华了?我们居然在这么几行代码下弄出了类似Sprite的效果,实在是太棒了,感觉离一个游戏就差一步之遥了。
先等一等,在这一章中还有一个重要的概念,那就是离屏表面。大家可能要问了,离屏表面是什么鬼?我们之前学习主表面、备用表面,它们是缓存在哪的呢,没错就是显存中。
离屏表面其实也是一段缓存,但不用作显示的表面,只用作缓存。简单的来说,我们之前的程序是在内存中缓存了一张图片,而现在我们要把它移动到显存中,让它显示的效率更加高!
好了,让我们看看程序:
LPDIRECTDRAWSURFACE7 lpdds_off = NULL; //离屏表面
// 游戏初始化
int Game_Init(void* params = NULL)
{
// 基础设置和载入24位图略
// 创建离屏界面
DDRAW_INIT_STRUCT(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT ;
ddsd.dwWidth = 300;
ddsd.dwHeight = 300;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; //如果第二个参数设置为DDSCAPS_SYSTEMMEMORY,那么离屏表面会缓存到内存中
if (FAILED(lpdd->CreateSurface(&ddsd, &lpdds_off, NULL)))
{
popMessage(TEXT("创建离屏表面出错了"));
return 0;
}
DDRAW_INIT_STRUCT(ddsd); //将载入的图片加载到离屏表面
lpdds_off->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL);
UINT *buffer = (UINT*)ddsd.lpSurface;
for (int x = 0; x < 300; ++x)
for (int y = 0; y < 300; ++y)
Plot_Pixel_Fast32_2(x, y, picture2->buffer[y * 300 * 3 + x * 3 + 2], picture2->buffer[y * 300 * 3 + x * 3 + 1], picture2->buffer[y * 300 * 3 + x * 3 + 0], 0, buffer, ddsd.lPitch);
lpdds_off->Unlock(NULL);
return 1;
}
// 游戏主循环
int Game_Main(void* params = NULL)
{
// 上面的代码略
// 使用离屏表面载入图片
RECT dest_rest, source_rect;
dest_rest.left = pos_x; //目标矩形,即你的备用表面
dest_rest.top = pos_x;
dest_rest.right = pos_x + 300 - 1;
dest_rest.bottom = pos_x + 300 - 1;
source_rect.left = 0; //源矩形,即你的离屏表面
source_rect.top = 0;
source_rect.right = 300 - 1;
source_rect.bottom = 300 - 1;
if (FAILED(lpddsback->Unlock(NULL))) //解锁
{
wsprintf(msg, TEXT("UNLOCK 出错了"));
popMessage(msg);
}
if (FAILED(lpddsback->Blt(&dest_rest, lpdds_off, &source_rect, DDBLT_WAIT, NULL))) //加载!
{
popMessage(TEXT("离屏表面使用出错了"));
return 0;
}
while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT))); //切换界面,这边的while不是很懂,应该每次只会调用一次
return 1;
}
需要注意的是使用Blt方法把离屏的内容切到备用表面,不能在这边加锁,因为方法内部就主动实现的加锁解锁功能。
效果是一样的,但是我们已经能主动使用显存了!
后面其实还有个挺重要的内容,实现窗口化,但是我在win10上使用该代码,效果并不是很好,所以暂时就不介绍了。
嗯,接下来是第八章了,我准备中秋节的时候看到第十章,如果有有趣的内容的话,会把代码发给大家看看的,请期待一下咯。