Android游戏开发之2048

这次是一个简单的2048游戏,设计比较粗糙,还可以往上面添加音乐、表情之类功能的代码,

一、 先来看看游戏的最终效果:
在这里插入图片描述
游戏图标的设计:
在这里插入图片描述

二、代码的主要设计:
在这里插入图片描述在这里插入图片描述

三、关键代码部分

Card部分:

类Card继承了FrameLayout,目的是作为游戏中的卡片。卡片数字和样式的实现:

public void
setNum(int num) {

        this.num = num;

 

        if (num<=0) {

            label.setText("");

        }else{

            label.setText(num+"");

        }

 

        switch (num) {

        case 0:

           
label.setBackgroundColor(0x00000000);//透明色

            break;

        case 2:

           
label.setBackgroundColor(0xffeee4da);

            break;

        case 4:

           
label.setBackgroundColor(0xffede0c8);

            break;

        case 8:

           
label.setBackgroundColor(0xfff2b179);

            break;

        case 16:

           
label.setBackgroundColor(0xfff59563);

            break;

            ……

             default:

            label.setBackgroundColor(0xff3c3a32);

            break;

        }

}

num<=0表明是空白方格。当前位置上如果没有card,则使用num<=0的card进行替代。card
0没有label,同时底色为透明。除了card 0之外,card 2之后的卡片都有对应的颜色和数字。

AnimLayer部分:

类AnimLayer继承了FramLayout,用于动画展示。在极客学院安卓2048最主要由两个动画:卡片移动和卡片出现。

卡片出现:

//目标卡片

public void
createScaleTo1(Card target){

    //缩放

     ScaleAnimation sa = new
ScaleAnimation(0.1f, 1, 0.1f, 1, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);

    sa.setDuration(100);

    target.setAnimation(null);

    target.getLabel().startAnimation(sa);

   }

卡片移动:

使用ArrayList
cards用于管理临时卡片的创建和回收(避免每次创建临时卡片时创建新的对象);创建一个临时卡片,从卡片from移动到卡片to,当完成动画之后将临时卡片设为不可见,并使用cards回收该卡片。

创建卡片:

private Card getCard(int num){

   Card c;

   if (cards.size()>0) {

      c = cards.remove(0);

   }else{

      c = new Card(getContext());

      addView(c);

   }

   c.setVisibility(View.VISIBLE);

   c.setNum(num);

   return c;

}

创建卡片时,如果cards不为空,则从cards队首取出一张临时卡片。(这里认为使用LinkedList更加适合临时卡片管理队列)

回收卡片:

private void recycleCard(Card c){

   c.setVisibility(View.INVISIBLE);

   c.setAnimation(null);

   cards.add(c);

}

回收卡片将当前卡片设为不可见,并加入到cards中。

卡片移动:

public
void createMoveAnim(final Card from,final Card to,int fromX,int toX,int
fromY,int toY){

    //临时卡片

    final Card c = getCard(from.getNum());

 

    //设置布局

    LayoutParams lp = new
LayoutParams(Config.CARD_WIDTH, Config.CARD_WIDTH);

    lp.leftMargin = fromX*Config.CARD_WIDTH;

    lp.topMargin = fromY*Config.CARD_WIDTH;

    c.setLayoutParams(lp);

 

    if (to.getNum()<=0) {

       
to.getLabel().setVisibility(View.INVISIBLE);

    }

    //从from卡片位置移动到to卡片

    TranslateAnimation ta = new
TranslateAnimation(0, Config.CARD_WIDTH*(toX-fromX), 0,
Config.CARD_WIDTH*(toY-fromY));

    ta.setDuration(25);

    ta.setAnimationListener(new
Animation.AnimationListener() {

 

        @Override

        public void onAnimationStart(Animation
animation) {}

 

        @Override

        public void onAnimationRepeat(Animation
animation) {}

 

        //动画结束,将临时卡片回收

        @Override

        public void onAnimationEnd(Animation
animation) {

           
to.getLabel().setVisibility(View.VISIBLE);

            recycleCard(c);

        }

    });

    c.startAnimation(ta);

 

  }

GameView部分:

GameView继承了GridLayout,包含了界面和游戏逻辑两个部分。这里介绍界面。

界面中比较重要的内容就是手势识别,用于操控格子的移动:

