2048游戏开发

游戏布局的设计
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout    
xmlns:android="http://schemas.android.com/apk/res/android"    
android:layout_width="match_parent"    
android:layout_height="match_parent"    
android:orientation="vertical"    >    
<LinearLayout       
 android:layout_width="match_parent"     
android:layout_height="wrap_content"        
android:orientation="horizontal">      
  <TextView         
   android:layout_width="wrap_content"            
android:layout_height="wrap_content"            
android:text="@string/score"/>   
     <TextView        
    android:id="@+id/show_score"            
android:layout_width="wrap_content"            
android:layout_height="wrap_content" />   
 </LinearLayout>   
 <!-- 铺满剩余空间-->
   <!-- 将类和xml绑定到一起-->    
<com.jiangjiang.game2048.GameView
 android:layout_width="match_parent" 
android:layout_height="0dp"      
android:layout_weight="1"     
android:id="@+id/gameView">    
</com.jiangjiang.game2048.GameView>
</LinearLayout>
实现2048游戏的主类
  • 新建一个类GridView,继承自GridLayout
    从xml资源中能够访问,添加能够传入的构造方法,三个构造方法。
  • 初始化的方法,从哪个构造方法中都能执行到。
  • 绑定xml,拷贝类的全路径到xml文件中
public class GameView extends GridLayout {    
        public GameView(Context context) {        
                    super(context);        
                    initGameView();    
        }    
         public GameView(Context context, AttributeSet attrs) 
          {
               super(context, attrs);
                initGameView();
            } 
         public GameView(Context context, AttributeSet attrs, int defStyleAttr)
         {
                  super(context, attrs, defStyleAttr); 
                   initGameView();
          }
}

android平台的触控交互设计
  • 纪录用户手指按下和离开的位置,即可判断用户的意图
  • onTouch()方法的返回值一定是True;如果是false的话,只会侦听到一个TouchDown的事件。TouchMove和TouchUp事件是侦听不到的。返回false,是通知系统我的TouchDown没有触发成功,然后后续事件是不会触发的。
    private float startX,startY,offsetX,offsetY;//纪录x、y 轴的偏移
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //纪录用户手指按下和离开的位置,即可判断用户的意图
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                startX = event.getX();//纪录x的位置
                startY = event.getY();//纪录y的位置
                break;
            case MotionEvent.ACTION_UP:
                offsetX = event.getX()-startX;//计算x的偏移量
                offsetY = event.getY()-startY;//计算y的偏移量
                if (Math.abs(offsetX)>Math.abs(offsetY))
                //判断x的绝对值大还是y的绝对值,x>y说明是水平方向
                 {
                    if(offsetX<-5)//向左,给一定的误差范围5
                    {
                        swipeLeft();
                    }else if (offsetX > 5)//向右
                     {
                         swipeRight();
                    }
                }else{
                    if (offsetY<-5)//向上
                    {
                        swipeUp();
                    }else if(offsetY > 5)//向下
                    {
                        swipeDown();
                    }
                }
                break;
        }
        return true;//一定要返回true
    }});
游戏卡片类的实现
  • 将游戏的方格抽象成卡片
public class Card extends FrameLayout {
    public Card(Context context) {
        super(context);
        label = new TextView(getContext());
        label.setTextSize(32);
        label.setBackgroundColor(0x33ffffff);
        label.setGravity(Gravity.CENTER);
        LayoutParams lp = new LayoutParams(-1,-1);
        lp.setMargins(10,10,0,0);//设置字之间的间隔
        addView(label,lp);
        setNum(0);//默认情况设置0
    }
    private int num = 0;//与卡片绑定的number
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
        if(num <= 0 )
        {
            label.setText("");
        }else {
            label.setText(num + "");//setText的参数是字符串
        }
    }
    public boolean equals(Card c) {
        return getNum() == c.getNum();//判断卡片是否相同
    }
    private TextView label;
}
添加卡片类

将方块抽象成卡片

public class Card extends FrameLayout {
    public Card(Context context) {
        super(context);
        label = new TextView(getContext());//初始化呈现文字的textview
        label.setTextSize(32);
        label.setBackgroundColor(0x33ffffff);//设置文本框的背景
        label.setGravity(Gravity.CENTER);
        LayoutParams lp = new LayoutParams(-1,-1);
        //填充满整个父级容器
        lp.setMargins(10,10,0,0);//设置卡片之间的间隔
        addView(label,lp);//添加进
        setNum(0);//默认的number
    }
    private int num = 0;//与卡片绑定的number
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
        if(num <= 0 )
        {
            label.setText("");
        }else {
            label.setText(num + "");
            //setText的参数是字符串。所以不能直接传num进去
        }
    }
    //判断卡片是否相同
    public boolean equals(Card c) {
        return getNum() == c.getNum();
    }
    private TextView label;
}
添加卡片
  • 每一种手机的宽高都不相同,为了让卡片的大小适应手机,所以需要动态的计算宽高,这样子在任何手机看起来卡片都铺满整个手机
  • 动态计算,需要重写父类的方法 onSetChanged()
  • 为了不让手机屏幕发生水平直立的变化,需要设置一下:

