为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

手把手教你用vc6做俄罗斯方块小游戏

2017-09-21 50页 doc 251KB 74阅读

用户头像

is_574951

暂无简介

举报
手把手教你用vc6做俄罗斯方块小游戏手把手教你用vc6做俄罗斯方块小游戏 第四章 俄罗斯方块 俄罗斯方块是我大学一年级刚学VC++时的课程设计,当时的课程设计有三种,单文档、 多文档和俄罗斯方块。我选择俄罗斯方块,就是因为它是游戏。 之前我是玩过俄罗斯方块的,一种是单人的(单人版),一种是两人对战的(对战版), 还有一种是网络版的,由于我还不了解网络,所以就决定编前两种。可是,这样没有新意, 我就想到了另外一种,配合游戏,或者称为情侣版。这里我先介绍三种,而网络版,由于我 们将介绍五子棋的网络游戏,鉴于它的简单性,我们将不介绍。 说明:以下三部分,可以以...
手把手教你用vc6做俄罗斯方块小游戏
手把手教你用vc6做俄罗斯方块小游戏 第四章 俄罗斯方块 俄罗斯方块是我大学一年级刚学VC++时的课程设计,当时的课程设计有三种,单文档、 多文档和俄罗斯方块。我选择俄罗斯方块,就是因为它是游戏。 之前我是玩过俄罗斯方块的,一种是单人的(单人版),一种是两人对战的(对战版), 还有一种是网络版的,由于我还不了解网络,所以就决定编前两种。可是,这样没有新意, 我就想到了另外一种,配合游戏,或者称为情侣版。这里我先介绍三种,而网络版,由于我 们将介绍五子棋的网络游戏,鉴于它的简单性,我们将不介绍。 :以下三部分,可以以三章看待。 1、 1、 游戏实现 俄罗斯方块,或称积木游戏,它是利用一些形状各异却又是用正方形组成的方块,经 过不同位置不同角度的变化之后,堆积在一起的一种智力游戏。 而从我们编程的角度讲,我们只需要提供各种方块的图形,提供几个键盘操作键以供 方块的形状和位置的变化,提供几个功能函数以供游戏的正常进行。 各种方块图形:利用数组定形,然后利用随机函数随机地不按顺序地按游戏的需要而 出现。 键盘操作键:就是四个方向键。其中左、右、下三个键意思一样,上键的功能不是使 方块向上,而是使方块的下落角度改变。 功能函数将在变量函数里面介绍。 新建单文档工程4_1。 2、 2、 资源编辑 封面: IDB_BITMAP1 背景: IDB_BITMAP2 方块: IDB_BITMAP4 开始: ID_MENU_START 3、 3、 变量函数 接着就是定义变量了,但是,由于这个游戏要添加的变量和函数太多了,我们要建一 个新类。 是否应该先添加应该类呢?最好是这样。因为新类将会涉及到变量。 添加普通类Crussia,见下图。 图4-1-1 由于两个类一共有很多变量函数,列举如下: // 4_1View.h : //俄罗斯类 CRussia russia; //开始标志 bool start; //封面 CBitmap fenmian; //暂停 BOOL m_bPause; //开始菜单 afx_msg void OnMenuStart(); //计时器 afx_msg void OnTimer(UINT nIDEvent); //键盘操作 afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); // Russia.h: //游戏数组 int Russia[100][100]; // 当前图形 int Now[4][4]; //上一图形 int Will[4][4]; //变换后的图形 int After[4][4]; //当前图形的左上角位置 CPoint NowPosition; //当前可能出现的图形形状数, int Count; //游戏结束 bool end; //级别 int m_Level; //速度 int m_Speed; //分数 int m_Score; //行列数 int m_RowCount,m_ColCount; //方块 CBitmap fangkuai; //背景 CBitmap jiemian; //显示分数等内容 void DrawScore(CDC*pDC); //消行 void LineDelete(); //方块移动 void Move(int direction); //方块变化,即方向键上键操作 bool Change(int a[][4],CPoint p,int b[][100]); //是否与原来方块接触,或与边界接触 bool Meet(int a[][4],int direction,CPoint p); //显示下一个方块 void DrawWill(); //显示界面 void DrawJiemian(CDC*pDC); //开始 void Start(); 4、 4、 具体实现 然后,我们就可以一步一步地实现游戏了。函数依然是一个一个添加,如果有还没定 义的函数,添加空函数。以保证程序的条理性和可运行性。 CMy4_1View::CMy4_1View() { // TODO: add construction code here fenmian.LoadBitmap(IDB_BITMAP1); start=false; m_bPause=false; } CRussia::CRussia() { jiemian.LoadBitmap(IDB_BITMAP2); fangkuai.LoadBitmap(IDB_BITMAP4); } void CMy4_1View::OnDraw(CDC* pDC) { CMy4_1Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CDC Dc; if(Dc.CreateCompatibleDC(pDC)==FALSE) AfxMessageBox("Can't create DC"); //没有开始,显示封面 if( !start) { Dc.SelectObject(fenmian); pDC->BitBlt(0,0,500,550,&Dc,0,0,SRCCOPY); } //显示背景 else russia.DrawJiemian(pDC); } 开始时我们是设start为假,它就会在OnDraw()函数中画封面,而当我们开始游戏,start 为真,那么,它干什么呢?画背景!其函数如下: 还是那个道理,当有一些客户区生效(被挡住或最小化)时,它必须重画,而如果游戏 只是玩了一半,它必然在重画时必须把原先已经出现的方块、分数等也显示出来,怎么办? 就必须在画封面的同时也画出它们。当然,刚开始时它们是不会符合条件的。 void CRussia::DrawJiemian(CDC*pDC) { CDC Dc; if(Dc.CreateCompatibleDC(pDC)==FALSE) AfxMessageBox("Can't create DC"); //画背景 Dc.SelectObject(jiemian); pDC->BitBlt(0,0,500,550,&Dc,0,0,SRCCOPY); //画分数,速度,难度 DrawScore(pDC); //如果有方块,显示方块 //游戏区 for(int i=0;iBitBlt(j*30,i*30,30,30,&Dc,0,0,SRCCOPY); } //预先图形方块 for(int n=0;n<4;n++) for(int m=0;m<4;m++) if(Will[n][m]==1) { Dc.SelectObject(fangkuai); pDC->BitBlt(365+m*30,240+n*30,30,30,&Dc,0,0,SRCCOPY); } } 其中还涉及另外一个函数DrawScore(pDC),它是画分数、速度、难度(本程序省略) 的。由于它的代码不是太少,另外用了一个函数,这样有利于理解。 void CRussia::DrawScore(CDC*pDC) { int nOldDC=pDC->SaveDC(); //设置字体 CFont font; if(0==font.CreatePointFont(300,"Comic Sans MS")) { AfxMessageBox("Can't Create Font"); } pDC->SelectObject(&font); //设置字体颜色及其背景颜色 CString str; pDC->SetTextColor(RGB(39,244,10)); pDC->SetBkColor(RGB(255,255,0)); //输出数字 str.Format("%d",m_Level); if(m_Level>=0) pDC->TextOut(440,120,str); str.Format("%d",m_Speed); if(m_Speed>=0) pDC->TextOut(440,64,str); str.Format("%d",m_Score); if(m_Score>=0) pDC->TextOut(440,2,str); pDC->RestoreDC(nOldDC); } 至此,可以看的都画完了。程序一般都是会先处理图形界面,因为这样在编核心内容时 能够让人有一个检查的机会。 现在,游戏总该开始了吧。添加菜单开始函数:ID_MENU_START 其函数如下: void CMy4_1View::OnMenuStart() { // TODO: Add your command handler code here start=true; russia.Start(); SetTimer(1,50*(11-russia.m_Speed ),NULL); } 先把start赋值为true,再调用russia.Start()函数,让它对俄罗斯方块游戏的相应变量赋 值,为了使游戏能够调整速度,设置一个可变的计数器。那么,russia.Start()函数做了什么 呢? void CRussia::Start() { end=false;//运行结束标志 m_Score=0; //初始分数 m_Speed=0; //初始速度 m_Level=1; //初始难度 m_RowCount=18; //行数 m_ColCount=12; //列数 Count=7; //方块种类 //清空背景数组 for(int i=0;ii) k=i; if(l>j) l=j; } for(i=0;i<4;i++) for(j=0;j<4;j++) Will[i][j]=0; //把变换后的矩阵移到左上角 for(i=k;i<4;i++) for(j=l;j<4;j++) Will[i-k][j-l]=tmp[i][j]; // Now[][]的开始位置 NowPosition.x=0; NowPosition.y=m_ColCount/2; } 有了SetTimer( ),就别忘了OnTimer(UINT nIDEvent)函数: 先下移,再重画。 void CMy4_1View::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default //下移 russia.Move(3); //重画 russia.DrawJiemian(GetDC()); CView::OnTimer(nIDEvent); } 下移,用Move()函数,见下面。 那么,其中的参数是什么意思?方向!就是四个方向键。显然,上面只是用了其中之 一,即下移。那这个函数究竟是在哪里调用?聪明的读者一定知道就是键盘上的方向键! 就让我们在讲Move()函数之前,添加一个OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)函数。 void CMy4_1View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default //没有开始 if(!start) return; //暂停 if(m_bPause==TRUE) return; switch(nChar) { case VK_LEFT: russia.Move(1); break; case VK_RIGHT: russia.Move(2); break; case VK_UP: russia.Move(4); break; case VK_DOWN: russia.Move(3); break; } //重画 CDC* pDC=GetDC(); russia.DrawJiemian(pDC); ReleaseDC(pDC); CView::OnKeyDown(nChar, nRepCnt, nFlags); } 很明显,上面其实都是根据下面的函数而工作的。下面的函数,先判断哪个方向,即 按了哪个方向键,然后调用一个判断是否过界或重叠的函数Meet(Now,1,NowPosition),然 后或者返回,或者移动。 另外,当下移并且被阻挡时,必须判断是否可以消行。 void CRussia::Move(int direction) { if(end) return; switch(direction) { //左 case 1: if(Meet(Now,1,NowPosition)) break; NowPosition.y--; break; //右 case 2: if(Meet(Now,2,NowPosition)) break; NowPosition.y++; break; //下 case 3: if(Meet(Now,3,NowPosition)) { LineDelete(); break; } NowPosition.x++; break; //上 case 4: Meet(Now,4,NowPosition); break; default: break; } } 上面涉及两个新的函数,介绍如下: //消去行 void CRussia::LineDelete() { int m=0; //本次共消去的行数 bool flag=0; for(int i=0;i0;k--) { //上行给下行 for(int l=0;l=m_ColCount) goto exit; if(Russia[p.x+i][p.y+j+1]==1) goto exit; break; case 3://下移 if((p.x+i+1)>=m_RowCount) goto exit; if(Russia[p.x+i+1][p.y+j]==1) goto exit; break; case 4://变换 if(!Change(a,p,Russia)) goto exit; for(i=0;i<4;i++) for(j=0;j<4;j++) { Now[i][j]=After[i][j]; a[i][j]=Now[i][j]; } return false; break; } } int x,y; x=p.x; y=p.y; //移动位置,重新给数组赋值 switch(direction) { case 1: y--;break; case 2: y++;break; case 3: x++;break; case 4: break; } for(i=0;i<4;i++) for(j=0;j<4;j++) if(a[i][j]==1) Russia[x+i][y+j]=1; return false; exit: for(i=0;i<4;i++) for(j=0;j<4;j++) if(a[i][j]==1) Russia[p.x+i][p.y+j]=1; return true; } 此函数是先把背景数组的相应位置赋值为零,而利用当前数组和一些局部变量数组的 交换赋值,然后检查是否符合放下背景数组的要求,是则按照这情况赋值,否则按原先情况 赋值。 其中变换时有一个新函数,是检查是否可以变换的。如下面。 转换,就是当按下向上方向键时,也要判断是否出界或重叠。 //转换 bool CRussia::Change(int a[][4], CPoint p,int b[][100]) { int tmp[4][4]; int i,j; int k=4,l=4; for(i=0;i<4;i++) for(j=0;j<4;j++) { tmp[i][j]=a[j][3-i]; After[i][j]=0; //存放变换后的方块矩阵 } for(i=0;i<4;i++) for(j=0;j<4;j++) if(tmp[i][j]==1) { if(k>i) k=i; if(l>j) l=j; } for(i=k;i<4;i++) for(j=l;j<4;j++) { After[i-k][j-l]=tmp[i][j]; } //把变换后的矩阵移到左上角 //判断是否接触,是:返回失败 for(i=0;i<4;i++) for(j=0;j<4;j++) { if(After[i][j]==0) continue; if(((p.x+i)>=m_RowCount)||((p.y+j)<0)||((p.y+j)>=m_ColCount)) return false; if(b[p.x+i][p.y+j]==1) return false; } return true; } 现在,我们的程序编好了,可以玩了。不难吧! 5、 附加内容 但是,比起附带的程序代码,是否还少了什么?暂停、热键、还有艺术字! , , 添加菜单如下图,添加函数如下: 图4-1-2 void CMy4_1View::OnMenuPause() { // TODO: Add your command handler code here m_bPause=!m_bPause; //停止计数器 if(m_bPause) KillTimer(1); //开始计数器 else SetTimer(1,50*(11-russia.m_Speed ),NULL); } void CMy4_1View::OnUpdateMenuPause(CCmdUI* pCmdUI) { // TODO: Add your command update UI handler code here //是否显示钩 pCmdUI->SetCheck(m_bPause); } , , 如上图,打开Accelerator,添加如上ID号,选择如上ID号和热键就行了。 , , 看了我的封面上的艺术字,是否有些心动,用VC++怎么设置出这样的艺术字? 我的答案是这不是用VC++做的,它只是一张位图。不过,这张位图是我自己做的, 怎么做?我可以说一下: 利用Word里面的艺术字,做好之后,把它拷贝到位图上就行了! 6、 6、 小结 一个人的游戏我们已经编出来了。 两个人的游戏又是如何的呢? 请看下一节! 1. 1. 游戏实现 上面各种界面告诉我们,接下来的工作将对游戏的界面进行很大的加工。最先是封面的 添加,接着是单人版的界面增加,最后还有本节的主要部分,对战版的添加。 封面:既然我们有了多种游戏,既然我们需要菜单选项,既然我们有了界面并且我们的 界面下面有空白可以让我们添加,我们就可以在封面是添加菜单选项。 界面:这里指的是游戏时的背景。上图我们显然是对界面进行了扩充,一是为了学习, 二是为了游戏的需要。由于原来的界面是接近正方形的,不利于我们这个双人游戏的显示; 我们最好是改变它,使它在玩双人游戏时的界面也是接近正方形的。当然,原来的可以保留。 使程序有多种界面可以选择。 对战版:本节的主要内容,它的实现是利用我们新建的类,利用类的多对象,同时产生 两个对象而形成的双人游戏。在这里,你将可以看到程序的另外一种扩充,你将会体会到原 来程序的扩充并不像上一章的方法,这里的更加简单且有趣。 当然,这是在4_1的基础上继续编程的。先复制文件夹4_1,改名为4_2,打开4_2文 件夹中的工作区,继续我们的程序。 2. 2. 资源编辑 图4-2-1 上面只是位图的组合,下面给出位图及其ID号(分别为IDB_BITMAP5~9)。 背景: IDB_BITMAP5 方块: IDB_BITMAP6 菜单: IDB_BITMAP7、IDB_BITMAP8、IDB_BITMAP9 删除开始菜单。 为了和封面菜单对应,我们添加菜单项如下: 文件: 单人游戏:ID_MENU_START 对战游戏:ID_MENU_DSTART 配合游戏:ID_MENU_TSTART 查看: 左视图: ID_VIEW_1 上视图: ID_VIEW_2。 其中,配合游戏只是一个空函数,它将在第三节实现。 3. 3. 变量函数 // 4_1View.h //选择三种菜单图形 CBitmap xuanze[3]; //选择哪一种游戏(相对于菜单位图) int ixuanze; //选择哪一种游戏(相对于菜单) int player; //哪种界面(菜单) int view; //新对象 CRussia russia2; // Russia.h: //哪种界面(位图) CBitmap jiemian2; //哪种方块(大小) CBitmap fangkuai2; //新的界面函数 void DrawJiemian1(int a,int b,CDC*pDC); void DrawJiemian2(int a,int b,CDC*pDC); 4. 4. 具体实现 记住变量要在各自的构造函数里面赋值。 CMy4_1View::CMy4_1View() { // TODO: add construction code here fenmian.LoadBitmap(IDB_BITMAP1); for(int i=0;i<3;i++) xuanze[i].LoadBitmap(IDB_BITMAP7+i); start=false; m_bPause=false; //第一种背景 view=1; //第一种游戏 player=1; //第一个封面菜单 ixuanze=1; } CRussia::CRussia() { jiemian.LoadBitmap(IDB_BITMAP2); fangkuai.LoadBitmap(IDB_BITMAP4); fangkuai2.LoadBitmap(IDB_BITMAP6); jiemian2.LoadBitmap(IDB_BITMAP5); } 如果是两人游戏,比起原先的界面,正方形的是不是感觉更好一些?那么,我们就必 须在原来的基础上重新添加一张位图(前面已经添加了),重新对DrawJiemian()函数进 行改造。但是,既然要改造,为何不干脆做成两种界面呢? 把查看菜单改为:左视图和上视图,其ID值分别为:ID_VIEW_1和ID_VIEW_2。并 添加如下函数: void CMy4_1View::OnView1() { // TODO: Add your command handler code here view=1; //调整窗口大小 if(player==1) AfxGetMainWnd() ->SetWindowPos(NULL,0,0,500,590,SWP_NOMOVE|SWP_NOZORDER ); if(player==2) AfxGetMainWnd() ->SetWindowPos(NULL,0,0,1000,590,SWP_NOMOVE|SWP_NOZORDER ); } void CMy4_1View::OnView2() { // TODO: Add your command handler code here view=2; if(!start) return; //调整窗口大小 if(player==1) AfxGetMainWnd() ->SetWindowPos(NULL,0,0,253,510,SWP_NOMOVE|SWP_NOZORDER ); if(player==2) AfxGetMainWnd() ->SetWindowPos(NULL,0,0,510,510,SWP_NOMOVE|SWP_NOZORDER ); } //判断在哪里画勾 void CMy4_1View::OnUpdateView1(CCmdUI* pCmdUI) { // TODO: Add your command update UI handler code here pCmdUI->SetCheck(view==1); } void CMy4_1View::OnUpdateView2(CCmdUI* pCmdUI) { // TODO: Add your command update UI handler code here pCmdUI->SetCheck(view==2); } 前两个函数分别赋值参数view,以有利于OnDraw()函数显示相应背景。而由于各个背 景大小不一,就需要对框架进行改变。后两个函数是判断应该在哪个菜单打钩。 那画界面的函数应该怎样改变呢? 它变化太大了,我们不得不说。函数由一个参数变成三个参数,前面两个是干什么的 呢?我们添加这的目的是为了可以在不同地方显示界面(的左上角),而添加的两个参数就 是显示界面的左上角的点。而为了能在正确位置显示分数,我们放弃了DrawScore()函数, 而是把它添加到这个函数里面。 void CRussia::DrawJiemian2(int a,int b,CDC*pDC) { CDC Dc; if(Dc.CreateCompatibleDC(pDC)==FALSE) AfxMessageBox("Can't create DC"); //画背景 Dc.SelectObject(jiemian2); pDC->BitBlt(a,b,240,460,&Dc,0,0,SRCCOPY); //画分数,速度,难度 //设置字体颜色及其背景颜色 CString str; pDC->SetTextColor(RGB(198,24,190)); pDC->SetBkColor(RGB(255,255,0)); //输出数字 str.Format("%d",m_Level); if(m_Level>=0) pDC->TextOut(a+50,b+70,str); str.Format("%d",m_Speed); if(m_Speed>=0) pDC->TextOut(a+50,b+42,str); str.Format("%d",m_Score); if(m_Score>=0) pDC->TextOut(a+50,b+12,str); //如果有方块,显示方块 //游戏区 for(int i=0;iBitBlt(a+j*20,b+100+i*20,30,30,&Dc,0,0,SRCCOPY); } //预先图形 for(int n=0;n<4;n++) for(int m=0;m<4;m++) if(Will[n][m]==1) { Dc.SelectObject(fangkuai2); pDC->BitBlt( a+120+m*20,b+10+n*20,30,30,&Dc,0,0,SRCCOPY); } } 既然你已经理解了这个问题,那么我们是否也该把原来的DrawJiemian(CDC*pDC)函数 改了呢?是的。如下: void CRussia::DrawJiemian1(int a,int b,CDC*pDC) { CDC Dc; if(Dc.CreateCompatibleDC(pDC)==FALSE) AfxMessageBox("Can't create DC"); //画背景 Dc.SelectObject(jiemian); pDC->BitBlt(a,b,500,550,&Dc,0,0,SRCCOPY); //画分数,速度,难度 int nOldDC=pDC->SaveDC(); //设置字体 CFont font; if(0==font.CreatePointFont(300,"Comic Sans MS")) { AfxMessageBox("Can't Create Font"); } pDC->SelectObject(&font); //设置字体颜色及其背景颜色 CString str; pDC->SetTextColor(RGB(39,244,10)); pDC->SetBkColor(RGB(255,255,0)); //输出数字 str.Format("%d",m_Level); if(m_Level>=0) pDC->TextOut(a+440,b+120,str); str.Format("%d",m_Speed); if(m_Speed>=0) pDC->TextOut(a+440,b+64,str); str.Format("%d",m_Score); if(m_Score>=0) pDC->TextOut(a+440,b+2,str); pDC->RestoreDC(nOldDC); // DrawScore(pDC); //如果有方块,显示方块 //游戏区 for(int i=0;iBitBlt(a+j*30,b+i*30,30,30,&Dc,0,0,SRCCOPY); } //预先图形 for(int n=0;n<4;n++) for(int m=0;m<4;m++) if(Will[n][m]==1) { Dc.SelectObject(fangkuai); pDC->BitBlt(a+365+m*30,b+240+n*30,30,30,&Dc,0,0,SRCCOPY); } } 接着,为了能看到效果,必须改变OnDraw(CDC* pDC)函数,如果没有开始,显示封 面,否则,依照view的值显示相应背景。由于我们现在player的值只有1,所以下面那个 player=2的条件不会执行,可以不管它。 void CMy4_1View::OnDraw(CDC* pDC) { //窗口在中间 AfxGetMainWnd()->CenterWindow(); CMy4_1Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CDC Dc; if(Dc.CreateCompatibleDC(pDC)==FALSE) AfxMessageBox("Can't create DC"); //没有开始,显示封面 if( !start) { Dc.SelectObject(fenmian); pDC->BitBlt(0,0,500,550,&Dc,0,0,SRCCOPY); //显示选择位图 Dc.SelectObject(xuanze[ixuanze-1]); pDC->BitBlt(200,350,150,150,&Dc,0,0,SRCCOPY); } //显示背景 else { if(view==1) { if(player==1) russia.DrawJiemian1(0,0,pDC); if(player==2) { russia.DrawJiemian1(500,0,pDC); russia2.DrawJiemian1(0,0,pDC); } } if(view==2) { if(player==1) russia.DrawJiemian2(0,0,pDC); if(player==2) { russia.DrawJiemian2(253,0,pDC); russia2.DrawJiemian2(0,0,pDC); } } } } 那么,上面的player=2是什么意思?封面已经告诉我们,我们的程序有三个子程序, 我们就分别用player=1,2,3来表示的。Player=3是下节的内容,这里只是顺便添加。三个 子程序,应该对应三个菜单项。添加函数如下: //单人版 void CMy4_1View::OnMenuStart() { // TODO: Add your command handler code here //先改变框架大小 if(view==1) AfxGetMainWnd() ->SetWindowPos(NULL,0,0,500,590,SWP_NOMOVE|SWP_NOZORDER ); if(view==2) AfxGetMainWnd() ->SetWindowPos(NULL,0,0,253,510,SWP_NOMOVE|SWP_NOZORDER ); //player赋值为1 player=1; start=true; russia.Start(); SetTimer(1,50*(11-russia.m_Speed ),NULL); } //双人版 void CMy4_1View::OnMenuDstart() { // TODO: Add your command handler code here if(view==1) AfxGetMainWnd() ->SetWindowPos(NULL,0,0,1000,590,SWP_NOMOVE|SWP_NOZORDER ); if(view==2) AfxGetMainWnd() ->SetWindowPos(NULL,0,0,510,510,SWP_NOMOVE|SWP_NOZORDER ); //player赋值为2 player=2; start=true; //开始第一人 russia.Start(); //时间间隔 Sleep(300); //开始第二人 russia2.Start(); SetTimer(1,50*(11-russia.m_Speed ),NULL); } //配合版 void CMy4_1View::OnMenuTstart() { // TODO: Add your command handler code here AfxMessageBox("还没有完成!"); } 现在运行,界面出来了,可是对战时,一边操作不了。 原来是OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)函数还没有改。 在 case VK_DOWN后面添加A,S,D,W四个键的操作: case VK_DOWN: russia.Move(3); break; case 65: russia2.Move(1); break; case 68: russia2.Move(2); break; case 87: russia2.Move(4); break; case 83: russia2.Move(3); break; 可还是不对,它不会自动下落。 改变OnTimer(UINT nIDEvent)函数如下: void CMy4_1View::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default //下移 russia.Move(3); //如果是两人,第二个也下移 if(player==2) russia2.Move(3); //重画 OnDraw(GetDC()); CView::OnTimer(nIDEvent); } 这样,对战游戏就完成了。简单又有趣。我们不止添加了游戏,还添加了功能。 5. 5. 附加内容 下面我们添加鼠标和键盘的应用。它只要体现在封面菜单的选择上面。我们的封面有三 个菜单项,我们分别用键盘和鼠标了实现我们对游戏开始的选择。 上面有一个变量ixuanze没有用到,它是干什么的?就是这三个菜单项的参数。数值分 别对应于player。 在OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)函数里面添加如下内容:如果 没有开始,如果按下上下键,改变菜单先项,体现在位图的更换。如果按空格键,开始相应 的游戏。 下面是整个函数的内容: void CMy4_1View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default //没有开始 if(!start) { //部分重画 CRect rect; rect.left=170; rect.top=330; rect.right=340; rect.bottom=450; //按下键,ixuanze循环增加 if(nChar==VK_DOWN) { if(ixuanze<3) ixuanze++; else ixuanze=1; InvalidateRect(&rect); } //按上键,ixuanze循环减少 if(nChar==VK_UP) { if(ixuanze>1) ixuanze--; else ixuanze=3; InvalidateRect(&rect); } if(nChar==VK_SPACE) { if(ixuanze==1) OnMenuStart(); if(ixuanze==2) OnMenuDstart(); if(ixuanze==3) OnMenuTstart(); } return; } //暂停 if(m_bPause==TRUE) return; switch(nChar) { case VK_LEFT: russia.Move(1); break; case VK_RIGHT: russia.Move(2); break; case VK_UP: russia.Move(4); break; case VK_DOWN: russia.Move(3); break; case 65: russia2.Move(1); break; case 68: russia2.Move(2); break; case 87: russia2.Move(4); break; case 83: russia2.Move(3); break; } //重画 OnDraw(GetDC()); CView::OnKeyDown(nChar, nRepCnt, nFlags); } 添加鼠标操作函数。鼠标有两种操作:一种是选择,利用鼠标的移动,选择相应的 参数;一种是开始,利用鼠标的按下,开始相应的游戏。添加函数: void CMy4_1View::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default //如果开始,退出 if(start) return; //部分重画 CRect rect; rect.left=170; rect.top=330; rect.right=340; rect.bottom=450; //point.x和point.y是判断指针是否在菜单上面 if(point.x>200&&point.x<350) { if(point.y>350&&point.y<380) { //如果选择菜单不同,改变,重画 if(ixuanze!=1) { ixuanze=1; InvalidateRect(&rect); } } if(point.y>380&&point.y<410) if(ixuanze!=2) { ixuanze=2; InvalidateRect(&rect); } if(point.y>410&&point.y<440) if(ixuanze!=3) { ixuanze=3; InvalidateRect(&rect); } } CView::OnMouseMove(nFlags, point); } 现在,当我们的鼠标指针在菜单上面移动时,就会显示不同的选项。但是,我们该 怎样让它开始?添加如下函数: void CMy4_1View::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default //判断是否在菜单位图上 if(point.x>200&&point.x<340&&point.y>350&&point.y<450) { //判断菜单参数,开始相应游戏 if(ixuanze==1) OnMenuStart(); if(ixuanze==2) OnMenuDstart(); if(ixuanze==3) OnMenuTstart(); } CView::OnLButtonDown(nFlags, point); } 6. 6. 小结 如果说,五子棋游戏的扩展太复杂的话,那这个游戏的扩展就简单多了。而且好象还 添加了更多的内容。为什么?因为五子棋的扩展是把其中一个人改为计算机,本来很多人的 想法都必须添加到程序里面,从而使程序复杂化。而这个游戏,它是利用资源重复的道理, 在原来的函数基础上改变一下,形成新的函数,实现新的功能,又利用一个类可以派生多个 对象的原理,根本就只要几行代码而已。 下面,我们将进行再一次的扩充,它又会是怎样的呢? 1、 1、 游戏实现 配合版的界面就在上面,它的规则是:两个人一起游戏,只有一个游戏区域,整行满 了才能消去;只有一个预备方块,谁先要就给谁,当然这也可以配合;还有一点,两个活动 方块都可以在整个区域移动,但是,如果下移时被下面的活动方块挡住的话,就会停止下移。 这个程序,我们将用到的是类的继承。为了实现一个新的游戏,我们不得不添加代码, 为了不至于添加太多的代码,我们需要利用原来的代码,继承就是这种思想。 关于这个游戏的实现,我要谈到创新的问题。何为创新?就是以独特的思维方式,想 出或做出有实际意义的而又不是通常能注意到的问题或方法。 如果妈妈生病进了医院,我们应该为她担心。这是正常的,能够得以理解的。当时, 我更为爸爸的身体担心!为什么呢?因为妈妈有很多人照顾,可爸爸没有,可能连他自己都 忘记了照顾自己,我很为他担心。而这个,是经常被人所忽略了的。当时,它确实很有意义。 首先,我们复制4_2 并改名为4_3,打开工作区, 开始编程。 2、 2、 资源编辑 添加如上背景位图ID_BITMAP10。 3、 3、 变量函数 新的游戏,新的需求。既然原来的类不能满足我们的要求,我们就必须添加新类。已 经说过,我们需要继承: class CRussia0 : public CRussia 并在CMy4_1View()中添加对象:russia0。 在CRussia0类中添加变量函数: //第二个活动位置 CPoint NowPosition0; //第二个活动方块 int Now0[4][4]; //背景 CBitmap jiemian0; //函数意思和上面相同 void LineDelete(int a[][4]); bool Meet0(int a[][4],int direction,CPoint p); void Move(int direction); void DrawWill(int a[][4]); void Start(); void DrawJiemian(CDC*pDC); 除了添加的之外,其他的都继承Crussia类。 4、 4、 具体实现 有了新类和变量jiemian0,我们可以来显示背景位图:在构造函数里添加语句: jiemian0.LoadBitmap(IDB_BITMAP10); 添加显示函数,如下: void CRussia0::DrawJiemian(CDC *pDC) { CDC Dc; if(Dc.CreateCompatibleDC(pDC)==FALSE) AfxMessageBox("Can't create DC"); //画背景 Dc.SelectObject(jiemian0); pDC->BitBlt(0,0,500,600,&Dc,0,0,SRCCOPY); //画分数,速度,难度 //设置字体颜色及其背景颜色 CString str; pDC->SetTextColor(RGB(198,24,190)); pDC->SetBkColor(RGB(255,255,0)); //输出数字 str.Format("%d",m_Level); if(m_Level>=0) pDC->TextOut(80,70,str); str.Format("%d",m_Speed); if(m_Speed>=0) pDC->TextOut(80,42,str); str.Format("%d",m_Score); if(m_Score>=0) pDC->TextOut(80,12,str); //如果有方块,显示方块 //游戏区 for(int i=0;iBitBlt(j*20,100+i*20,30,30,&Dc,0,0,SRCCOPY); } //预先图形 for(int n=0;n<4;n++) for(int m=0;m<4;m++) if(Will[n][m]==1) { Dc.SelectObject(fangkuai2); pDC->BitBlt( 220+m*20,10+n*20,30,30,&Dc,0,0,SRCCOPY); } } 这个函数就不用解释了。 然后,在void CMy4_1View::OnDraw()函数里面添加显示位图的内容:在else里最后的 地方添加两个语句: //显示背景 else { //省略。。。。。。。。。。。。。。 if(player==3) russia0.DrawJiemian(pDC); } 如果现在运行,本选择配合游戏,就能看到界面了。现在,我们从菜单开始,实现游 戏的具体功能。 在OnMenuTstart()函数中去掉原来的消息框,改变窗口大小,调用开始函数: void CMy4_1View::OnMenuTstart() { // TODO: Add your command handler code here // AfxMessageBox("还没有完成!"); AfxGetMainWnd() ->SetWindowPos(NULL,0,0,513,650,SWP_NOMOVE|SWP_NOZORDER ); player=3; start=true; russia0.Start(); SetTimer(1,50*(11-russia0.m_Speed ),NULL); } 开始函数是新游戏的开始,是不可能继承的,添加如下: void CRussia0::Start() { end=false;//运行结束标志 m_Score=0; //初始分数 m_Speed=0; //初始速度 m_Level=0; //初始难度 m_RowCount=25; //行数 m_ColCount=25; //列数 Count=7; //方块种类 for(int i=0;ii) k=i; if(l>j) l=j; } for(i=0;i<4;i++) for(j=0;j<4;j++) Will[i][j]=0; //把变换后的矩阵移到左上角 for(i=k;i<4;i++) for(j=l;j<4;j++) Will[i-k][j-l]=tmp[i][j]; //开始位置 if(a==Now) { NowPosition.x=0; NowPosition.y=m_ColCount/4; } if(a==Now0) { NowPosition0.x=0; NowPosition0.y=3*m_ColCount/4; } } 现在运行,方块出来了,可是不会动,设置计数器:把OnTimer(UINT nIDEvent) 函数 改为如下: void CMy4_1View::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default //下移 if(player==1) russia.Move(3); if(player==2) { russia.Move(3); russia2.Move(3); } if(player==3) { russia0.Move(3); russia0.Move(7); } OnDraw(GetDC()); CView::OnTimer(nIDEvent); } Move(3)是行的,Move(7)对于程序运行也没有问题。问题是,这有什么用,Move (7)对于原来的函数根本就没有作用。也就是说,这个函数我们也不能继承,那么,怎么 办? 添加如下:当然,这是一个翻版的函数,但由于同时有八个键在操作,我们不得不这 么做。 void CRussia0::Move(int direction) { if(end) return; switch(direction) { //左 case 1: if(Meet(Now,1,NowPosition)) break; NowPosition.y--; break; //右 case 2: if(Meet(Now,2,NowPosition)) break; NowPosition.y++; break; //下 case 3: if(Meet(Now,3,NowPosition)) { LineDelete(Now); break; } NowPosition.x++; break; //上 case 4: Meet(Now,4,NowPosition); break; //左 case 5: if(Meet(Now0,1,NowPosition0)) break; NowPosition0.y--; break; //右 case 6: if(Meet(Now0,2,NowPosition0)) break; NowPosition0.y++; break; //下 case 7: if(Meet(Now0,3,NowPosition0)) { LineDelete(Now0); break; } NowPosition0.x++; break; //上 case 8: Meet0(Now0,8,NowPosition0); break; default: break; } } 上面出现了两个问题:一是有两种Meet()函数,二是LineDelete()函数有了参数。 对于第一个问题,是因为前面七种情况适合父类的函数,我们采用继承;而最后一个 由于关系到一部分新类的变量,我们只能另外添加,并做了合理的调整: bool CRussia0::Meet0(int a[][4], int direction, CPoint p) { int i,j; //先把原位置清0 for(i=0;i<4;i++) for(j=0;j<4;j++) if(a[i][j]==1) Russia[p.x+i][p.y+j]=0; for(i=0;i<4;i++) for(j=0;j<4;j++) if(a[i][j]==1) { if(!Change(a,p,Russia)) { for(i=0;i<4;i++) for(j=0;j<4;j++) if(a[i][j]==1) Russia[p.x+i][p.y+j]=1; return true; } for(i=0;i<4;i++) for(j=0;j<4;j++) { Now0[i][j]=After[i][j]; a[i][j]=Now0[i][j]; } return false; } return true; } 显然,第二个问题也是这种情况,它涉及到新类的变量。可是,我们没有那么幸运, 不得不重写全部的代码: 本函数的关键部分是:中间的生成新活动方块;后面的判断是否结束:它必须是刚停 止的活动方块达到顶点才结束。 void CRussia0::LineDelete(int a[][4]) { int m=0; //本次共消去的行数 bool flag=0; for(int i=0;i0;k--) //上行给下行 for(int l=0;l目录
。 5. 5. 选中Pop-up Menu,即右键菜单。 6. 6. 单击插入按钮,确定。 7. 7. 在弹出对话框中选择CMy4_1View。确定,关闭。 8. 8. 打开ResourceView中的Menu,双击新添加的菜单项。然后按编辑菜单的方法 添加要添加的项目。 运行,右键点击,菜单就出来了。 可是,我们的封面已经有够多的菜单选项了。我们能否让右键菜单在进去之后才生效 呢?找到以下函数,在第一行添加语句: void CMy4_1View::OnContextMenu(CWnd*, CPoint point) { //如果还没有开始 if(!start) //返回, return; // CG: This block was added by the Pop-up Menu component { if (point.x == -1 && point.y == -1){ //keystroke invocation CRect rect; GetClientRect(rect); ClientToScreen(rect); point = rect.TopLeft(); point.Offset(5, 5); } CMenu menu; VERIFY(menu.LoadMenu(CG_IDR_POPUP_MY4_1_VIEW)); CMenu* pPopup = menu.GetSubMenu(0); ASSERT(pPopup != NULL); CWnd* pWndPopupOwner = this; while (pWndPopupOwner->GetStyle() & WS_CHILD) pWndPopupOwner = pWndPopupOwner->GetParent(); pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, pWndPopupOwner); } } 6、 6、 小结 这一章,我们学习了俄罗斯方块游戏。 我们学习了不只是游戏的算法,我们的重点是学习程序的扩展过程,扩展的思路。 我们学习了类的继承,类的多对象性。 最后,我们还学习了一个实用的函数:鼠标的右键菜单。 程序,本来不是扩展而来的;而是有目的,有计划的进行编程的。 但是,这不是本书的目的;本书的编写目的是为了学习。 学习是应该从无到有,从小到大的。而这,体现出来的就是程序的扩展! 程序的扩展,可以是功能的扩展,可以是项目的扩展。 下面,我们将再介绍一些本程序的扩展思路: 1 1 这个游戏,前面我们只涉及到传统的方块类型。而事实上,我们在玩游戏的时候总 是会遇到一些其它的,形状更怪的,难道也就增加的方块。我们也可以添加,并改 变功能函数让其实现。 如下: //适应于难度扩展 case 7: Will[0][0]=1; Will[1][0]=1; Will[1][1]=1; Will[1][2]=1; Will[0][2]=1; break; case 8: Will[0][0]=1; Will[1][0]=1; Will[2][0]=1; Will[1][1]=1; Will[1][2]=1; break; 2 2由于本游戏是用位图实现方块的,我们用不了多颜色的变换。当然,你也可以制作多种 颜色的方块位图,然后实现随机变换颜色。 3 3 即设置边长不同的方块,同时也涉及到框架大小的变化。 4 4 可以是背景的修饰,也可以是方块的修饰,如在方块上面画一朵玫瑰花,画一颗红 心等。 5 5 添加联机版本的游戏。实现网上游戏。 本书最后将会介绍联机游戏。一章联机基础,一章五子棋联机游戏。那么,俄罗斯方块 的联机,就留给读者练习。
/
本文档为【手把手教你用vc6做俄罗斯方块小游戏】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索