一步一步教你开发《松鼠推箱子》手机游戏

来源:互联网 发布:数据分析师简历 编辑:程序博客网 时间:2024/03/29 22:01

这类游戏大家肯定都玩过,一个很有趣味性的小游戏。操作简单,具有一定的逻辑性。很适合无聊的时候消遣时间。:)

首先简单介绍下手机游戏的一般性开发过程。首先需要策划出一个游戏方案,也就是要给出一个游戏的整体形象。当然是想象中的。比如:游

戏题材,背景,操作方法,人物,与奖励机制。然后对这个策划方案进行可行性分析,包括技术,市场,可用资源等。下一步就是编码阶段,

一般编码和制作资源可以同步进行。 等程序和资源都弄好了,一个游戏的雏形就大致出来了。后面就是测试与移植等等工作。


我们要做的是让一只小松鼠推箱子,操作方法就是通过四方向键对松鼠进行上,下,左,右的移动。在移动过程中可以把碰到的箱子推到任意

一个方向。当然在游戏中需要设置一些障碍物,让游戏具备可玩性。游戏采用过关制,当松鼠把所有箱子推到指定的位置就算过关。所以每关

的场景要不同。这需要对每关的地图进行编排。好了,我们的游戏策划就算完成,嘿嘿!简单吧,想象是多么美好啊。


下面就没这么轻松了,我们要进行技术分析。就是具体的代码如何来实现的。首先我们来确定一下开发难点。对松鼠的操作很简单,就是四方

向移动,松鼠移动,箱子也移动,所以对按键处理也比较简单些。当箱子到达某个位置时,就会产生游戏过关事件。需要一个逻辑判断。那么

我们仔细想一下,这些所有的事件都发生在一张地图中。这张地图就包括了 箱子的初始化位置,箱子最终放置的位置,和障碍等。每一关地图

都要更换。这些位置也要变。所以我们发现每关的地图数据是最关键的。它决定了每关的不同场景和物体位置。好。那么我们就重点分析一下

地图。

我们把地图想象成一个网格,每个格子就是松鼠每次移动的步长,也是箱子移动的距离,这样问题就简化多了。首先我们设计一个8*8的数据结

构。按照这样的框架来思考。 每个格子都会有哪些属性呢?首先就是格子的坐标,包括X,Y两个数值,还有一些地图的属性,比如这个格子是

否为障碍,是否为初始化的箱子位置,是否为箱子终点的位置。由于我们的数据结构是二维的,但是还有一维表示不出来,所以我们设计一个

三维数据:如下:

private static int[][][] map_data = new int[8][8][5]; 
//第一维  -  [8] 表示地图的高,由8个格子组成
               
//第二维 -  [8] 表示地图的宽,由8个格子组成
                

//第三维  - [0] 每个格子的X坐标
               
//        - [1] 每个格子的Y坐标
               
//        - [2] 是否为障碍 0表示空地 1表示障碍
               
//        - [3] 箱子的终点坐标,0不是终点坐标 1绿箱子 2 红箱子
               
//        - [4] 箱子的初始化位置 [0]不是初始化坐标  [1]绿箱子 2红箱子

地图大致就是这样的数据结构,我们设计两种颜色的箱子,这样增加一下游戏的难度。


好了,有了地图,我们的逻辑就可以实现了。利用一点时间,我把游戏的图片画好了,好在我是美术专业毕业,画这些小像素图还挺顺利。

^+^.

我们先把游戏的界面部分处理一下。界面就是菜单,进度条,还有游戏中的绘制。 菜单和进度条略过,因为这些可以自己设计的。

下面是画地图和松鼠.箱子以及指示终点位置的方法。

//画地图
private void drawMap(Graphics g)
{
  

 for(int m = 0 ; m < map_data.length ; m ++ ) // Y坐标
 {
  for(int n = 0 ; n < map_data[m].length; n++) // X坐标
  {
    
    
   if(map_data[n][m][2] == 1) //如果是空地
   {
    g.drawImage(map[0],map_data[n][m][0],map_data[n][m][1],0);
      

   }else if(map_data[n][m][2] == 0) //如果是障碍
   {
    g.drawImage(map[1],map_data[n][m][0],map_data[n][m][1],0);     

 
      
   }

   
  }
   
 }

}

map数组是只有两个元素的图片数组,里面有两幅图,分别为空地和障碍。


//画松鼠
private void drawSqu(Graphics g)
{
 g.drawImage(squirrel[sdir],map_data[( splace[1] )][( splace[0] )][0],map_data[( splace[1] )][( splace[0] )][1],0);
}


这里的splace[] 是松鼠的初始化位置,这个随每关的不同,也会有变化。
sdir 表示松鼠的方向,对应着squirre[] 数组中的松鼠图片