private
void initGameView(){

    setColumnCount(Config.LINES);

    setBackgroundColor(0xffbbada0);

    setOnTouchListener(new
View.OnTouchListener() {

 

        private float startX,startY,offsetX,offsetY;

 

        @Override

        public boolean onTouch(View v,
MotionEvent event) {

 

            switch (event.getAction()) {

                case MotionEvent.ACTION_DOWN://按下坐标

                    startX = event.getX();

                    startY = event.getY();

                    break;

                case MotionEvent.ACTION_UP:

                    offsetX =
event.getX()-startX;

                    offsetY =
event.getY()-startY;

                    if
(Math.abs(offsetX)>Math.abs(offsetY)) {

                        if (offsetX<-5) {

                            swipeLeft();

                        }else if (offsetX>5)
{

                            swipeRight();

                        }

                    }else{

                        if (offsetY<-5) {

                            swipeUp();

                        }else if (offsetY>5)
{

                            swipeDown();

                        }

                    }

                    break;

            }

            return true;//listener已经处理了事件

        }

    });

     }

使用了View.OnTouchListener来侦听触摸事件:计算按下和抬起来时offsetX和offsetY,预测手势的移动。

游戏实现的原理部分:

调用函数initGameView()完成游戏初始化:

private void initGameView(){

   
setColumnCount(Config.LINES);//设置行数量

    setBackgroundColor(0xffbbada0);

 

 

   
setOnTouchListener(new View.OnTouchListener() {

        }

    });

}

设置控件的方格数量,随后设置了控件北京,最后注册了刚才分析过的触摸事件监听器。此时游戏已经准备好了,正式开始。

开始游戏:

函数startGame();正式开始游戏,首先向方格内随机写入两个方块:

public void startGame(){

 

    MainActivity
aty = MainActivity.getMainActivity();

   
aty.clearScore();

   
aty.showBestScore(aty.getBestScore());

 

    for (int y =
0; y < Config.LINES; y++) {

        for (int x
= 0; x < Config.LINES; x++) {

           
cardsMap[x][y].setNum(0);

        }

    }

 

   
addRandomNum();

   
addRandomNum();

}

这个函数addRandomNum()向游戏面板内随机加入两个方块,开始游戏:

private void addRandomNum(){

    //private
List<Point> emptyPoints = new ArrayList<Point>(); 

   
emptyPoints.clear();

 

    //将所有空格子搜集起来

    for (int y =
0; y < Config.LINES; y++) {

        for (int x
= 0; x < Config.LINES; x++) {

            if
(cardsMap[x][y].getNum()<=0) {

               
emptyPoints.add(new Point(x, y));

            }

        }

    }

 

    if
(emptyPoints.size()>0) {

        //随机位置生成一个card

        Point p =
emptyPoints.remove((int)(Math.random()*emptyPoints.size()));

        int num =
Math.random()>0.1?2:4;

       
cardsMap[p.x][p.y].setNum(num);

       
MainActivity.getMainActivity().getAnimLayer().createScaleTo1(cardsMap[p.x][p.y]);

    }

}

函数addRandomNum()向面板中空的格子中随机生成一个卡片。首先搜集面板中所有空的位置,搜集到一个List中,最后生成随机数,随机生成一个数字,并完成生成动画。

2048游戏通过游戏中所有的方格朝某个方向移动,合并相同数字的方块。有四个函数负责移动,分别是上下左右,这里只分析一个方向。

private void swipeLeft(){

 

    boolean merge
= false;//是否合并卡片, 1空卡片和已有卡片合并 2两个数字相同的卡片合并

 

    for (int y =
0; y < Config.LINES; y++) {//对所有列

        for (int x
= 0; x < Config.LINES; x++) {

            //检查当前点的右侧是否有非空卡片(非空:num>=2)

            for
(int x1 = x+1; x1 < Config.LINES; x1++) {

                if
(cardsMap[x1][y].getNum()>0) {//如果右边有非空卡片

 

                   
if (cardsMap[x][y].getNum()<=0) {//当前坐标上没有格子(空卡片和已有卡片合并)

 

                       
MainActivity.getMainActivity().getAnimLayer().createMoveAnim(cardsMap[x1][y],cardsMap[x][y],
x1, x, y, y);

 

                       
cardsMap[x][y].setNum(cardsMap[x1][y].getNum());

                       
cardsMap[x1][y].setNum(0);

 

                       
x--;//和空卡片合并,还需要从当前位置计算(否则:|0|2|2|2|左移之后变为|2|2|2|0|)

                       
merge = true;

 

                   
}else if (cardsMap[x][y].equals(cardsMap[x1][y])) {

                       
MainActivity.getMainActivity().getAnimLayer().createMoveAnim(cardsMap[x1][y],
cardsMap[x][y],x1, x, y, y);

                       
cardsMap[x][y].setNum(cardsMap[x][y].getNum()*2);

                       
cardsMap[x1][y].setNum(0);

 

                       
MainActivity.getMainActivity().addScore(cardsMap[x][y].getNum());

                       
merge = true;

                   
}

 

                   
break;

                }

            }

        }

    }

 

    //只要有任意一行发生过卡片移动,则需要产生新的卡片

    if (merge) {

 

       
addRandomNum();

       
checkComplete();//判断当前游戏是否失败

    }

}