android:screenOrientation="portrait"//屏幕是竖直的,这样子onSetChanged()方法就只会在布局创建的时候被执行。

//动态的计算卡片的宽高
@Overrideprotected
 void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    int cardWidth = (Math.min(w,h)-10)/4;
  //卡片的宽高,求宽高的最小值,减一个数字是为了在屏幕边缘留下空隙
    addCard(cardWidth,cardWidth);//添加卡片
    startGame();
}
  • 添加卡片
private void addCard(int cardWidth,int cardHeight){
    Card c ;
    //添加16张卡片
    for(int y = 0;y<4;y++)//四列
    {
        for(int x = 0;x<4;x++)//四行
        {
            c = new Card(getContext());
            c.setNum(0);//初始时全部添加0
            addView(c,cardWidth,cardHeight);//添加到当前的GridVIew中
            cardsMap[x][y] = c;//将创建的卡片纪录到二维数组中
        }
    }
}
  • 需要显示4列:

setColumnCount(4);//说明GridLayout 是4列的

在游戏中添加随机数
private void addRandomNum(){
    emptyPoints.clear();
    for(int y = 0;y<4;y++)//对所有的卡片进行遍历
    {
        for(int x = 0;x<4;x++) 
       {
            if (cardsMap[x][y].getNum()<= 0) //空点
           {
                emptyPoints.add(new Point(x,y)); //空点才能添加数字
           }
        }
    }
    Point p = emptyPoints.remove((int)(Math.random()*emptyPoints.size()));//随机移除一个点
    //将这个点对应的值设置成2,或者4,比例是9:1
    cardsMap[p.x][p.y].setNum(Math.random()>0.1?2:4);//2:4 = 9:1
}
  • 开始游戏
    首先是清理元素。将卡片中的元素清除
    然后是添加随机数
private void startGame(){
    MainActivity.getMainActivity().clearScore();
    for(int y = 0;y<4;y++)
    {
        for(int x = 0;x<4;x++)
        {
            cardsMap[x][y].setNum(0);
        }
    }
    addRandomNum();//要显示两个数字,所以要添加两次
    addRandomNum();
}
实现2048 的逻辑
  • 往左滑的效果
private void swipeLeft(){
    boolean merge = false;
    for(int y= 0;y<4;y++)
    {
        for(int x = 0;x<4;x++)//一行一行遍历
        {
            //从当前位置往右遍历
            for(int x1 = x+1;x1<4;x1++)
            {
                if (cardsMap[x1][y].getNum()>0)//如果右边不为空
                {
                    if (cardsMap[x][y].getNum()<=0)//如果当前位置的卡片为空
                    {
                        //将右边的值放到右边去
                        cardsMap[x][y].setNum(cardsMap[x1][y].getNum()) ;
                        cardsMap[x1][y].setNum(0);
                        x--;//再遍历一次
                       merge = true;
                    }else if(cardsMap[x][y].equals(cardsMap[x1][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();
    }
}
游戏的计分
  • 有合并就有添加分数。再有合并的完成之后,数字是几就加几分。
  • 在游戏开始的时候要清零
public MainActivity(){
    mainActivity = this;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    showScore = (TextView)findViewById(R.id.show_score);
}
public  void clearScore(){//将score重新归0
    score = 0;
    showScore();
}
private void showScore(){//呈现分数
    showScore.setText(score+"");
}
public void addScore(int s){//添加score
    score += s;
    showScore();
}
private int score = 0;
private TextView showScore;
检查游戏的结束
  • 卡片没有空位置
  • 没有任意两个相邻的位置上的数字是相同的
private void checkComplete(){
    boolean complete = true;
    ALL:
    for(int y = 0;y<4;y++)
    {
        for(int x = 0;x<4;x++)
        {
            if (cardsMap[x][y].getNum() == 0||
                    (x>0&&cardsMap[x][y].equals(cardsMap[x-1][y]))||
                    (x<3&&cardsMap[x][y].equals(cardsMap[x+1][y]))||
                    (y>0&&cardsMap[x][y].equals(cardsMap[x][y-1]))||
                    (y<3&&cardsMap[x][y].equals(cardsMap[x][y+1]))
                    )
            {
                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();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,716评论 0 33
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,531评论 18 399
  • //出自51博客:www.Amanda0928.51.com 第一章 一、选择题 1.B; (typedef ,t...
    Damongggggg阅读 11,084评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,559评论 18 139
  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,609评论 0 89