//画箱子
private void drawBoxs(Graphics g)
{
 for(int i = 0; i < nplace.length; i++)
 {
  g.drawImage(nut[(nplace[i][2]-1)],map_data[(nplace[i][1])][(nplace[i][0])][0],map_data[(nplace[i][1])]

[(nplace[i][0])][1],0);
  
 }
}

nplace[][]  是箱子的初始化位置坐标,第一维表示有几个箱子,第二维表示 X,Y坐标 和 颜色 属性
nut[] 是箱子图片数组

//画箱子终点位置的指示
private void drawNutDest(Graphics g)
{
 for(int i = 0; i < ndplace.length; i++)
 {
  g.drawImage(nut[(ndplace[i][2]+1)],map_data[(ndplace[i][1])][(ndplace[i][0])][0],map_data[(ndplace[i][1])]

[(ndplace[i][0])][1],0);
 }
}

我们对箱子重点的指示也采用图片,这样使画面效果更美观一些。
同样 ndplace[][]  和 nplace[][]  的属性都一样。 只不过表示终点坐标
nut[]数组的另外元素表示 指示重点坐标的图片。

 

以上就是我们的游戏主界面。看着挺简单的吧。呵呵。下面要实现操作控制方法与判断输赢的逻辑了。嘿嘿,要有耐心。

前面我们提到,当移动松鼠时,箱子就跟着动,那么我们就分别写两个方法,来移动他们。

/*移动松鼠*/
private static final void moveSqu()

 

 /*表示在松鼠的移动方向是否有箱子*/
 boolean hit = false ;
  
 /*指示一个箱子可以被移动*/
 boolean mf = false ;
  
 /*当为向上移动时*/
 if( moveS[0] )
 { 
  
  /*当向上移动时,调整图片为第二张松鼠图*/
  sdir = 2 ;  
   
  /*将可移动表示设置为假*/
  moveS[0] = false;
   
   
  /*查找是否有箱子在松鼠傍边*/
  for(int r=0; r < nplace.length; r++) 
  {
   /*如果有箱子*/
   if( ((splace[1] - 1) == nplace[r][1]) && (splace[0]  == nplace[r][0] )){
     
    /*移动箱子,并返回一个判断,表示是否可以继续被移动*/
    mf = moveBoxs(0,r);
    
     
    /*设置碰撞标志*/
    hit = true ;
   }
    
  }
  
  /*如果傍边没箱子,是空地*/
  if(  (map_data[(splace[1] - 1)][(splace[0])][2] == 0 ) && (!hit ) ) 
  {  
    /*移动松鼠*/
    splace[1] -= 1 ;
     
  }
  /*如果碰到箱子*/
  if(hit)
  {
   /*并且这个箱子可以被推动*/
   if(mf){
    /*移动松鼠*/
    splace[1] -= 1 ;
    /*设置碰撞为否*/
    hit = false ;
   }
  }
   
 }
 /*向下方向*/
 if( moveS[1] )
 {
  //debug("down");
  sdir = 3;
  moveS[1] = false;
   
  for(int r=0; r < nplace.length; r++)
  {
   if( ((splace[1] + 1) == nplace[r][1] ) && ( splace[0]  == nplace[r][0] )){
    mf = moveBoxs(1,r);
    hit = true ;      
   }
    
  }
  if( (map_data[(splace[1] + 1)][(splace[0])][2] == 0 ) && (!hit))
  {
   splace[1] += 1 ; 
    
  }
  if(hit)
  {
   if(mf)
   {
    splace[1]+=1;
    hit = false ;
   }
  }
   
   
   
   
 }
 /*向左方向*/
 if( moveS[2] )
 {
  //debug("left");
  sdir = 0;
  moveS[2] = false;
   
  for(int r=0; r < nplace.length; r++)
  {
   if( ((splace[0] - 1) == nplace[r][0]) && ( splace[1] == nplace[r][1] )){
    mf = moveBoxs(2,r);
    hit = true ;      
   }    
    
  }
  if( (map_data[(splace[1])][(splace[0] - 1)][2] == 0 ) && (!hit)){
   splace[0] -= 1 ;
    
  }
  if(hit)
  {
   if(mf)
   {
    splace[0] -= 1 ;
    hit = false;
   }
  }
   
   
 }
 /*向右方向*/
 if( moveS[3] )
 {
  //debug("right");
  sdir = 1 ;
  moveS[3] = false;
  for(int r=0; r < nplace.length; r++)
  {
   if( ((splace[0] + 1) == nplace[r][0] ) && ( splace[1] == nplace[r][1] )){
    mf = moveBoxs(3,r);
    hit = true ;
   }
    
  }
  if( (map_data[(splace[1])][(splace[0] + 1)][2] == 0) && (!hit)) {
   splace[0] += 1 ;
    
  }
  if(hit)
  {
   if(mf)
   {
    splace[0] += 1 ;
    hit = false ;
   }
  }
  
   
 }

  

}

