VC++打地鼠游戏

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

第五章老鼠和滚球
1.1.实现思想
老鼠和滚球游戏,是我根据自己的意愿,是为了我将要说明的下面有关内容而设置的。

由于没有经过具体实践,我并不能保证其趣味性和吸引力,我们学习的是它实现过程中的一些思想。

希望对读者有所帮助。

其实,这种游戏,在街头电子游戏中是常见的。

但由于电脑的渐渐普及,大规模游戏的迅速出现,已经不适合玩者的需求了。

但是,我们可以以小见大,在学习小游戏的基础上,慢慢培养设计大游戏的一些方法和技巧。

关于游戏的角色:
老鼠,是游戏的主角,它有三条生命,它为了生存,必须能够逃脱灾难性的局面(被滚球撞到),它在经过所谓的适者生存的淘汰之后,学会了使用子弹;而且是一种能够消灭滚球的子弹,以达到防身的目的。

每打中一个球得一分,每被撞一下失去一条生命。

滚球,一共有50个,但为了维持生态平衡,最多只能出来六个。

滚球,是为了消灭老鼠而存在的,它将会在现代科技的伪随机函数中不定向地出现。

红心,生命的象征,只要你能碰到它,你就幸运了,因为由此你会得到一条生命。

当然,生存需要竞争,需要你自己去创造;只要你在消灭了很多滚球之后,你才有可能取得。

正因为这个原因,我把它安排在滚球出现的地方。

关于游戏的实现:
游戏,经常都是全屏的,但是由于我们的目的不仅仅在于游戏本身,我们的宗旨是利用游戏的趣味性和吸引力,来增强读者学习程序语言的信心。

所以,在前面我们都没有用到全屏显示,我们不需要。

另外说明:本游戏的全屏只适应于800*600的分辨率。

全屏,是这个游戏的一点要学习的。

多位图的移动,是本游戏要学习的第二个内容,上面的最多只有两个位图可以移动,这是由于游戏本身决定的。

当然,这个游戏的多位图移动,也是游戏本身决定的,但却是我之所以选择它为第五章的原因。

当然,我们没有那么多的手去操作键盘,去移动位图。

我们必须设置我们的程序,让程序自己去执行,去移动,去显示计算机的优越性。

这个游戏,比起上面来,它是一个更加复杂的社会,它不再只是玩者一个人充当角色。

所以,我们新建了各自的类。

新建工程5_1,为单文档默认设置。

2.2.制作位图
既然游戏是复杂的,它就需要很多位图,但由于我们在前面已经用了很多位图,我们这里就不多说了。

只是说明一下位图的数量和大小。

背景位图:800*600 1张IDB_BITMAP1
老鼠位图:50*50 4张IDB_BITMAP2
(两只老鼠两个方向)IDB_BITMAP3
IDB_BITMAP7
IDB_BITMAP8
滚球位图:50*50 1张IDB_BITMAP4
红心位图:50*50 1张IDB_BITMAP11
子弹位图:50*50 4张IDB_BITMAP5
(两种颜色两个方向)IDB_BITMAP6
IDB_BITMAP9
IDB_BITMAP10
3.3.变量和函数
老鼠:
它有位置,分数,方向,生命力,子弹和它自己。

其中它自己的图像及它射出来的子弹都有前后方向。

我们必须为它定义一个类,以让两个老鼠共用。

新建类:CMouse
添加变量如下:
CPoint point;//位置
CBitmap bitmapa;//向后图像
CBitmap bitmapb;//向前图像
CBitmap shota;//向后子弹
CBitmap shotb;//向前子弹
int score;//分数
short direction;//方向
short lifes;//生命
滚球:
它也有位置,也有它自己的图像。

由于一共有五十个球,最多会显示六个球,我们也定义它为一共类。

新建类:CBall
添加变量如下:
CPoint point;
CBitmap bitmap;//由于对称,我们只要一个位图
游戏:
由于它的复杂性,程序,由于它的复杂性,为了不使它们更加复杂,我们也为游戏的实现本身建立一个类。

