首页
社区
课程
招聘
vc++游戏源码
发表于: 2008-9-17 16:31 4097

vc++游戏源码

2008-9-17 16:31
4097
一个vc++游戏源码
1、        游戏实现

俄罗斯方块,或称积木游戏,它是利用一些形状各异却又是用正方形组成的方块,经过不同位置不同角度的变化之后,堆积在一起的一种智力游戏。

而从我们编程的角度讲,我们只需要提供各种方块的图形,提供几个键盘操作键以供方块的形状和位置的变化,提供几个功能函数以供游戏的正常进行。

各种方块图形:利用数组定形,然后利用随机函数随机地不按顺序地按游戏的需要而出现。

键盘操作键:就是四个方向键。其中左、右、下三个键意思一样,上键的功能不是使方块向上,而是使方块的下落角度改变。

功能函数将在变量函数里面介绍。

新建单文档工程4_1。

2、        资源编辑

添加位图:

        封面:       IDB_BITMAP1

背景:          IDB_BITMAP2

方块:          IDB_BITMAP4

添加菜单:

        开始:      ID_MENU_START

3、        变量函数

接着就是定义变量了,但是,由于这个游戏要添加的变量和函数太多了,我们要建一个新类。

是否应该先添加应该类呢?最好是这样。因为新类将会涉及到变量。

添加普通类Crussia,

           

添加变量函数:

由于两个类一共有很多变量函数,列举如下:

// 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、        具体实现

然后,我们就可以一步一步地实现游戏了。函数依然是一个一个添加,如果有还没定义的函数,添加空函数。以保证程序的条理性和可运行性。

构造函数:

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;i<m_RowCount;i++)

              for(int j=0;j<m_ColCount;j++)

                     if(Russia[i][j]==1)

                     {

                            Dc.SelectObject(fangkuai);

                            pDC->BitBlt(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;i<m_RowCount;i++)

              for(int j=0;j<m_ColCount;j++)

              {

                     Russia[i][j]=0;

              }

    //清空方块数组Now[ ][ ]  Will[ ][ ]

       for(i=0;i<4;i++)

              for(int j=0;j<4;j++)

              {

                     Now[i][j]=0;

                     Will[i][j]=0;

              }

    //先画Will[][]

       DrawWill();

       //再画Now[][]&Will[][]

       DrawWill();

}

预备方块:

DrawWill()的作用是,把预备方块给当前方块,再生成一个预备方块。由于开始时预备方块自己都没有,它根本就不能拿一个方块给当前数组,它只能自己生成一个,这也是什么连续调用两次的原因。

void CRussia::DrawWill()

{

       int i,j;

       int k=4,l=4;

    //把将要出现的方块给当前数组,并把将要出现数组赋值为零

       for(i=0;i<4;i++)

              for(j=0;j<4;j++)

              {

                     Now[i][j]=Will[i][j];

                     Will[i][j]=0;

              }

       //初始化随即数种子

    //其中是什么意思?不知道的话,不如按一下F1。

       srand(GetTickCount());

       int nTemp=rand()%Count;

       //各种图形

       switch(nTemp)

       {

       case 0:

              Will[0][0]=1;

              Will[0][1]=1;

              Will[1][0]=1;

              Will[1][1]=1;

              break;

       case 1:

              Will[0][0]=1;

              Will[0][1]=1;

              Will[1][0]=1;

              Will[2][0]=1;

              break;

       case 2:

              Will[0][0]=1;

              Will[0][1]=1;

              Will[1][1]=1;

              Will[2][1]=1;

              break;

       case 3:

              Will[0][1]=1;

              Will[1][0]=1;

              Will[1][1]=1;

              Will[2][0]=1;

              break;

       case 4:

              Will[0][0]=1;

              Will[1][0]=1;

              Will[1][1]=1;

              Will[2][1]=1;

              break;

       case 5:

              Will[0][0]=1;

              Will[1][0]=1;

              Will[1][1]=1;

              Will[2][0]=1;

              break;

       case 6:

              Will[0][0]=1;

              Will[1][0]=1;

              Will[2][0]=1;

              Will[3][0]=1;

              break;

//适应于难度扩展

/*    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;

*/

       }

    //转换

       int tmp[4][4];

       for(i=0;i<4;i++)

              for(j=0;j<4;j++)

                     tmp[i][j]=Will[j][3-i];

    //整理,为了移动的需要

       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=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;i<m_RowCount;i++)

       {

              //检查要不要消行

              flag=true;

              for(int j=0;j<m_ColCount;j++)

                     if(Russia[i][j]==0)

                            flag=false;

              //如果要

              if(flag==true)

              {

                     m++;

                     for(int k=i;k>0;k--)

                     {

                            //上行给下行

                            for(int l=0;l<m_ColCount;l++)

                            {

                                   Russia[k][l]=Russia[k-1][l];

                            }

                     }

                     //第一行为零

                     for(int l=0;l<m_ColCount;l++)

                     {

                            Russia[0][l]=0;

                     }

              }

       }

   

  //消行,表示运动方块已经停止

  //必须生成新的运动方块

       DrawWill();

       //加分

       switch(m)

       {

       case 1:

              m_Score++;

              break;

       case 2:

              m_Score+=3;

              break;

       case 3:

              m_Score+=6;

              break;

       case 4:

              m_Score+=10;

              break;

       default:

              break;

       }

       //速度赋值

       m_Speed=m_Score/100;

      

       for(i=0;i<4;i++)

              for(int j=0;j<4;j++)

                     if(Now[i][j]==1)

                            //到了顶点

                            if(Russia[i+NowPosition.x][j+NowPosition.y]==1)

                            {

                                   end=true;

                                   AfxMessageBox("游戏结束!");

                                   return;

                            }

}

    这个函数容易看懂。一点要说的是,既然要判断消行,证明当前方块已经停止,所以无论如何,都必须在这个时候调用一下DrawWill()函数,生成新的当前运动方块。

过界或重叠:

如果方块移动时出界,或者和已经有了的方块重叠,应该取消操作。

//是否遇到了边界或者有其他方块档住了

bool CRussia::Meet(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)

              {

                     switch(direction)

                     {

                     case 1:    //左移

                            if((p.y+j-1)<0) goto exit;

                            if(Russia[p.x+i][p.y+j-1]==1) goto exit;

                            break;

                     case 2://右移

                            if((p.y+j+1)>=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   暂停

添加菜单如下(ID_MENU_PAUSE)菜单id,关联一个键盘上的热键F3,

,添加函数如下:

            

       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);

}

   

l       热键

如上图,打开Accelerator,添加如上ID号,选择如上ID号和热键就行了。

l       艺术字

  看了我的封面上的艺术字,是否有些心动,用VC++怎么设置出这样的艺术字?我的答案是这不是用VC++做的,它只是一张位图。不过,这张位图是我自己做的,怎么做?我可以说一下:

  利用Word里面的艺术字,做好之后,把它拷贝到位图上就行了

[课程]Linux pwn 探索篇!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//