贪吃蛇---毁墙模式详解

毁墙模式(罗利)

游戏思路:

毁墙模式,主要是蛇推动随机产生的炸弹,然后将墙炸毁,蛇必须在用完命数之前,炸完所有的墙,玩家赢得胜利。

1) 蛇头能够按照指定的方向进行移动,蛇身则是根据蛇头的移动方向进行移动,蛇每次移动一格,只可90度转换反向

2) 游戏中随机产生2个炸弹,但不可与游戏中已有物体的位置重叠,蛇可携带此炸弹去炸毁任意一块砖,蛇携带炸弹的同时,炸弹消失并产生新炸弹

3) 游戏中有4个会移动的“怪物”,在墙边的周围反复移动,蛇未携带着炸弹接触到他们就会被吃掉,命数减1;蛇携带着炸弹接触到他们,怪物必须躲避蛇,不能吃蛇;蛇的命数初始为5

4) 每次炸墙,与蛇对应的3块墙将被炸毁并消失,并且蛇和怪物的速度都会加快

5) 如果蛇在移动中蛇头撞到墙或障碍物或者蛇头撞到自己或没命数了,则游戏结束

6) 蛇炸完所有的墙,玩家赢得胜利

主要运用的技术:

布局设计、在画布上绘制图形、使用Bitmap加载图片、线程类的使用、利用Server来播放音乐背景、数据的处理等

一、 毁墙模式界面设计

1)设置墙图片的大小

mTileSize = a.getInt(R.styleable.TileView_tileSize, 12); 

2)加载图片,这里设置了一个标志flag,是用来确定是加载哪种图片的,如,如是墙的图片,则大小是mTileSize* mTileSize,如是蛇的图片,则其大小是墙的3倍,相关代码如下:

public void loadTile(int key,int id,int flag)

{

 int width=mTileSize;

 int height=mTileSize;

 Resources r = this.getContext().getResources();

 Drawable tile=r.getDrawable(id);

      Bitmap source = BitmapFactory.decodeResource(this.getResources(), id);     

if(flag==IMAGE_BOMB||flag==IMAGE_SNAKE||flag==IMAGE_EXPLOSION){

 width=3*mTileSize;

 height=3*mTileSize;  

}

if(flag==IMAGE_MONSTER){

 width=2*mTileSize;

 height=2*mTileSize;  

}  

 Bitmap  bitmap= Bitmap.createBitmap(width, height,Bitmap.Config.ARGB_8888);

Canvas canvas = new Canvas(bitmap);

tile.setBounds(0, 0,width, height);

tile.draw(canvas);

mTileArray[key] = bitmap;

}

无论图片大小如何,这里只要得到图片的起始坐标就将图片绘制出来,主要是用了下面代码来控制图片的大小

tile.setBounds(0, 0,width, height);

3)最后使用在画布上将图片都绘制出来

canvas.drawBitmap(mTileArray[mTileGrid[x][y]], mXOffset + x

*mTileSizemYOffset + y *mTileSizemPaint);

二、游戏程序设计

1)游戏中蛇可移动,主要是通过线程实现的,定义了一个线程类对象mRedrawHandler,并通过它的sleelp()方法延迟,并不断更新物体的位置产生动画效果,相关代码如下:

private RefreshHandler mRedrawHandler = new RefreshHandler(UpdateView.this);  //内部类

protected void update()                     //负责更新小蛇的移动轨迹

{  

if (mMode == RUNNING)

{

long now = System.currentTimeMillis();//读取系统时间

if (now - mLastMove > 300)//时间间隔超过小蛇移动延时时间,如退出

{      

clearTiles();

updateWalls();

updateBombs();

updateMonster();

//更新小蛇位置

updateSnake();    

mLastMove = now;

}    //产生动画效果

mRedrawHandler.sleep(mMoveDelay);

}

}

2)在更新蛇体时,将会判断游戏的下列各种情况

a检测小蛇是否撞到怪物:  蛇的任意部位(9个坐标)与4个怪物的任意重叠

  蛇未携带炸弹时,命数不小于0时,将蛇重新跳到屏幕的中间位置,命数减1,否则,游戏失败;

  蛇携带炸弹时, 怪物躲避蛇,不能吃蛇