新建类:CGame
添加变量如下:
CBall ball[6];//六个滚球
short ballnum;//滚球数:50
CMouse m1,m2;//两只老鼠
int yy[16][12];//屏幕数组
CBitmap shot;//子弹位图
bool heart1,heart2;//两个红心是否显示
CBitmap heart;//红心位图
既然是游戏本身,它必定要实现很多功能,我们添加如下函数:
void GetHeart();//老鼠得到红心
void KillBall();//老鼠打中滚球
void KillMouse();//滚球杀死老鼠
void Draw(CDC*pDC);//画界面
void DrawScore(CDC*pDC);//画分数
void BallMove();//使球滚动
最后,到了主程序,为了实现全屏,我们在CMainFrame 中添加如下变量和函数:CRect m_FullScreenRect;//全屏显示时的窗口位置
void OnFullScreen();//全屏显示
在CMy5_1View中添加如下变量和函数:
CGame game;//游戏对象
CBitmap cloud;//背景云图
OnTimer(UINT nIDEvent)
OnCreate(LPCREATESTRUCT lpCreateStruct)
OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
4.4.具体实现
全屏显示:
首先,我们必须使单文档以全屏形式出现,并设置为不能改动大小,没有工具栏和状态栏,没有菜单。

添加OnFullScreen()函数如下:
void CMainFrame::OnFullScreen()
{
// TODO: Add your command handler code here
CRect WindowRect;
GetWindowRect(&WindowRect);
CRect ClientRect;
RepositionBars(0,0xffff,AFX_IDW_PANE_FIRST,reposQuery,&ClientRect);
ClientToScreen(&ClientRect);
//获取屏幕的分辨率
int nFullWidth=GetSystemMetrics(SM_CXSCREEN);
int nFullHeight=GetSystemMetrics(SM_CYSCREEN);
m_FullScreenRect.left=WindowRect.left-ClientRect.left;
m_FullScreenRect.top=WindowRect.top-ClientRect.top;
m_FullScreenRect.right=WindowRect.right-ClientRect.right+nFullWidth;
m_FullScreenRect.bottom=WindowRect.bottom-ClientRect.bottom+nFullHeight;
//进入全屏显示状态
WINDOWPLACEMENT wndpl;
wndpl.rcNormalPosition=m_FullScreenRect;
SetWindowPlacement(&wndpl);
}
再改变函数OnCreate(LPCREATESTRUCT lpCreateStruct)和PreCreateWindow(CREATESTRUCT& cs)如下:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
OnFullScreen();//全屏显示
return 0;
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
cs.style=WS_SYSMENU;
cs.dwExStyle=WS_EX_TOPMOST; //窗口置于最前
return TRUE;
}
这样,全屏就显示出来了。

构造函数:
接着,就要把位图显示出来,在CMy5_1View()函数添加语句如下:
CMy5_1View::CMy5_1View()
{
// TODO: add construction code here
cloud.LoadBitmap(IDB_BITMAP1);
}
显示背景:
在OnDraw(CDC* pDC)函数里面添加语句如下:
void CMy5_1View::OnDraw(CDC* pDC)
{
CMy5_1Doc* pDoc = GetDocument();
ASSERT_V ALID(pDoc);
// TODO: add draw code for native data here
CDC Dc;
if(Dc.CreateCompatibleDC(pDC)==FALSE)
AfxMessageBox("Can't create DC");
//在不同位置显示位图
Dc.SelectObject(cloud);
pDC->StretchBlt(0,0,800,600,&Dc,0,0,800,600,SRCCOPY);
game.Draw(pDC);
}
由于真正的画图是在类Game里面实现的,我们添加game.Draw(pDC)函数。

