- 易迪拓培训,专注于微波、射频、天线设计工程师的培养
LabWindows/CVI虚拟仪器: 打蜜蜂
4.5 打蜜蜂
4.5.1 打蜜蜂设计方法
20 世纪80 年代开始流行电视游戏,打蜜蜂游戏是其中一款非常有特点的小游戏,相信很多70 后的人都有同图4-12 运行效果图感。利用LabWindows/CVI 开发一个简单的打蜜蜂游戏,设置好自定义的蜜蜂、飞机、炸弹、子弹、背景、音效等,按下上下左右键(↑、↓、←、→)移动飞机,按下“a”键发射子弹,一场生动的游戏便展现在眼前。
需要注意的是,在LabWindows/CVI 中,设置Picture 控件背景透明,控件中的图片并非透明,若要将图片与背景融合,可以将图片转换为图标(*.ico)格式文件,并设置图标背景透明。本例程所有图片资源与音效文件均放在同一目录下。
背景透明图标制作过程并不复杂,可将现有的一些图片资源直接转换为图标文件,并将其背景设置为透明即可。PictureToIcon 能将图片或屏幕的一部分转化为Icon 图标,调整图标大小以及从资源库中提取Icon,支持BMP 、JPEG 、GIF 、CUR、WMF 等格式转换。通过PictureToIcon 工具,可以方便地创建、编辑、载入和保存图标,并可设置图标透明度,甚至可以从.exe、.dll、.scr 和.ocx 文件中提取图标;支持16 位、24 位和32 位真彩图标,其最新版本支持图标改变大小从1×1 到256×256 。
在图标设计文件夹下找到PictureToIcon.exe 文件,双击打开该程序,如图4-13 所示。点击载入图标按钮,弹出打开对话框,选择需要转换的图像文件,如打开bee.bmp 文件,如图4-14 所示。
图4-13 PictureToIcon 图标转换工具
图4-14 打开对话框
在对话框中点击打开按钮,弹出图标编辑对话框,如图4-15 所示。在图中选择设置透明色复选框,点击确定按钮,弹出请确认对话框,点击是按钮即可,如图4-16 所示。
图4-15 图标编辑对话框
图4-16 请确认对话框
加到主窗口后,在右侧显示获得的透明图标文件,如图4-17 所示,点击保存图标按钮进行保存。至此,一个背景透明的蜜蜂图标就做好了,依次将飞机、炸弹、子弹等做成透明图标,以备程序中调用。
4-17 透明图标
4.5.2 打蜜蜂程序设计
(1)面板设计
编写一个打蜜蜂游戏,游戏一开始,蜜蜂开始移动,并每隔一段时间发射一颗炸弹,如果枪(飞机)被炸弹击中,则显示击中画面,并弹出游戏结束面板,点击重新开始则继续游戏,点击退出结束游戏。如果枪(飞机)发射的子弹击中蜜蜂,同样显示击中画面,弹出游戏结束面板。面板设计如图4-18 所示,面板中主要控件属性设置如表4-9 所示。
(a)打蜜蜂面板
(b)游戏结束面板
图4-18打蜜蜂游戏面板
表4-9控件属性设置表
常量名 |
控件类型 |
控件的主要属性 |
PANEL |
Panel |
标题:打蜜蜂回调函数:PanelCB |
PICTURE |
Picture |
Load Image:backgraph.jpg |
PICTURE_BEE |
Picture |
Load Image:bee.ico |
PICTURE_SHOOTBEE |
Picture |
Load Image:shootbee.ico |
PICTURE_GUN |
Picture |
Load Image:gun.ico |
PICTURE_SHOOTGUN |
Picture |
Load Image:shootgun.ico |
PICTURE_BOOM |
Picture |
Load Image:boom.ico |
PICTURE_BULLET |
Picture |
Load Image:bullet.ico |
TIMER |
Timer |
标题:timer 回调函数:timer Interval:0.010 |
TIMER_BEE |
Timer |
标题:beerun 回调函数:beerun Interval:0.100 |
PANEL_POP |
Panel |
标题:游戏结束回调函数:PanelCB |
TEXTMSG |
Text Message |
默认值:游戏结束!请重新开始 |
CMD_RESTART |
Command Button |
标题:重新开始回调函数:restart |
CMD_QUIT |
Command Button |
标题:退出回调函数:quit |
(2)程序源代码
//头文件声明
#include "windows.h"
#include "asynctmr.h"
#include "toolbox.h"
#include "mmsystem.h"
#include "bee.h"
static int panelHandle;
//声明异步定时器句柄
static int timerkey;
//声明游戏结束面板句柄
static int pophandle;
//定义发射子弹可见为1,不可见为0
static int shootflag = 0;
//定义蜜蜂炸弹可见为1,不可见为0
static int boomflag = 0;
//定义蜜蜂炸弹发射间隔
static int boomtime = 0;
//定义蜜蜂水平移动单位
static int hh = 3;
//定义蜜蜂垂直移动单位
static int vv = 2;
//定义枪(飞机)、子弹、蜜蜂、炸弹等位置信息
Rect gun;
Rect bullet;
Rect bee;
Rect boom;
Rect shootbee;
Rect panelpp;
//自定义函数
void porperty (void);
void position (void);
void popmessage (void);
int CVICALLBACK keystroke(int reserved, int timerId, int event, void *callbackData, int eventData1, int
eventData2);
//主函数
int main (int argc, char *argv[])
{
if (InitCVIRTE (0, argv, 0) == 0)
return –1;
if ((panelHandle = LoadPanel (0, "bee.uir", PANEL)) < 0)
return –1;
//设置枪(飞机)、子弹、蜜蜂、炸弹等控件背景透明
porperty ();
//装载异步定时器
timerkey = NewAsyncTimer (0.001, –1, 1, keystroke, 0);
DisplayPanel (panelHandle);
RunUserInterface ();
DiscardPanel (panelHandle);
//释放异步定时器资源
DiscardAsyncTimer (timerkey);
return 0;
}
//面板回调函数
int CVICALLBACK panelCB (int panel, int event, void *callbackData,
int eventData1, int eventData2)
{
int character;
int vk;
switch (event)
{
case EVENT_CLOSE:
QuitUserInterface (0);
break;
//面板尺寸改变时响应事件
case EVENT_PANEL_SIZE:
// 获得枪(飞机)、子弹、蜜蜂、炸弹等控件位置
position();
// 重新定位枪(飞机)控件Top 属性
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_TOP, panelpp.height–50);
break;
}
return 0;
}
//底端枪(飞机)子弹发射路径以及击中蜜蜂效果
int CVICALLBACK timer (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event)
case EVENT_TIMER_TICK:
// 获得枪(飞机)、子弹、蜜蜂、炸弹等控件位置
position();
// 子弹向上发射路径
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_TOP, bullet.top–5);
// 当子弹超过屏幕顶端后消失
if (bullet.top < -bullet.height)
{
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_VISIBLE, 0);
shootflag = 0;
}
// 子弹击中蜜蜂的效果
if ((bullet.left+bullet.width>=bee.left) && (bullet.left<=bee.left+bee.width) && (bullet.top<=bee.
top+bee.height) && (bullet.top+bullet.height>=bee.top) && shootflag)
{
// 用击中后的图片替换原有图片
SetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTBEE, ATTR_LEFT, bee.left);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTBEE, ATTR_TOP, bee.top);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BEE, ATTR_VISIBLE, 0);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTBEE, ATTR_VISIBLE, 1);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_VISIBLE, 0);
// 游戏停止
SetCtrlAttribute (panelHandle, PANEL_TIMER, ATTR_ENABLED, 0);
SetCtrlAttribute (panelHandle, PANEL_TIMER_BEE, ATTR_ENABLED, 0);
SetAsyncTimerAttribute (timerkey, ASYNC_ATTR_ENABLED, 0);
// 播放击中音乐
sndPlaySound("shootbee.wav", SND_SYNC);
// 弹出游戏结束面板
popmessage();
}
break;
}
return 0;
}
//顶端蜜蜂运动路径以及蜜蜂炸弹击中枪(飞机)效果
int CVICALLBACK beerun (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event)
{
case EVENT_TIMER_TICK:
// 间隔一定时间,蜜蜂发射炸弹,炸弹垂直下落
boomtime++;
if (boomtime>=50)
{
boomtime = 0;
}
if (boomtime == 0 && (!boomflag))
{
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_VISIBLE, 1);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_LEFT, bee.left);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_TOP, bee.top+bee.height);
sndPlaySound("boom.wav", SND_SYNC);
boomflag = 1;
}
else
{
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_TOP, boom.top+20);
}
// 重新获得炸弹、蜜蜂等位置信息
position();
if (boom.top>=panelpp.height)
{
boomflag = 0;
}
// 蜜蜂到达边界自动反弹,并重新计算位置
if (bee.left <= 10 || bee.left + bee.width >= panelpp.width – 10)
{
hh = -hh;
}
if (bee.top <= 10 || gun.top - bee.top <= 100)
{
vv = -vv;
}
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BEE, ATTR_LEFT, bee.left+hh*(int)Random
(0, 10));
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BEE, ATTR_TOP, bee.top+vv*(int)Random
(0, 10));
// 蜜蜂炸弹击中枪(飞机)效果
if ((boom.left+boom.width>=gun.left) && (boom.left<=gun.left+gun.width) && (boom.top+
boom.height–30>=gun.top) && (boom.top+boom.height<=gun.top+gun.height) && (boomflag))
{
// 用击中后的图片替换原有图片
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_VISIBLE, 0);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_VISIBLE, 0);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTGUN, ATTR_VISIBLE, 1);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTGUN, ATTR_LEFT, gun.left);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTGUN, ATTR_TOP, gun.top);
// 游戏停止
SetCtrlAttribute (panelHandle, PANEL_TIMER, ATTR_ENABLED, 0);
SetCtrlAttribute (panelHandle, PANEL_TIMER_BEE, ATTR_ENABLED, 0);
sndPlaySound("dead.wav", SND_SYNC);
popmessage();
}
break;
}
return 0;
}
//退出按钮
int CVICALLBACK quit (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event)
{
case EVENT_COMMIT:
DiscardPanel (pophandle);
QuitUserInterface (0);
break;
}
return 0;
}
//重新开始按钮
int CVICALLBACK restart (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
switch (event)
{
case EVENT_COMMIT:
// 参数重置
shootflag = 0;
boomflag = 0;
boomtime = 0;
porperty();
// 开始游戏
SetCtrlAttribute (panelHandle, PANEL_TIMER, ATTR_ENABLED, 1);
SetCtrlAttribute (panelHandle, PANEL_TIMER_BEE, ATTR_ENABLED, 1);
SetAsyncTimerAttribute (timerkey, ASYNC_ATTR_ENABLED, 1);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_VISIBLE, 1);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BEE, ATTR_VISIBLE, 1);
DiscardPanel (pophandle);
break;
}
return 0;
}
//设置枪(飞机)、子弹、蜜蜂、炸弹等控件背景透明
void porperty (void)
{
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BEE, ATTR_PICT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_PICT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTBEE, ATTR_PICT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_PICT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_PICT_BGCOLOR, VAL_TRANSPARENT);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTGUN, ATTR_PICT_BGCOLOR, VAL_TRANS-
PARENT);
//一些控件初始态不可见
SetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTBEE, ATTR_VISIBLE, 0);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_VISIBLE, 0);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_VISIBLE, 0);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTGUN, ATTR_VISIBLE, 0);
}
//获得枪(飞机)、子弹、蜜蜂、炸弹等控件位置
void position (void)
{
//枪(飞机)
GetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_LEFT, &gun.left);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_TOP, &gun.top);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_HEIGHT, &gun.height);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_WIDTH, &gun.width);
//子弹
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_LEFT, &bullet.left);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_TOP, &bullet.top);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_HEIGHT, &bullet.height);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_WIDTH, &bullet.width);
//蜜蜂
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BEE, ATTR_LEFT, &bee.left);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BEE, ATTR_TOP, &bee.top);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BEE, ATTR_HEIGHT, &bee.height);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BEE, ATTR_WIDTH, &bee.width);
//击中蜜蜂
GetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTBEE, ATTR_LEFT, &shootbee.left);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTBEE, ATTR_TOP, &shootbee.top);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTBEE, ATTR_HEIGHT, &shootbee.height);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_SHOOTBEE, ATTR_WIDTH, &shootbee.width);
//炸弹
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_LEFT, &boom.left);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_TOP, &boom.top);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_HEIGHT, &boom.height);
GetCtrlAttribute (panelHandle, PANEL_PICTURE_BOOM, ATTR_WIDTH, &boom.width);
//面板
GetPanelAttribute (panelHandle, ATTR_HEIGHT, &panelpp.height);
GetPanelAttribute (panelHandle, ATTR_WIDTH, &panelpp.width);
}
void popmessage (void)
{
pophandle = LoadPanel (0, "bee.uir", PANEL_POP);
//以模态对话框形式显示游戏结束面板
InstallPopup (pophandle);
}
//异步定时器回调函数
int CVICALLBACK keystroke(int reserved, int timerId, int event, void *callbackData, int eventData1, int
eventData2)
{
int left;
int right;
int up;
int down;
int a;
switch (event)
{
case EVENT_TIMER_TICK:
// 获得虚拟键↑↓←→的按键状态,以及是否按下a 键
right = GetAsyncKeyState(VK_RIGHT);
left = GetAsyncKeyState(VK_LEFT);
up = GetAsyncKeyState(VK_UP);
down = GetAsyncKeyState(VK_DOWN);
a = GetAsyncKeyState('A');
// 向左移动
if (left != 0)
{
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_LEFT, gun.left–2);
}
// 向右移动
if (right != 0)
{
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_LEFT, gun.left+2);
}
// 向上移动
if (up != 0)
{
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_TOP, gun.top–2);
}
// 向下移动
if (down != 0)
{
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_TOP, gun.top+2);
}
// 按下a 键,发射子弹
if (a && (!shootflag))
{
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_LEFT, gun.left+gun.
width/2–8);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_TOP, gun.top);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_VISIBLE, 1);
sndPlaySound("bullet.wav", SND_SYNC);
shootflag = 1;
}
break;
}
return 0;
}
(3) 程序注释
① sndPlaySound 函数播放指定音频文件。此函数为PlaySound 函数的子集,主要是为保持向后兼容而设置。文件声明在mmsystem.h 中,导入库为winmm.lib 。函数原型为:
BOOL sndPlaySound(LPCSTR lpszSound, UINT fuSound);
lpszSound :指定播放音频文件名,该参数可以是WAVE 文件名或是在系统注册表WIN.INI
中定义的系统事件音频文件。如果该参数设置为NULL 则停止正在播放的音频文件。fuSound:播放标志。
播放标志说明如表4-10 所示。
表4-10 播放标志说明
常量名 |
常量值 |
说明 |
SND_ASYNC |
0x0001 |
用异步方式播放音频文件 |
SND_LOOP |
0x0008 |
重复播放音频文件,与SND_ASYNC 标志同时使用 |
SND_MEMORY |
0x0004 |
播放载入到内存中的音频文件 |
SND_NODEFAULT |
0x0002 |
不播放缺省音频文件 |
SND_NOSTOP |
0x0010 |
立即播放音频文件 |
SND_SYNC |
0x0000 |
同步播放音频文件 |
返回值:成功返回True ,否则返回False 。
② GetAsyncKeyState 函数判断按键是处于按下状态,还是处于弹起状态。函数原型为:
SHORT GetAsyncKeyState(int vKey);
vKey:虚拟键值。
返回值:返回指定键是否处于按下状态,若为1,则键被按下,否则为弹起状态。
③ GetKeyPressEventVirtualKey 函数当回调函数响应EVENT_KEYPRESS 事件时,调用GetKeyPressEventVirtualKey 函数获得虚拟键值。如果回调函数eventData1 参数包含字符而非虚拟键时,函数返回0 值。函数原型为:
int GetKeyPressEventVirtualKey (int eventData2); eventData2 :面板或控件回调函数的eventData2 参数。
返回值:返回虚拟键值,如有非虚拟键按下,返回0。
④ GetKeyPressEventCharacter 函数当回调函数响应EVENT_KEYPRESS 事件时,调用GetKeyPressEventCharacter 函数获得按键字符。如果回调函数eventData1 参数包含虚拟键而非字符时,函数返回0 值。函数原型为:
int GetKeyPressEventCharacter (int eventData2);
eventData2 :面板或控件回调函数eventData2 参数。
返回值:返回字符值,如有非字符按下,返回0。
⑤ InstallPopup 函数显示模态对话框。函数原型为:
int InstallPopup (int Panel_Handle);
一般来说,在Windows 应用程序中,对话框分为模态对话框和非模态对话框两种。二者的区别在于当对话框打开时,是否允许用户进行其他对象的操作。模态对话框(Modal Dialogue Box, 又称模式对话框),是指在用户要操作对话框以外的窗口时,必须首先响应该对话框,如单击确定或取消按钮等关闭对话框。非模态对话框(Nonmodal Dialogue Box ,又称无模式对话框),与模态对话框不同,当用户打开非模态对话框时,依
然可以操作其他窗口。
⑥ 按键处理
在本例程中,按键处理采用异步定时器查询方式,需要将定时器的触发时间设置极短,这样会占用大量系统资源。另外,也可以停止异步定时器,采用LabWindows/CVI 事件响应机制,在面板回调函数panelCB 中添加EVENT_KEYPRESS 事件代码,程序如下:
int CVICALLBACK panelCB (int panel, int event, void *callbackData,
int eventData1, int eventData2)
{
int character;
int vk;
switch (event)
{
case EVENT_KEYPRESS:
// 获得虚拟键↑↓←→的按键状态,以及是否按下a 键
vk = GetKeyPressEventVirtualKey (eventData2);
character = GetKeyPressEventCharacter (eventData2);
switch (vk)
{
case VAL_LEFT_ARROW_VKEY:
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_LEFT, gun.left–10);
break;
case VAL_RIGHT_ARROW_VKEY:
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_LEFT, gun.left+10);
break;
case VAL_UP_ARROW_VKEY:
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_TOP, gun.top–10);
break;
case VAL_DOWN_ARROW_VKEY:
SetCtrlAttribute (panelHandle, PANEL_PICTURE_GUN, ATTR_TOP, gun.top+10);
break;
}
if (('a' == tolower (character)) && (!shootflag))
{
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_LEFT, gun.left+
gun.width/2–8);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_TOP, gun.top);
SetCtrlAttribute (panelHandle, PANEL_PICTURE_BULLET, ATTR_VISIBLE, 1);
sndPlaySound("bullet.wav", SND_SYNC);
shootflag = 1;
}
以上程序中,获得虚拟键值和字符可以用GetKeyPressEventVirtualKey 和GetKeyPress
EventCharacter 函数,也可以采用EVENT_KEYPRESS 事件的eventData1 参数获得相关数据,即将以上两个函数用以下代码代替。
vk = eventData1 & 0xFF00;
character = eventData1 & 0x00FF;
(4)运行效果图
点击工具栏中的Debug Project 按钮,程序开始运行,其效果如图4-19 所示。
图4-19 运行效果图
上一篇:LabWindows/CVI虚拟仪器: 打字练习
下一篇:LabWindows/CVI虚拟仪器游戏设计之: 下雪场景