相关代码如下 

 int snakelength = mSnakeTrail.size();  

for (int snakeindex = 0; snakeindex < snakelength; snakeindex++)

{  

   Coordinate c = mSnakeTrail.get(snakeindex);

      int monsterlength=mMonsterList.size();

   for (int index = 0; index <monsterlength; index++){

   Coordinate m =mMonsterList.get(index);

   ArrayList<Coordinate> monster = new ArrayList<Coordinate>();

  monster.add(m);

  monster.add(new Coordinate(m.x+1, m.y));

  monster.add(new Coordinate(m.x, m.y+1));

  monster.add(new Coordinate(m.x+1, m.y+1));  

      for(int i=0;i<monster.size();i++){

        if(judge(c,monster.get(i)))       

        {   

        if(!getBomb){

      if(snakeLife>0){       

     snakeLife--;     

     life.setText(snakeLife+"");      

     newHead=init();       

      return;

         }

       else{

             Log.e("monster","monster");

            setMode(LOSE);      

            clearRecord(record);

            getBomb=false;

            mMoveDelay=400;

            snakeLife=5;

            mScore=0;

                return;

         }

         }      

b检测小蛇是否撞到墙:蛇的任意部位与墙的坐标重叠

 蛇未携带炸弹时,该砖块已被炸过时,蛇可以从该墙穿出去,之间跳转到屏幕中间位置;否则命数减1,直到命数小于0时,游戏失败,结束

        蛇携带炸弹时,直接炸墙,蛇将反向移动,将被炸的砖块做标记,被炸到的砖消失

    相关代码:

   if ((newHead.x < 2) || (newHead.y < 2) || (newHead.x > mXTileCount - 3)

|| (newHead.y > mYTileCount - 3))

   {

   if(!getBomb){  

   if(empty(newHead))//从墙穿出去

   {

   newHead=init(); 

   }

   else  if(snakeLife>0){

     snakeLife--;

     life.setText(snakeLife+"");

     newHead=init();       

     }

       else{//游戏失败

            Log.e("wall","wall");

            setMode(LOSE);       

            clearRecord(record);

            snakeLife=5;

            mScore=0;

            mMoveDelay=400;

            return;

   }

   }

   else{//炸墙

   Coordinate c=null;

   if(newHead.x < 2){

     record[newHead.x-1][newHead.y-1]=1;

 record[newHead.x-1][newHead.y]=1;

 record[newHead.x-1][newHead.y+1]=1;

 c=new Coordinate(newHead.x-1,newHead.y);  

       }

       if(newHead.y < 2){

     record[newHead.x-1][newHead.y-1]=1;

 record[newHead.x][newHead.y-1]=1;

 record[newHead.x-1][newHead.y-1]=1;

 c=new Coordinate(newHead.x-1,newHead.y);

     }

     if(newHead.x > mXTileCount - 3){

     record[newHead.x+1][newHead.y-1]=1;

 record[newHead.x+1][newHead.y]=1;

 record[newHead.x+1][newHead.y+1]=1;

 c=new Coordinate(newHead.x-1,newHead.y-1);

     }

     if(newHead.y > mYTileCount - 3){

     record[newHead.x-1][newHead.y+1]=1;

 record[newHead.x][newHead.y+1]=1;

 record[newHead.x+1][newHead.y+1]=1;

 c=new Coordinate(newHead.x,newHead.y+1);

     }    

     mSnakeTrail.remove(0);      

     backSnake(c);

     mMoveDelay*=0.9;      

   }

  }  

  

c检测小蛇是否得到炸弹:蛇的任意部分坐标与炸弹坐标重叠

           若蛇未携带炸弹时,碰到炸弹,将炸弹的中间坐标作为蛇头,蛇携带此炸弹移动去炸墙

          若蛇已携带炸弹,不再携带炸弹,直接可越过此炸弹,蛇一次只能携带一个炸弹

相关代码如下

if(!getBomb){

    int bombcount = mBombList.size();

    boolean eatBomb=false;//只能携带一个炸弹     

   for (int bombindex = 0; bombindex < bombcount; bombindex++)

   {

   Coordinate c = mBombList.get(bombindex);       

       

    if(judge(c,newHead))  

      {

        eatBomb=true;

    getBomb=true;  

    mBombList.remove(c);

    addRandomBomb();

    break;

      }    

      }

     if(eatBomb&&getBomb)

    {

    mSnakeTrail.add(0, newHead);  

    }

     else{

mSnakeTrail.add(0, newHead);

mSnakeTrail.remove(mSnakeTrail.size() - 1);

     }     

}

else{

mSnakeTrail.add(0, newHead);

mSnakeTrail.remove(mSnakeTrail.size() - 1);

}

4)小蛇的移动

根据要移动的方向,是蛇的坐标向前移动一格,不同方向,蛇向前移动的坐标不同,相关代码如下:

protected Coordinate snakeMove(Coordinate head){

switch (mDirection)

{

case EAST:

{

head= new Coordinate(head.x + 1, head.y); 

break;

}

case WEST:

{  

head= new Coordinate(head.x - 1, head.y);

break;

}

case NORTH:

{

head=new Coordinate(head.x, head.y - 1);

break;

}

case SOUTH:

{  

head=new Coordinate(head.x, head.y + 1);

break;

}

   

   }

return head;

}

5)游戏中设有4个怪物和两个炸弹 

对于炸弹,通过随机产生坐标的方式来绘制出图形,必须在不能与已有物体重叠的条件下才可,因此进行了相关判断,每次蛇得到炸弹时,需新生成炸弹,相关代码如下

boolean found = false;

while (!found)

{

int newX = 1 + RNG.nextInt(mXTileCount - 4);

int newY = 1 + RNG.nextInt(mYTileCount - 4);

newCoord= new Coordinate(newX, newY);       

boolean collision = false;

int monsterlength =mMonsterList.size();//怪物坐标的个数

for (int index = 0; index <monsterlength; index++){

   if(mMonsterList.get(index )!=null)

   if(judge(newCoord,mMonsterList.get(index)))        

        {

      collision = true;

       break;

        }

}

int snakelength = mSnakeTrail.size();//蛇坐标的个数

for (int index = 0; index < snakelength; index++)

{   

if(judge(newCoord,mSnakeTrail.get(index)))

       {

     collision = true;

     break;

      }

}

found = !collision;

}

怪物是固定在墙的周围循环移动的,故起始坐标可固定,怪物是来保卫墙的,因此在蛇没有炸弹时可吃掉蛇

mMonsterList.add(new Coordinate(2,2 ));//左上角怪物

mMonsterList.add(new Coordinate(mXTileCount-4,5 ));//右上角  

mMonsterList.add(new Coordinate(2,mYTileCount-6));//左下角

mMonsterList.add(new Coordinate(mXTileCount-4,mYTileCount-4));//右下角

  6)设计了两个TextView对蛇的命数和玩家的得分进行了视觉化,每次蛇的命数和玩家的得分发生改变时,相应的在两个TextView中显示,相关代码:

life.setText(snakeLife+"");

score.setText(mScore+"");

显示效果如下:

7)数据的保存