上面这个方法看起来比较长,其实逻辑比较简单,就是分别对应四个方向的移动来判断。可以看向上方向移动的注释。moveS[]是个blooean数

组,用来表示松鼠的移动方向,这个数组会在keyPressed()被赋值,当某方向键按下时对应方向的标志就设置为真。下面的moveBoxs() 方法,是

判断某个箱子在被推动的方向上是否还有其它箱子阻碍,如果有就返回true.否则false.并且移动这个箱子。

 

/*
*
*方法功能:移动箱子
*
*参数: dir:被推动的方向 . nm: 表示哪个箱子
*
*返回值: 该箱子是否可以被推动
*/
private static final boolean moveNuts(int dir,int nm)
{
 /*指示另外的箱子在该箱子傍边*/
 boolean hit = false;
  
 /*临时变量*/
 boolean mf = false ;
 
 switch(dir)
 {
  case 0: //'up'

   /*对所有箱子坐标遍历*/
   for(int i = 0 ; i < nplace.length ; i++)
   {
    /*使用坐标判断,如果当前箱子要移动的方向上有其他箱子那么就标志上*/
    if( ((nplace[nm][1] - 1) == nplace[i][1] ) && ( nplace[nm][0] == nplace[i][0]))
     hit = true ;

   }
   /*如果为空地*/
   if( (map_data[(nplace[nm][1] - 1)][(nplace[nm][0])][2] == 0 ) && (!hit) )
   {
    /*移动这个箱子,并且将返回值设置为可以移动*/
    nplace[nm][1] -= 1 ;
    mf = true ;
   }
     
  break;
  case 1: //'down'
   for(int i = 0 ; i < nplace.length ; i++)
   {
    if( ((nplace[nm][1] + 1) == nplace[i][1] ) && ( nplace[nm][0] == nplace[i][0]))
     hit = true ;

   }
   if( (map_data[(nplace[nm][1] + 1)][(nplace[nm][0])][2] == 0) && (!hit) )
   {
    nplace[nm][1] += 1 ;      
    mf = true ;
   }
  break;
  case 2: //'left'
   for(int i = 0 ; i < nplace.length ; i++)
   {
    if( ((nplace[nm][1]) == nplace[i][1] ) && ( (nplace[nm][0] - 1) == nplace[i][0]))
     hit = true ;
   }
   if( (map_data[(nplace[nm][1])][(nplace[nm][0] - 1)][2] == 0) && (!hit))
   {
     nplace[nm][0] -= 1 ;
     mf = true ;
   }
  break;
  case 3: //'right'
   for(int i = 0 ; i < nplace.length ; i++)
   {
    if( ((nplace[nm][1]) == nplace[i][1] ) && ( (nplace[nm][0] + 1) == nplace[i][0]))
     hit = true ;

   }
     
   if( (map_data[(nplace[nm][1])][(nplace[nm][0] + 1)][2] == 0) && (!hit) )
   {
    nplace[nm][0] += 1 ;      
    mf = true ;
   }
  break;
 }

 return mf ;
}


这个方法跟判断松鼠是否可以移动原理一样,其实完全可以和移动松鼠的方法结合一起来处理,而且可以优化很多东西,但为了逻辑更清晰,

就分开来做。


/*判断过关*/
private static final boolean isWin()
{
 boolean temp = false;
 int nn = 0;
 for(int r = 0 ; r < nplace.length ; r++)
 {
  for(int m = 0 ; m < ndplace.length ; m++)
  {
   if( (nplace[r][0] == ndplace[m][0] ) && (nplace[r][1] == ndplace[m][1]))
    nn++;
  }
 }

 if(nn == nplace.length)
  temp = true;
 else
  temp = false;

 return temp;
}

判断过关的逻辑很简单,就是对目的地坐标与当前箱子坐标一一对照,如果所有箱子都已经被正确推到目的地,就算过关。


到现在,我们的游戏界面,和游戏逻辑实现就都完成了,感觉是不是很简单。呵呵,当然要完成整个游戏,还要写一些固定的方法。比如

keyPressed();获得按键值  paint() 来画图  线程run() 来实现刷屏幕等等。由于篇幅有限就不一一介绍了。下面附上一幅游戏完成的截图。

看看效果吧。

代码与图片下载:moveBox.rar (白天有效)

《松鼠推箱子》游戏截图

程序设计与图片制作:关文柏       Email:k7sem_88@hotmail.com