左移,针对面板中所有列,将每行的方块向左移动。在两种情况发生卡片合并:

1 当前位置为空卡片,右侧为非空卡片,合并后当前位置卡片Num为右侧卡片,右侧卡片清零。

2 当前位置为非空卡片,右侧卡片数值和它相等,合并后当前位置卡片数量翻倍,右侧卡片清零。

从游戏角度来讲:1 对应卡片单纯的移动,2 对应两张相同卡片的合并。因此,只要发生卡片实质上的移动,就应该随机再生产一个卡片,调用addRandomNum()。

游戏结束的判断:

每次发生卡片移动,都要检查游戏还能否继续,是否已经结束。函数checkComplete()完成游戏失败(感觉叫做checkFailure()更好)的检查:

private void checkComplete(){

 

    boolean
complete = true;

 

ALL:

    for (int y =
0; y < Config.LINES; y++) {

        for (int x
= 0; x < Config.LINES; x++) {

            //满足任意两个条件,游戏就可以继续:1 有空的格子,2 有可以合并的卡片

            if
(cardsMap[x][y].getNum()==0||//1 有多余空间

                   
(x>0&&cardsMap[x][y].equals(cardsMap[x-1][y]))||//2 和左面相等

                   
(x<Config.LINES-1&&cardsMap[x][y].equals(cardsMap[x+1][y]))|//2
和右面相等

                   
(y>0&&cardsMap[x][y].equals(cardsMap[x][y-1]))||//2 和上面相等

                   
(y<Config.LINES-1&&cardsMap[x][y].equals(cardsMap[x][y+1])))
{//2 和下面相等

 

               
complete = false;

               
break ALL;

        }

    }

    if (complete)
{

        new
AlertDialog.Builder(getContext()).setTitle("你好").setMessage("游戏结束").setPositiveButton("重新开始",
new DialogInterface.OnClickListener() {

 

           
@Override

            public
void onClick(DialogInterface dialog, int which) {

               
startGame();

            }

        }).show();

    }

 

}

游戏可以继续的两个条件:有空的格子,或者还有能够合并的卡片。

四、部分界面设计的代码
在这里插入图片描述

<LinearLayout

   
android:layout_width="fill_parent"

   
android:layout_height="wrap_content"

    android:orientation="horizontal"
>

    <TextView

       
android:layout_width="wrap_content"

       
android:layout_height="wrap_content"

       
android:text="@string/score"

       
android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView

       
android:id="@+id/tvScore"

       
android:layout_width="0dp"

       
android:layout_height="wrap_content"

       
android:layout_weight="1"

       
android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView

        android:layout_width="wrap_content"

       
android:layout_height="wrap_content"

       
android:text="@string/bestscore"

       
android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView

       
android:id="@+id/tvBestScore"

        android:layout_width="0dp"

       
android:layout_height="wrap_content"

       
android:layout_weight="1"

       
android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>

<Button

   
android:id="@+id/btnNewGame"

   
android:layout_width="fill_parent"

   
android:layout_height="wrap_content"

    android:background="#0000"

   
android:text="@string/newgame"

    android:textSize="20dp"

    android:textColor="#deb887"/>

在这里插入图片描述
在这里插入图片描述

关于这次2048游戏的一个简单心得体会:

这次是一个简单的2048游戏,关于这个Android的APP程序,这个程序基本实现了2048的要求,在游戏界面,点击重新开始,能刷新界面,出现另外的两个卡片。对于手指的触碰,在卡片的代码中,设计判断滑动的方向,让卡片做出相应的改变。

游戏过程中,只有卡片相邻的位置没有重合的时候才会结束,但点击重新开始也会刷新界面。当整个界面都没有重合的时候,弹出结束界面,可以点击重新开始。

这个程序在设计的时候,遇到点击重新开始时,有些卡片的颜色,不会刷新到初始界面,而是停留到上一个界面。在寻找了相关的资料后,解决了这一个问题。游戏比较粗糙,还可以添加音乐,表情之类的功能。