游戏开发技术总结(经典之作)第三集 让图片动起来----快速切换图形实现动画
转载请注明出处
作者:孙广东 个人主页:http://blog.csdn.net/u010019717
更多精彩内容见:http://passport.baidu.com/business&un=a1224708372&fr=prin#7
3-1 任务
我们这里将利用VC 的时钟消息函数,在屏幕上显示变换的图形,由此形成动画的
效果。
3-2 建立时钟消息
在 VC 编程环境中选择菜单View 下的“ ClassWizard”项,进入MFC 的类向导(MFC
ClassWizard) 。图3-1
在类向导中选择WM_TIMER, 双击后,在成员功能栏(Member functions)可以看到已生
成的时钟消息函数ON_WM_TIMER。再按编辑代码(Edit Code), 就进入到时钟消息函数
OnTimer()中了。
void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数,[类向导中定义生成] { CDialog::OnTimer(nIDEvent); }
在程序中我们只要执行命令SetTimer(1,150,NULL),在程序运行期间每隔150 毫秒,
时钟消息函数OnTimer()中的程序就会被执行一次。
SetTimer 参数说明:
SetTimer(1, 150, NULL);
设定时器(第1 个定时器, 间隔时间(毫秒), 空值);
3-3 让角色动起来
我们在时钟消息里面写上图形显示的程序,图形就可以快速地变化了。当然你要
调入的图形是变化的,而要调入的图形变化,只要调入的图形文件名变化就行了。
3-3-1 变化的文件名
如果图形文件在 C 盘的game 目录下,并且文件名为“b0p.bmp”,我们要给出的文
件名格式就应该为“c:/game/b0p.bmp”。其中p 是顺序变化的。
图 3-2
在C++语言中我们可以用sprintf(cc,"c:/game/b%02d.bmp", p); 来设置图形文件名。cc
是字符串型变量,springf 是字符格式化函数,它可以将其它数据类型的值转换为字符串。
注意,引号内的写法 %02d,“%”的意义是转换的数p 是整形数,其中“2”的意义
是转换后的字串是两位,“0”的意义是数字不足两位时前面用“ 0”补位。
如 p=2, 执行sprintf(cc,"c:/game/b%02d.bmp", p);后。
cc 的值就是“c:/game/b02.bmp”。
如 p=12, 执行sprintf(cc,"c:/game/b%02d.bmp", p);后。
cc 的值就是“c:/game/b12.bmp”。
为了使用方便,这里我们把动态获取将调用的图形文件名也写成一个功能函数
getpic(⋯)。
3-3-2 getpic(⋯)调图片到相关位图
//************************************************** // getpic(CString cc,int p) 调图片到相关位图 // 由p 得到将调的图形文件名。 // 在指定目录中调入图形到相关位图bit //************************************************** BOOL getpic(CString cc,int p)//调图片到相关位图[2 章] { char name[256]; SetCurrentDirectory(appdir); //置当前目录 sprintf(name,"%s%s/c%05d.bmp",dir,cc,p);//生成将调的图形文件名 loadbmp(name); //调BMP 图片 return TRUE; }
这个函数的功能是:根据输入的目录名cc 和图形编号p,生成将调用的图形文
件名。
例如:dir = “图形/”,cc =“人” ,p =15;
则:name=“图形/人/00015.bmp” ;
接着就将调入 loadbmp(“图形/人/00015.bmp”) 图片到bit 中。
好了,我们可以将前面的调入图形文件和显示图形的代码写入时钟消息OnTimer()
中(程序中的行号是为了便于注释加的)。
3-3-3 可以动了
设定:p=0;m0=0; m1=400;dir="c:/game/";
现在,程序每隔150 毫秒, 时钟消息函数OnTimer()中的程序就会被执行一次。
void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数,[类向导中定义生成] 1{ CClientDC dc(this); //客户区设备环境 2 if(getpic("人",p)==FALSE) //调角色图片 3 {AfxMessageBox(cc+"没找到!");return;} 4 SelectObject(MemDC,bit); //设备相关位图关联到暂存设备场景 5 BitBlt(dc.m_hDC,200,160,w,h,MemDC,0,0,SRCCOPY);//显示游戏角色 6 p++; //下一动作 7 if(p>m1) p=m0; //若动作完成,重复。 CDialog::OnTimer(nIDEvent); }
当第一次执行时,p=0;
第 2 行,调入"c:/game/人/c00000.bmp"到bit。
第 4 行,将目录bit 内容调入并关联到MemDC 中。
第 5 行,将MemDC 中的图形拷贝到当前显示区在屏幕上显示。
第 6 行,p 加1,变成了1。
第二次执行时,p=1;
程序将目录 c:/game/人/下名为"c00001.bmp"图片内容在屏幕上显示。
第 n 次执行时, p=n-1;
程序将目录 c:/game/人/下名为"c0000p.bmp"图片内容在屏幕上显示。
每次在第 7 行判断p 是否达到m1(=400)的值,若达到,p=m0(=0); 从头开始。
如此400 幅动作不同的图片在屏幕上循环显示,一个活生生的人物就出现了。
3-3-4 OnOK()启动时钟
现在我们还有一个事,在OnOK()中写入时钟消息函数的启动程序。
void CMyDlg::OnOK() //确定键,[类向导中定义生成] { GetDlgItem(IDC_EDIT1)→ShowWindow(SW_HIDE);//隐藏文本框 CClientDC dc(this); //客户区设备环境 GetWindowRect(rect); //取当前窗口尺寸 BitBlt(dc.m_hDC,0,0,rect.Width(),rect.Height(),MemDC,0,0,SRCCOPY); //将背景拷贝到当前屏幕 //启动时钟 SetTimer(1,150,NULL); //设定时器150 毫秒 }
现在程序运行后,只要一按“确定键”,程序首先将背景拷贝到当前屏幕,然后启
动时钟,活动的人就在屏幕上出现了。
3-4 窗口、控件的基本操作
在程序的界面设计中,我们常需要对窗口和各种控件的大小、位置进行控制。
VC++对窗口、各种控件的一些基本操作命令如下:
GetDlgItem(IDC_EDIT1)→EnableWindow(FALSE); //控件失效、有效TRUE
GetDlgItem(IDC_LIST1)→ShowWindow(SW_HIDE); //控件隐藏、显示SW_SHOW
GetDlgItem(IDC_EDIT1)→SetWindowText(“cc”); //控件上显示cc 字串
GetDlgItem(IDCANCEL)→MoveWindow( x,y,w,h,TRUE); //控件大小、定位
SetDlgItemText(IDC_STATIC0, "cc"); //控件上显示cc 字串
GetDlgItemText(IDC_STATIC0, "cc"); //取控件上文字到字串cc
MoveWindow(x,y,w,h); //当前窗口定位
CenterWindow(); //当前窗口居中
例如:SetDlgItemText(IDC_EDIT1,cc) 将字符串cc 显示在编辑框“ IDC_EDIT1”上。
例如:MoveWindow(0,0,640,480); 当前窗口大小、定位。
例如:CenterWindow(); 当前窗口居中。
例如:GetDlgItem(IDOK)→MoveWindow(580,0,55,18,TRUE)
确定按钮控件“IDOK”的大小(w=55,h=18)、定位(x=580,y=0)。
为了给读者一个对程序的完整的认识 ,下面我们给出这一章的全部程序和注释。
在这一章我们编的程序只在“让我动吧Dlg.cpp”这一个文件中。
3-5 “让我动吧 Dlg.cpp”完整的程序和注释
3-5-1 全局定义
注,灰色部份为MFC 自动产生的。
#include "stdafx.h" #include "让我动吧.h" #include "让我动吧Dlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////// // 全局变量定义 ///////////////////////////////////////////// HBITMAP bit; //设备相关位图 HDC MemDC; //角色设备场景 int w,h; //图形尺寸 CString dir; //定义路径变量 CString cc; //公用变量 char appdir[256]; //当前目录 CRect rect; //定义窗口尺寸变量 int js; //角色[0 男,1 女] int fw; //方位[0 南1 西南2 西3 西北4 北5 东北6 东7 东南] int m0; //动画初值 int m1; //动画终值 int p; //当前图形序号 //////////////////////////////////////////// // 函数定义 //////////////////////////////////////////// BOOL getpic(CString cc,int p); //调图片到相关位图 BOOL loadbmp(CString cc); //调BMP 图片
3-5-2 程序的初始入口
CMyDlg::CMyDlg(CWnd* pParent /*=NULL*/)//[MFC 自动生成] : CDialog(CMyDlg::IDD, pParent) {⋯⋯} void CMyDlg::DoDataExchange(CDataExchange* pDX)//[MFC 自动生成] {⋯⋯} BEGIN_MESSAGE_MAP(CMyDlg, CDialog)//[MFC 自动生成] ⋯⋯ END_MESSAGE_MAP() BOOL CMyDlg::OnInitDialog()//对话框程序的初始入口,[MFC 自动产生] {CDialog::OnInitDialog(); SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here //A.显示说明信息 CString cc; cc="\r\n 这是《学VC 编游戏》的第二个示例:\r\n\r\n"; cc+=" 在这一章我们使用了以下知识、技术\r\n"; cc+="1.介绍计算机动画的基本知识和实现方法\r\n"; cc+="2.在VC++中建立时钟消息,使用时钟消息产生动画。\r\n"; cc+="3. 介绍格式化函数springf()的用法。由此动态地获取图形文件名。\r\n"; cc+="4.介绍在VC++中窗口、控件大小控制和定位的方法。\r\n"; SetDlgItemText(IDC_EDIT1,cc); //B.窗口定位 MoveWindow(0,0,640,480); //窗口定位 CenterWindow(); //居中窗口 GetDlgItem(IDOK)→MoveWindow(640-60,0,55,18,TRUE);//确定按钮控件位置 //C.建立图形环境 MemDC =CreateCompatibleDC(0); //创建设备场景 //D.设主角数据 js=0; //角色,0 号人物 fw=0; //方位,0 南 m0=js*400+fw*4; //初值,0 号人物首位置 m1=(js+1)*400-1; //终值,1 号人物首位置 p=m0; //当前图形序号 //E.设置路径 GetCurrentDirectory(256,appdir); //取当前目录 dir=appdir; if(dir.Right(8)=="运行程序") dir="图片/"; else dir="../运行程序/图片/"; //图片路径 //F.调入显示背景 loadbmp(dir+"地面.BMP"); //调背景图片 SelectObject(MemDC,bit); //调入位图关联到地图设备场景 //G.在背景上显示文字 SetTextColor(MemDC,RGB(255,255,255)); //设置地图设备场景字色 SetBkMode(MemDC,TRANSPARENT); //字为透明方式 cc="嘿嘿!我可以动了!!走走、跑跑,世间真美好!!!"; //设文字内容 TextOut(MemDC,150,100,cc,lstrlen(cc)); //在MemDC 显示文字 SetTextColor(MemDC,RGB(255,255,255)); //设置地图设备场景字色 cc="不对呀,弄个框框把我笼起? 放开我 !!!"; // TextOut(MemDC,150,220,cc,lstrlen(cc)); //在MemDC 显示文字 cc="BMP 图片本身就是矩形的,图片的底色是白色。"; TextOut(MemDC,151,250,cc,lstrlen(cc)); //在MemDC 显示文字 return TRUE; }
3-5-3 OnOK()确定键
void CMyDlg::OnPaint() //[MFC 自动生成] {⋯⋯ } HCURSOR CMyDlg::OnQueryDragIcon()//[MFC 自动生成] {⋯⋯ } void CMyDlg::OnOK() //确定键,[类向导中定义生成] {GetDlgItem(IDC_EDIT1)→ShowWindow(SW_HIDE);//隐藏文本框 CClientDC dc(this); //客户区设备环境 GetWindowRect(rect); //取当前窗口尺寸 BitBlt(dc.m_hDC,0,0,rect.Width(),rect.Height(),MemDC,0,0,SRCCOPY); //启动时钟 SetTimer(1,150,NULL); //设定时器150 毫秒 }
3-5-4 OnCancel()退出程序
void CMyDlg::OnCancel() //退出,[类向导中定义生成] { DeleteDC(MemDC); //删除暂存设备场景 DeleteObject(bit); //删除暂存设备相关位图 CDialog::OnCancel(); }
3-5-5 时钟函数
void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数,[类向导中定义生成] { CClientDC dc(this); //客户区设备环境 if(getpic("人",p)==FALSE) //调角色图片 {AfxMessageBox(cc+"没找到!");return;} SelectObject(MemDC,bit); //设备相关位图关联到暂存设备场景 BitBlt(dc.m_hDC,200,160,w,h,MemDC,0,0,SRCCOPY); //显示游戏角色 p++; //下一动作 if(p>m1) p=m0; //若动作完成,重复。 CDialog::OnTimer(nIDEvent); }
3-5-6 调图片到相关位图
//************************************************** // getpic(CString cc,int p) 调图片到相关位图 // 由p 得到将调的图形文件名。 // 在指定目录中调入图形到相关位图bit //************************************************** BOOL getpic(CString cc,int p)//调图片到相关位图 { char name[256]; SetCurrentDirectory(appdir); //置当前目录 sprintf(name,"%s%s/c%05d.bmp",dir,cc,p); //生成将调的图形文件名 loadbmp(name); //调BMP 图片 return TRUE; }
3-5-7 调BMP 图片
//************************************************** // loadbmp(CString cc)//调BMP 图片 // 调cc 指定的图形;取得的图形在设备相关位图bit 中 // 图形的宽、高存于全局变量w,h 中 //************************************************** BOOL loadbmp(CString cc)//调BMP 图片 { DeleteObject(bit); //删除上次的位图内存。 bit=(HBITMAP)LoadImage //调入cc 指定的图形 (AfxGetInstanceHandle(),// cc, //文件名 IMAGE_BITMAP, //位图方式 0, //图形宽 0, //图形高 LR_LOADFROMFILE|LR_CREATEDIBSECTION//方式 ); if(bit==NULL) return FALSE; //调图失败 DIBSECTION ds; // BITMAPINFOHEADER &bm = ds.dsBmih; // GetObject(bit,sizeof(ds),&ds); //取位图的信息→bminfo w = bm.biWidth; //得到位图宽度值 h = bm.biHeight; //得到位图高度值 return TRUE; }
3-6 有关程序运行时的目录
注意,在OnInitDialog()中设置路径一段程序的作用(行号是为了说明加上的)。
//E.设置路径 1 GetCurrentDirectory(256,appdir); //取当前目录 2 dir=appdir; 3 if(dir.Right(8)=="运行程序") 4 dir="图片/"; //图片路径 5 else dir="../运行程序/图片/"; //图片路径
第 1 行,是一个Windows 的API 函数调用,是将当前程序运行的目录取到字符数组
变量appdir 中。
第 2 行,将当前目录赋于字符串变量dir。因为字符串变量支持我们下面要用到的
一些灵活的操作。
第 3 行,如果dir 后边8 个字符(4 个汉字)是“运行程序”,
则第4 行dir="图片/";
否则第5 行dir="../运行程序/图片/";
第3~5 行算法的意义是:我们整个教学范例的运行程序,都集中放在与各个范例
源程序同等的目录“ 运行程序”下的,而所有动画图片都在目录“运行程序”的下级
目录“图片”下;即在与源程序同级目录“运行程序/图片/”下的。(见图3-3)
图3-3
当程序直接在VC 中编译运行时,当前目录为范例源程序目录( 如本例是“ 02.让我
动吧”这个目录)。按第3 行判断不是“运行程序”目录,所以取图片的目录就应该是
dir="../运行程序/图片/",即上一级目录下的“运行程序/图片/”。
如果在“ 运行程序”下执行程序“02.让我动吧.exe”;当前目录就是"运行程序",所
以取图片的目录就应该是dir="图片/"。即本级目录下的"图片/"。
这样做的作用是,无论是在VC 中直接编译运行程序,还是在目录“运行程序”下
运行程序,都可以准确地取图片所在的目录。
好了,现在可以编译运行这个程序了。
3-7 流程图
程序流程主要分三块:程序开始执行时的初始化, 按键后设置参数和启动时钟函
数,由时钟函数和调图形函数构成的程序主循环。注意,我们下面对游戏的进一步编
程主要是在这个主循环中进行的。
图3-4 主流程图
具体的程序请看本章实例程序:“让我动吧”。
3-8 小结
在这一章里我们学了以下知识和方法。
1.介绍计算机动画的基本知识和实现方法
2.在VC++中建立时钟消息,使用时钟消息产生动画。
3.介绍格式化函数sprintf()的用法。由此动态地获取图形文件名。
4.介绍在VC++中窗口、控件大小控制和定位的方法。
5.程序运行时的目录定位方法。