但是,为了防止某些变量的赋值和初始化问题,我们先添加CGame()函数的语句如下:
构造函数:
CGame::CGame()
{
int i,j;
heart.LoadBitmap(IDB_BITMAP11);
m1.bitmapb.LoadBitmap(IDB_BITMAP2);
m2.bitmapb.LoadBitmap(IDB_BITMAP3);
m1.bitmapa.LoadBitmap(IDB_BITMAP7);
m2.bitmapa.LoadBitmap(IDB_BITMAP8);
m1.shotb.LoadBitmap(IDB_BITMAP5);
m2.shotb.LoadBitmap(IDB_BITMAP9);
m1.shota.LoadBitmap(IDB_BITMAP6);
m2.shota.LoadBitmap(IDB_BITMAP10);
//生命数量为3
m1.lifes=3;
m2.lifes=3;
//红心显示
heart1=true;
heart2=true;
//滚球数量50
ballnum=50;
for(i=0;i<6;i++)
ball[i].bitmap.LoadBitmap(IDB_BITMAP4);
//滚球开始出现的位置
for(i=0;i<3;i++)
{
ball[i].point.x=4+i;
ball[i].point.y=0;
ball[i+3].point.x=9+i;
ball[i+3].point.y=0;
}
//老鼠开始出现的位置
m1.point.x=0;
m1.point.y=10;
m2.point.x=15;
m2.point.y=10;
//老鼠开始的方向
m1.direction=1;
m2.direction=-1;
//清理背景数组,或1或0
for(i=0;i<16;i++)
for(j=0;j<11;j++)
yy[i][j]=0;
yy[4][1]=1;
yy[5][1]=1;
yy[6][1]=1;
yy[9][1]=1;
yy[10][1]=1;
yy[11][1]=1;
for(i=0;i<16;i++)
yy[i][3]=1;
yy[5][3]=0;
yy[10][3]=0;
for(i=5;i<11;i++)
yy[i][5]=1;
for(i=4;i<12;i++)
yy[i][7]=1;
for(i=0;i<5;i++)
yy[i][9]=1;
for(i=11;i<16;i++)
yy[i][9]=1;
for(i=0;i<16;i++)
yy[i][11]=1;
}
显示前景:
再添加如下函数:
void CGame::Draw(CDC *pDC)
{
int i,j;
CDC Dc;
if(Dc.CreateCompatibleDC(pDC)==FALSE)
AfxMessageBox("Can't create DC");
//在不同位置显示位图
//显示老鼠位图
if(m2.lifes>0)
{
Dc.SelectObject(m2.direction==1?m2.bitmapb:m2.bitmapa);
pDC->BitBlt(m2.point.x*50,m2.point.y*50,50,50,&Dc,0,0,SRCCOPY);
}
if(m1.lifes>0)
{
Dc.SelectObject(m1.direction==1?m1.bitmapb:m1.bitmapa);
pDC->BitBlt(m1.point.x*50,m1.point.y*50,50,50,&Dc,0,0,SRCCOPY);
}
//画滚球
//大于六个的画六个
j=ballnum-m1.score;
j=j-m2.score;
if(j>6)
j=6;
for(i=0;i<j;i++)
{
Dc.SelectObject(ball[i].bitmap);
pDC->BitBlt(ball[i].point.x*50,ball[i].point.y*50,50,50,&Dc,0,0,SRCCOPY);
}
//检查界面数组
//2表示m2的子弹,3表示m1的子弹
for(i=0;i<16;i++)
for(j=0;j<12;j++)
{
if(yy[i][j]==2)
{
Dc.SelectObject(m2.direction==1?m2.shotb:m2.shota);
pDC->BitBlt(i*50,j*50,50,50,&Dc,0,0,SRCCOPY);
}
if(yy[i][j]==3)
{
Dc.SelectObject(m1.direction==1?m1.shotb:m1.shota);
pDC->BitBlt(i*50,j*50,50,50,&Dc,0,0,SRCCOPY);
}
}
//画老鼠的生命数量
for(i=0;i<m1.lifes;i++)
{
Dc.SelectObject(m1.bitmapb);
pDC->StretchBlt(i*40+10,0,30,30,&Dc,0,0,50,50,SRCCOPY);
}
for(i=0;i<m2.lifes;i++)
{
Dc.SelectObject(m2.bitmapa);
pDC->StretchBlt(760-i*40,0,30,30,&Dc,0,0,50,50,SRCCOPY);
}
//显示分数,函数在下面!
DrawScore(pDC);
//显示红心
Dc.SelectObject(heart);
if(heart1)
pDC->BitBlt(250,0,50,50,&Dc,0,0,SRCCOPY);
if(heart2)
pDC->BitBlt(500,0,50,50,&Dc,0,0,SRCCOPY);
}
显示分数信息:
由于这个函数在前面已经出现,只是添加如下:
void CGame::DrawScore(CDC *pDC)
{
int nOldDC=pDC->SaveDC();
//设置字体
CFont font;
if(0==font.CreatePointFont(250,"Comic Sans MS"))
{
AfxMessageBox("Can't Create Font");
}
pDC->SelectObject(&font);
//设置字体颜色及其背景颜色
CString str;
pDC->SetTextColor(RGB(0,10,244));
pDC->SetBkColor(RGB(0,255,0));
//输出数字
str.Format("%d",m1.score);
if(m2.score>=0)
pDC->TextOut(40,28,str);
str.Format("%d",m2.score);
if(m2.score>=0)
pDC->TextOut(740,28,str);
pDC->RestoreDC(nOldDC);
}
滚球实现:
老鼠的移动是由我们操作的,我们后面再实现,而滚球是程序驱动的,它们是怎样实现的呢?添加如下函数:
void CGame::BallMove()
{
int i,j,k;
j=ballnum-m1.score;
j=j-m2.score;
if(j>6)
j=6;
for( i=0;i<j;i++)
{
//其中三个
if((i==1)||(i==3)||(i==5))
{
//向右移动
ball[i].point.x++;
//到了边界,在左边出现
if(ball[i].point.x>15)
ball[i].point.x=0;
//下面空,下掉
if(yy[ball[i].point.x][ball[i].point.y+1]!=1)
for(k=ball[i].point.y;k<12;k++)
if(yy[ball[i].point.x][k]==1)
{
ball[i].point.y=k-1;
break;
}
//到了最底面
if((ball[i].point.x==15)&&(ball[i].point.y==10))
{
//在上面出现
if(i==1)
{
ball[i].point.x=10;
ball[i].point.y=-1;
}
else
{
ball[i].point.x=5;
ball[i].point.y=-1;
}
}
}
//同上
else if((i==2)||(i==4)||(i==0))
{
ball[i].point.x--;
if(ball[i].point.x<0)
ball[i].point.x=15;
if(yy[ball[i].point.x][ball[i].point.y+1]!=1)
for(k=ball[i].point.y;k<12;k++)
if(yy[ball[i].point.x][k]==1)
{
ball[i].point.y=k-1;
break;
}
if((ball[i].point.x==0)&&(ball[i].point.y==10))
{
if(i==2)
{
ball[i].point.x=5;
ball[i].point.y=0;
}
else
{
ball[i].point.x=10;
ball[i].point.y=0;
}
}
}
}
}
计时器函数:
那么,现在应该添加计数器函数了。