a数组转换成简单的序列化的int数组

 由于蛇、炸弹和怪物的都是通过自定义类Coordinate坐标类来使用的,在保存时稍微有点复杂,故需将其转换成简单的数据类型,这样方便存储,其中数组偶位存x坐标,数组奇位y坐标,将坐标列表转换成数组的相关代码如下:

private int[] coordArrayListToArray(ArrayList<Coordinate> cvec)

{

int count = cvec.size();

int[] rawArray = new int[count * 2];//Coordinate保存为坐标,坐标有x,y2,故要乘2

for (int index = 0; index < count; index++)

{

Coordinate c = cvec.get(index);

//偶坐标存x坐标,奇坐标存y坐标

rawArray[2 * index] = c.x;

rawArray[2 * index + 1] = c.y;

}

return rawArray;

}

b、保存数据

将坐标转换成数组后,进行数据保存,这里采用Bundle来保存数据,一一将数据要保存的数据保存在Bundle中,代码如下:

public Bundle saveState()

{

Bundle map = new Bundle();  

map.putIntArray("mBombList", coordArrayListToArray(mBombList));

map.putIntArray("mMonsterList", coordArrayListToArray(mMonsterList));

map.putInt("mDirection", Integer.valueOf(mDirection));

map.putInt("mNextDirection", Integer.valueOf(mNextDirection));

map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));

