这类游戏大家肯定都玩过,一个很有趣味性的小游戏。操作简单,具有一定的逻辑性。很适合无聊的时候消遣时间。:)
首先简单介绍下手机游戏的一般性开发过程。首先需要策划出一个游戏方案,也就是要给出一个游戏的整体形象。当然是想象中的。比如:游
戏题材,背景,操作方法,人物,与奖励机制。然后对这个策划方案进行可行性分析,包括技术,市场,可用资源等。下一步就是编码阶段,
一般编码和制作资源可以同步进行。 等程序和资源都弄好了,一个游戏的雏形就大致出来了。后面就是测试与移植等等工作。
我们要做的是让一只小松鼠推箱子,操作方法就是通过四方向键对松鼠进行上,下,左,右的移动。在移动过程中可以把碰到的箱子推到任意
一个方向。当然在游戏中需要设置一些障碍物,让游戏具备可玩性。游戏采用过关制,当松鼠把所有箱子推到指定的位置就算过关。所以每关
的场景要不同。这需要对每关的地图进行编排。好了,我们的游戏策划就算完成,嘿嘿!简单吧,想象是多么美好啊。
下面就没这么轻松了,我们要进行技术分析。就是具体的代码如何来实现的。首先我们来确定一下开发难点。对松鼠的操作很简单,就是四方
向移动,松鼠移动,箱子也移动,所以对按键处理也比较简单些。当箱子到达某个位置时,就会产生游戏过关事件。需要一个逻辑判断。那么
我们仔细想一下,这些所有的事件都发生在一张地图中。这张地图就包括了 箱子的初始化位置,箱子最终放置的位置,和障碍等。每一关地图
都要更换。这些位置也要变。所以我们发现每关的地图数据是最关键的。它决定了每关的不同场景和物体位置。好。那么我们就重点分析一下
地图。
我们把地图想象成一个网格,每个格子就是松鼠每次移动的步长,也是箱子移动的距离,这样问题就简化多了。首先我们设计一个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:[email protected]