添加函数OnCreate(LPCREATESTRUCT lpCreateStruct)和OnTimer(UINT nIDEvent)如下:
int CMy5_1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
SetTimer(1,250,NULL);
return 0;
}
void CMy5_1View::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
//经常是否有以下事件发生
game.BallMove();
game.KillMouse();
game.KillBall();
game.GetHeart();
//重画
OnDraw(GetDC());
int i,j;
//如果有子弹,清除子弹
for(i=0;i<16;i++)
for(j=0;j<12;j++)
if(game.yy[i][j]==2||game.yy[i][j]==3)
game.yy[i][j]=0;
CView::OnTimer(nIDEvent);
}
其中的game.KillMouse(),game.KillBall(),game.GetHeart()函数分别表示是否有老鼠被杀,是否有球被打,是否有红心被拿。

分别介绍如下:
得到红心:
void CGame::GetHeart()
{
//是哪个老鼠是得到了哪个红心
//第一只老鼠
if((m1.point.x==5)&&(m1.point.y==0))
//第一只红心
if(heart1)
{
m1.lifes++;
heart1=false;
}
if((m2.point.x==5)&&(m2.point.y==0))
if(heart1)
{
m2.lifes++;
heart1=false;
}
if((m1.point.x==10)&&(m1.point.y==0))
if(heart2)
{
m1.lifes++;
heart2=false;
}
if((m2.point.x==10)&&(m2.point.y==0))
if(heart2)
{
m2.lifes++;
heart2=false;
}
}
射中滚球:
void CGame::KillBall()
{
for(int i=0;i<6;i++)
//m2的子弹
if(yy[ball[i].point.x][ball[i].point.y]==2)
{
//m2的分数添加
m2.score++;
//滚球消失
ball[i].point.x=5;
ball[i].point.y=-1;
}
//同上
else if(yy[ball[i].point.x][ball[i].point.y]==3)
{
m1.score++;
ball[i].point.x=10;
ball[i].point.y=-1;
}
}
杀死老鼠:
void CGame::KillMouse()
{
int i;
for(i=0;i<6;i++)
{
//老鼠的位置和滚球的位置
if(m1.point==ball[i].point)
{
//生命减少
m1.lifes--;
//老鼠重新出现
m1.point.x=7;
m1.point.y=6;
}
//同上
if(m2.point==ball[i].point)
{
m2.lifes--;
m2.point.x=8;
m2.point.y=6;
}
}
}
键盘操作:
最后,就剩下键盘操作函数了,实现如下:
void CMy5_1View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
// TODO: Add your message handler code here and/or call default
int i;
switch(nChar)
{
//子弹按钮
//数字键盘3
case VK_NUMPAD3:
//界面数组位置为2
game.yy[game.m2.point.x+game.m2.direction][game.m2.point.y]=2;
break;
case VK_LEFT:
//向左移动1
game.m2.point.x--;
//老鼠方向为左
game.m2.direction=-1;
//如果是边界,从右边出现
if(game.m2.point.x<0)
game.m2.point.x=15;
//如果下面空,下掉
if(game.yy[game.m2.point.x][game.m2.point.y+1]!=1)
for(i=game.m2.point.y;i<12;i++)
if(game.yy[game.m2.point.x][i]==1)
{
game.m2.point.y=i-1;
break;
}
break;
//道理同上
case VK_RIGHT:
game.m2.point.x++;
game.m2.direction=1;
if(game.m2.point.x>15)
game.m2.point.x=0;
if(game.yy[game.m2.point.x][game.m2.point.y]!=1)
for(i=game.m2.point.y;i<12;i++)
if(game.yy[game.m2.point.x][i]==1)
{
game.m2.point.y=i-1;
break;
}
break;
//上跳
case VK_UP:
//如果上面能站住,上移
if(game.yy[game.m2.point.x][game.m2.point.y-1]==1)
game.m2.point.y=game.m2.point.y-2;
//否则,如果前面能够站住,上移
else if(game.yy[game.m2.point.x+game.m2.direction][game.m2.point.y-1]==1) {
game.m2.point.x+=game.m2.direction;
game.m2.point.y=game.m2.point.y-2;
}
//否则,如果后面能够,上移
else if(game.yy[game.m2.point.x-game.m2.direction][game.m2.point.y-1]==1) {
game.m2.point.x-=game.m2.direction;
game.m2.point.y=game.m2.point.y-2;
game.m2.direction=-game.m2.direction;
}
break;
//下跳
case VK_DOWN:
//下掉
for(i=game.m2.point.y+2;i<12;i++)
if(game.yy[game.m2.point.x][i]==1)
{
game.m2.point.y=i-1;
break;
}
break;
//道理同上
case 75:
game.yy[game.m1.point.x+game.m1.direction][game.m1.point.y]=3;
break;
case 65:
game.m1.point.x--;
game.m1.direction=-1;
if(game.m1.point.x<0)
game.m1.point.x=15;
if(game.yy[game.m1.point.x][game.m1.point.y+1]!=1)
for(i=game.m1.point.y;i<12;i++)
if(game.yy[game.m1.point.x][i]==1)
{
game.m1.point.y=i-1;
break;
}
break;
case 68:
game.m1.point.x++;
game.m1.direction=1;
if(game.m1.point.x>15)
game.m1.point.x=0;
if(game.yy[game.m1.point.x][game.m1.point.y]!=1)
for(i=game.m1.point.y;i<12;i++)
if(game.yy[game.m1.point.x][i]==1)
{
game.m1.point.y=i-1;
break;
}
break;
case 87:
if(game.yy[game.m1.point.x][game.m1.point.y-1]==1)
game.m1.point.y=game.m1.point.y-2;
else if(game.yy[game.m1.point.x+game.m1.direction][game.m1.point.y-1]==1) {
game.m1.point.x+=game.m1.direction;
game.m1.point.y=game.m1.point.y-2;
}
else if(game.yy[game.m1.point.x-game.m1.direction][game.m1.point.y-1]==1) {
game.m1.point.x-=game.m1.direction;
game.m1.point.y=game.m1.point.y-2;
game.m1.direction=-game.m1.direction;
}
break;
case 83:
for(i=game.m1.point.y+2;i<12;i++)
if(game.yy[game.m1.point.x][i]==1)
{
game.m1.point.y=i-1;
break;
}
break;
}
//重画
CDC* pDC=GetDC();
OnDraw(pDC);
ReleaseDC(pDC);
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
现在,游戏完成,运行,看看效果。