map.putLong("mScore", Long.valueOf(mScore));

map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));

            map.putInt("snakeLife", snakeLife);

            map.putBoolean("getBomb", getBomb);

            map.putInt("monsterXDirection",monsterXDirection);

            map.putInt("monsterYDirection",monsterYDirection);

return map;

}

c读取保存在Bundle中的数据

coordArrayListToArray()的逆过程,用来读取保存在Bundle中的数据

private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray)

{

ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>(); 

int coordCount = rawArray.length;

for (int index = 0; index < coordCount; index += 2)

{

Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);

coordArrayList.add(c);

}

return coordArrayList;

}

d、读取游戏进度

 回复游戏数据,saveState()的逆过程 

public void restoreState(Bundle icicle)

{

setMode(PAUSE);

mBombList = coordArrayToArrayList(icicle.getIntArray("mBombList"));

mMonsterList = coordArrayToArrayList(icicle.getIntArray("mMonsterList"));

mDirection = icicle.getInt("mDirection");

mNextDirection = icicle.getInt("mNextDirection");

mMoveDelay = icicle.getLong("mMoveDelay");

mScore = icicle.getLong("mScore");

mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));

    snakeLife=icicle.getInt("snakeLife");

    getBomb=icicle.getBoolean("getBomb");

    monsterXDirection=icicle.getInt("monsterXDirection");

    monsterYDirection=icicle.getInt("monsterXDirection");

}

8) 事件监听

本程序主要是设计了返回菜单按钮和暂停(开始)按钮,并对其进行了事件监听,

返回菜单监听事件是利用IntentActivity间的跳转实现,返回到主菜单,相关代码:

Intent intent=new Intent();      

   intent.setClass(DestroyWallActivity.this, Snake.class);  

   DestroyWallActivity.this.startActivity(intent);

暂停(开始)事件主要是利用屏幕的是否失去焦点,使用代码setFocusable(true)来实现此功能

9)播放背景音乐

本游戏添加了游戏背景音乐,主要是将音乐文件加入到Server里面,启动Server时,即可播放音乐,加载MP3文件代码如下:

music = MediaPlayer.create(this, R.raw.music);

music.setLooping(true);

music.start();

 在AndroidManifest.xml声明

<service android:name=".MusicServer">

          <intent-filter>

              <action android:name="com.angel.Android.MUSIC"/>

             <category android:name="android.intent.category.default" />

            </intent-filter>

           </service>

              <service android:name=".ExplosionServer">          

              <action android:name="com.angel.Android.EMUSIC"/>

             <category android:name="android.intent.category.default" />            

           </service> 

 在Activity中启动音乐背景代码:startService(intent);

游戏样例:

心得:

    总的来说,这次参与设计贪吃蛇游戏的收获很大,达到了实训的目的。本次实训培养了团队合作精神,自己的Java语言设计能力得到了提高,体验了软件开发过程,培养了项目开发的分析能力和程序设计能力。

首先,从玩游戏到设计游戏的角色转化,体验软件开发过程,培养了项目开发的分析能力。

其次,培养了团队合作精神。整个程序开发过程中,小组成员团结合作,努力思考,认真讨论。可以说我们的程序开发小组,凝聚了更深的友情。

最后,最直接的收获就是在Android平台上用Java语言开发程序的能力。为以后真正进入社会工作奠定了基础。

本人是设计毁墙模式的程序,在此过程中,开始时对代码不是完全看懂,在网上查阅资料后,才开始都贪吃蛇经典游戏的代码初步掌握。了解后,自己初步想出了一个新模式,于是努力去完善这个模块的设计,之后的代码编写中,纠结于坐标的计算中,相对来说叫为复杂,花费了我比较久的时间才设计好,再整个程序调试时也出现了不少问题,大都是通过百度解决的,同组同学也给了我很多建议,相信本次游戏设计大家都学会了很多关于android的知识,也增加了大家合作的默契。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值