Android實(shí)現(xiàn)2048小游戲
本文實(shí)例介紹了Android實(shí)現(xiàn)2048小游戲的相關(guān)代碼,分享給大家供大家參考,具體內(nèi)容如下
根據(jù)界面,主要實(shí)現(xiàn)4*4的格子方塊比較麻煩,其他的都挺簡單的.總體為實(shí)現(xiàn)4*4的格子,自定義GridLayout,并在其中添加觸摸監(jiān)聽事件,進(jìn)行一系列的操作,從而實(shí)現(xiàn)游戲的邏輯,最后再添加動畫效果即可完成.
下面是設(shè)計(jì)思路:
一.GameView的設(shè)計(jì)
首先自定義一個(gè)類,繼承GridLayout,添加兩個(gè)構(gòu)造方法
public class GameView extends GridLayout { //兩個(gè)必要的構(gòu)造方法 public GameView(Context context) { super(context); initView(); } public GameView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } }
接下來在initView()中實(shí)現(xiàn)設(shè)置GridLayout為四列,并且添加觸摸事件監(jiān)聽.(監(jiān)聽方法還可以重寫onTouchEvent(),返回值為true即可),判斷觸摸方向,主要是通過x軸和y軸的偏移量的比較
//初始化變量的方法 public void initView(){ //設(shè)置只有四列 setColumnCount(4); //設(shè)置監(jiān)聽事件 setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: setX = event.getX(); setY = event.getY(); break; case MotionEvent.ACTION_UP: offsetX = event.getX() - setX; offsetY = event.getY() - setY; //判斷滑動方向 if (Math.abs(offsetX) >= Math.abs(offsetY)) { if (offsetX > 0) { swipright(); } else if (offsetX < 0) { swipleft(); } } else { if (offsetY > 0) { swipdown(); } else if (offsetY < 0) { swipup(); } } break; } return true; } }); }
監(jiān)聽事件實(shí)現(xiàn)后先放在那里,接下來把4*4的里面每個(gè)小格子設(shè)計(jì)成小卡片,每個(gè)卡片就是一個(gè)TextView,卡片設(shè)計(jì)很簡單,需要什么就添加什么,默認(rèn)數(shù)字為0,這個(gè)時(shí)候代表是空值,也就是空卡片.
public class Card extends FrameLayout { public Card(Context context) { super(context); tvCard = new TextView(getContext()); tvCard.setTextSize(40f); tvCard.setGravity(Gravity.CENTER); LayoutParams lp = new LayoutParams(-1,-1); lp.setMargins(15,15,0,0); addView(tvCard, lp); } //卡片上的數(shù)字 private int num; private boolean is2048 = true; private void judgeIs2048(int num){ if (is2048){ if (2048==num){ Toast.makeText(getContext(),"恭喜趙瑩達(dá)到2048",Toast.LENGTH_LONG).show(); is2048 = false; } } } public int getNum() { return num; } public void setNum(int num) { this.num = num; if (num<=0){ tvCard.setText(""); }else { //這里傳進(jìn)去的是字符串因此需要加上空字符 tvCard.setText(num+""); } switch (num) { case 0: tvCard.setBackgroundColor(0x33ffffff); break; case 2: tvCard.setBackgroundColor(0xffeee4da); break; case 4: tvCard.setBackgroundColor(0xffede0c8); break; case 8: tvCard.setBackgroundColor(0xfff2b179); break; case 16: tvCard.setBackgroundColor(0xfff59563); break; case 32: tvCard.setBackgroundColor(0xfff67c5f); break; case 64: tvCard.setBackgroundColor(0xfff65e3b); break; case 128: tvCard.setBackgroundColor(0xffedcf72); break; case 256: tvCard.setBackgroundColor(0xffedcc61); break; case 512: tvCard.setBackgroundColor(0xffedc850); break; case 1024: tvCard.setBackgroundColor(0xffedc53f); break; case 2048: tvCard.setBackgroundColor(0xffedc22e); break; default: tvCard.setBackgroundColor(0xff3c3a32); break; } judgeIs2048(num); } //判斷是否相等,用于合并 public boolean equals(Card o) { return getNum()==o.getNum(); } //用于顯示數(shù)字 private TextView tvCard; public TextView getTvCard() { return tvCard; } }
卡片設(shè)計(jì)就需要添加到GameView里面,這個(gè)時(shí)候重寫onSizeChanged()函數(shù),這個(gè)在程序打開的時(shí)候運(yùn)行一次,通過他來動態(tài)設(shè)計(jì)卡片大小,并且添加卡片和開始游戲.
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, w, oldw, oldh); Config.CARD_WIDTH = (Math.min(w,h)-10)/4; AddCard(Config.CARD_WIDTH); StartGame(); }
添加卡片,一開始全設(shè)置為0,也就是全部添加空卡片
//添加卡片 private void AddCard(int CARD_WIDTH){ Card c; for (int x = 0;x<4;x++){ for (int y = 0;y<4;y++){ c = new Card(getContext()); c.setNum(0); addView(c, CARD_WIDTH, CARD_WIDTH); cardMap[y][x] = c; } } }
游戲開始需要隨機(jī)添加兩張卡片,數(shù)值2或者4,出現(xiàn)比率9:1
//開始游戲 public void StartGame(){ for (int y = 0;y<4;y++){ for (int x = 0;x<4;x++){ cardMap[y][x].setNum(0); } } AddRandomCard(); AddRandomCard(); }
隨機(jī)添加卡片設(shè)計(jì)
//添加隨機(jī)卡片 private void AddRandomCard(){ CardPoint.clear(); for (int y = 0;y<4;y++){ for (int x = 0;x<4;x++){ if (cardMap[x][y].getNum()<=0){ CardPoint.add(new Point(x,y)); } } } //把一張空卡片換成帶數(shù)字的 Point p = CardPoint.remove((int)(Math.random()*CardPoint.size())); cardMap[p.x][p.y].setNum(Math.random()>0.1?2:4); MainActivity.getMainActivity().getAnimLayer().createScaleTo1(cardMap[p.x][p.y]); }
這樣大體框架就設(shè)計(jì)好了
接下來是滑動事件,這里只舉例左滑
private void swipleft(){ boolean status = false; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { for (int x1 = x+1; x1 < 4; x1++) { if (cardMap[x1][y].getNum()>0) { if (cardMap[x][y].getNum()<=0) { MainActivity.getMainActivity().getAnimLayer().createMoveAnim(cardMap[x1][y],cardMap[x][y], x1, x, y, y); cardMap[x][y].setNum(cardMap[x1][y].getNum()); cardMap[x1][y].setNum(0); x--; status = true; }else if (cardMap[x][y].equals(cardMap[x1][y])) { MainActivity.getMainActivity().getAnimLayer().createMoveAnim(cardMap[x1][y], cardMap[x][y],x1, x, y, y); cardMap[x][y].setNum(cardMap[x][y].getNum() * 2); cardMap[x1][y].setNum(0); MainActivity.getMainActivity().addScore(cardMap[x][y].getNum()); status = true; } break; } } } } if (status){ AddRandomCard(); checkGame(); } }
每次添加卡片還需要判斷是否結(jié)束游戲
//結(jié)束游戲 private void checkGame(){ boolean complete = true; ALL: for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { if (cardMap[x][y].getNum()==0|| (x>0&&cardMap[x][y].equals(cardMap[x-1][y]))|| (x<3&&cardMap[x][y].equals(cardMap[x+1][y]))|| (y>0&&cardMap[x][y].equals(cardMap[x][y-1]))|| (y<3&&cardMap[x][y].equals(cardMap[x][y+1]))) { complete = false; break ALL; } } } if (complete) { Toast.makeText(getContext(), "游戲結(jié)束" + MainActivity.getMainActivity().getScore(), Toast.LENGTH_LONG).show(); } }
設(shè)計(jì)總體上框架就是上面說的那些.
二.動畫效果
動畫效果主要是創(chuàng)建,移動,合并這三個(gè)效果,因此重寫個(gè)繼承FrameLayout的class,覆蓋到游戲界面上,這樣的目的可以通過MainActivity中實(shí)例化當(dāng)前這個(gè)類,然后可以操作其方法,然后通過滑動來設(shè)置動畫
public class AnimLayer extends FrameLayout { public AnimLayer(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public AnimLayer(Context context, AttributeSet attrs) { super(context, attrs); } public AnimLayer(Context context) { super(context); } 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.getTvCard().setVisibility(View.INVISIBLE); } TranslateAnimation ta = new TranslateAnimation(0, Config.CARD_WIDTH*(toX-fromX), 0, Config.CARD_WIDTH*(toY-fromY)); ta.setDuration(100); ta.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationEnd(Animation animation) { to.getTvCard().setVisibility(View.VISIBLE); recycleCard(c); } }); c.startAnimation(ta); } 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; } private void recycleCard(Card c){ c.setVisibility(View.INVISIBLE); c.setAnimation(null); cards.add(c); } private List<Card> cards = new ArrayList<Card>(); 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.getTvCard().startAnimation(sa); } }
最后主布局文件如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#fffaf8ef" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <LinearLayout android:layout_marginTop="15dp" android:orientation="horizontal" android:gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ff776e65" android:text="@string/title" android:textSize="50sp"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:orientation="horizontal" android:layout_marginTop="10dp" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ff776e65" android:layout_marginLeft="30dp" android:textSize="35sp" android:text="@string/Score"/> <TextView android:id="@+id/tvscore" android:layout_marginLeft="20dp" android:textSize="25sp" android:textColor="#ff776e65" android:layout_width="70dp" android:layout_height="37dp" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/startgame" android:layout_marginLeft="40dp" android:background="#ffbbada0" android:textSize="15sp" android:text="@string/start"/> </LinearLayout> <FrameLayout android:id="@+id/gameContainer" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1"> <develop.niuli.com.game.GameView android:layout_marginTop="40dp" android:id="@+id/Gridlayout" android:layout_width="match_parent" android:background="#ffbbada0" android:layout_height="350dp"> </develop.niuli.com.game.GameView> <develop.niuli.com.game.AnimLayer android:id="@+id/animLayer" android:layout_width="match_parent" android:layout_height="match_parent"> </develop.niuli.com.game.AnimLayer> </FrameLayout> </LinearLayout>
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Android中Retrofit 2.0直接使用JSON進(jìn)行數(shù)據(jù)交互
本篇文章主要介紹了Android中Retrofit 2.0直接使用JSON進(jìn)行數(shù)據(jù)交互,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08Android7.0版本影響開發(fā)的改進(jìn)分析
這篇文章主要介紹了Android7.0版本影響開發(fā)的改進(jìn),總結(jié)分析了Android7.0版本中比較常見的開發(fā)注意事項(xiàng)與操作技巧,需要的朋友可以參考下2017-11-11Android 采用AOP方式封裝6.0權(quán)限管理的方法
這篇文章主要介紹了Android 采用AOP方式封裝6.0權(quán)限管理的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04Android App開發(fā)中Gradle構(gòu)建過程的配置方法
這篇文章主要介紹了Android App開發(fā)中Gradle構(gòu)建過程的配置方法,包括在Gradle中配置manifest的方法,需要的朋友可以參考下2016-06-06Android基于PhotoView實(shí)現(xiàn)的頭像/圓形裁剪控件
這篇文章主要給大家介紹了關(guān)于Android基于PhotoView實(shí)現(xiàn)的頭像/圓形裁剪控件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07Android中獲得手機(jī)屏幕大小實(shí)現(xiàn)代碼
這篇文章主要介紹了Android中獲得手機(jī)屏幕大小實(shí)現(xiàn)代碼,Android開發(fā)中經(jīng)常需要獲得屏幕的寬高,本文直接封裝成一個(gè)工具類,需要的朋友可以參考下2015-06-06Android編程實(shí)現(xiàn)兩個(gè)Activity之間共享數(shù)據(jù)及互相訪問的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)兩個(gè)Activity之間共享數(shù)據(jù)及互相訪問的方法,簡單分析了Android中Activity數(shù)據(jù)共享與訪問的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11