5.5.附加内容
程序的调整:
编程序,我们不可能一开始就以最好的方法,最好的思想去实现它,成功地去完成它。

我们总是会遇到各种难题,出现各种差错,碰到各种思想障碍,以致我们在编程序的时候不能按照正常的形式编写,那么,我们是不是该检查我们刚写的程序,并做一些必要的调整呢?
不难看出,我们上面最后一个函数太复杂了,我们是不该不那么多的代码写在一个函数里面,不该可以用函数实现的代码直接写出来,也不该让可以不重复的代码重复出现!
那么,我们应该任何实现这个函数的调整?
首先,我们容易发现,m1和m2的方向操作几乎一样,就只是m1和m2的差别而已,是否可以把它们用代码重用的想法,合二为一。

第二,合二为一,就必须添加一个函数来给m1和m2共用,那么,这个函数应该添加在哪里?是在view类,还是在game类?由于函数里面的变量都是用game.开头的,显然,完成可以添加在game类里面。

而至于参数m1和m2的差别,我们应该在添加的move()函数里添加它们的标志参数:CMouse m。

另外,方向也是应该有参数的,分别用1,2,3,4表示。

最后函数的形式如下:
void CGame::Move(CMouse m,int direct)
第三,把OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)函数里面的相应内容移到新函数里面,去掉不必要的game.,并把m1,m2改为m,而在OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)函数里面添加新函数的相应调用。

更改完毕,你是否去试试!
支持多分辨率:
可以制作多种位图,依据对不同分辨率的判断,相应显示位图,并在draw()里面编写多种位图的显示。

6.6.小结
这个程序,我们学习了另外一种常见的游戏。

我们还学习了全屏显示的算法,虽然这里的全屏显示并不完全;但是它说明了一个道理:
圆是不存在的,因为它太完美了!
所有的程序,所有的算法都是不完美的,都是有待改进的。

我们必须承认它的存在,当然,我们也必须努力改进它,尽量使它完美。

相关文档
最新文档