2048游戏拆解_附上我最真诚的心得

经过对2048游戏的拆解,发现了里面越来越多的好点子,拆游戏,拆得是游戏的思路,为了拆一个游戏,我把每一行代码基本上都写了注释,所以就算是新手也能轻易的了解一个游戏的来龙去脉。
此游戏的代码需要的点子有很多:
1.View代码布局
2.SharedPreferences
3.requestWindowFeature
4.2048游戏代码逻辑分析
还有很多知识点我就不一一罗列了,比如Point的使用,移动动画 TranslateAnimation,缩放动画ScaleAnimation,对View.OnTouchListener()的重写等等。

现在开始上代码加注解:
主类,此类主要用来初始化
MainActivity:

public class MainActivity extends Activity { /* * * QQ:845145080 */ public MainActivity() { mainActivity = this; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); // 去掉标题 setContentView(R.layout.activity_main); root = (LinearLayout) findViewById(R.id.container); // 通过id找到Linearlayout root.setBackgroundColor(0xfffaf8ef); // 给背景加颜色tvScore = (TextView) findViewById(R.id.tvScore); // 分数的显示 tvBestScore = (TextView) findViewById(R.id.tvBestScore); // 最高分数的显示gameView = (GameView) findViewById(R.id.gameView); // 将GameView这个类做成了一个控件,在主文件中找到这个控件btnNewGame = (Button) findViewById(R.id.btnNewGame); // 重新开始按钮 btnNewGame.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { gameView.startGame(); // GameView类中定义的方法,清除分数,将所有的格子里的显示为0 } }); animLayer = (AnimLayer) findViewById(R.id.animLayer); // 找到布局文件中的控件 } @Override public boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } public void clearScore() { // 点击重新开始时,会把分数一栏归零 score = 0; showScore(); } public void showScore() { tvScore.setText(score + ""); // 显示分数 } public void addScore(int s) { // 如果Card移动的时候遇到一个有值的,就将自己的值和遇到的值相加 score += s; showScore(); int maxScore = Math.max(score, getBestScore()); // 将现在的值和存储的最大的值进行比较,取出两个中较大的一个 saveBestScore(maxScore); // 将最大的那个值存到sd卡中 showBestScore(maxScore); // 在显示最大的那个TextView中显示出来 } public void saveBestScore(int s) { // 保存数据,将最大的值存到sd卡中 Editor e = getPreferences(MODE_PRIVATE).edit(); e.putInt(SP_KEY_BEST_SCORE, s); e.commit(); } public int getBestScore() { // MODE_PRIVATE为能读能写 return getPreferences(MODE_PRIVATE).getInt(SP_KEY_BEST_SCORE, 0); // SP_KEY_BEST_SCORE="bestScore" // getInt用键值对 } public void showBestScore(int s) { // 最高纪录的那个地方会显示出保存的最好的成绩 tvBestScore.setText(s + ""); } public AnimLayer getAnimLayer() { return animLayer; } private int score = 0; private TextView tvScore, tvBestScore; private LinearLayout root = null; private Button btnNewGame; private GameView gameView; private AnimLayer animLayer = null; private static MainActivity mainActivity = null; public static MainActivity getMainActivity() { return mainActivity; } public static final String SP_KEY_BEST_SCORE = "bestScore"; }



此类主要用来描述动画的效果
AnimLayout:

public class AnimLayer extends FrameLayout { public AnimLayer(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initLayer(); } public AnimLayer(Context context, AttributeSet attrs) { super(context, attrs); initLayer(); } public AnimLayer(Context context) { super(context); initLayer(); } private void initLayer() { } public void createMoveAnim(final Card from, final Card to, int fromX, int toX, int fromY, int toY) {final Card c = getCard(from.getNum()); //将要移动的Card从原来的地方消失 LayoutParams lp = new LayoutParams(Config.CARD_WIDTH, Config.CARD_WIDTH); //CARD_WIDTH=0 lp.leftMargin = fromX * Config.CARD_WIDTH; //将card里面的布局的宽和高变成0; lp.topMargin = fromY * Config.CARD_WIDTH; c.setLayoutParams(lp); //c在执行这个方法之后,view的大小将发生改变if (to.getNum() <= 0) {//如果移动到的位置的Card为0,则将此Card的TextView隐藏。 to.getLabel().setVisibility(View.INVISIBLE); }/* TranslateAnimation为位移动画 * TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) * * 在上面的时候,因为自定义的布局的大小发生了改变,所以会自动调用onSizeChanged()方法,Config.CARD_WIDTH = (Math.min(w, h) - 10) / Config.LINES; * */ 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.getLabel().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) {//因为原来要移动的Card已经移走了,剩下的地方就是空了,将剩下的地方添加到list中,list是专门存储空的card的 c.setVisibility(View.INVISIBLE); c.setAnimation(null); cards.add(c); } private List cards = new ArrayList(); /* * ScaleAnimation(float fromX, float toX, float fromY, float toY,int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) * *float fromX 动画起始时 X坐标上的伸缩尺寸 float toX 动画结束时 X坐标上的伸缩尺寸 float fromY 动画起始时Y坐标上的伸缩尺寸 float toY 动画结束时Y坐标上的伸缩尺寸 int pivotXType 动画在X轴相对于物件位置类型 float pivotXValue 动画相对于物件的X坐标的开始位置 int pivotYType 动画在Y轴相对于物件位置类型 float pivotYValue 动画相对于物件的Y坐标的开始位置 Animation.RELATIVE_TO_SELF为相对于自身 Animation.RELATIVE_TO_PARENT为相对于父控件 * */ 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,//相对于Card自身的0.5f就是Card的中心 0.5f); sa.setDuration(100); //持续时间为0.5s target.setAnimation(null); target.getLabel().startAnimation(sa); //给Card对应的TextView控件添加动画 }}


此类主要用来实现关键性功能,如判断手移动的方向

GameView:

public class GameView extends LinearLayout { public GameView(Context context) { super(context); initGameView(); } public GameView(Context context, AttributeSet attrs) { super(context, attrs); initGameView(); } private void initGameView() { setOrientation(LinearLayout.VERTICAL); setBackgroundColor(0xff9B30FF); 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://如果是点击下来,获取点击地点的x和y的坐标 startX = event.getX(); startY = event.getY(); break; case MotionEvent.ACTION_UP://离开屏幕时的位置,获取离开屏幕时的位置,并获得位移量 offsetX = event.getX() - startX; //手指离开时的X坐标减去按下去时X的坐标 offsetY = event.getY() - startY; // 手指离开时 的Y坐标减去按下去时的Y的坐标if (Math.abs(offsetX) > Math.abs(offsetY)) {//取offsetxX和offsetY的绝对值 if (offsetX < -5) { swipeLeft(); //滑动向左 } else if (offsetX > 5) { swipeRight(); //向右划 } } else { if (offsetY < -5) { swipeUp(); //向上划 } else if (offsetY > 5) { swipeDown(); //向下划 } }break; } return true; //这个地方如果是false的话,手指抬起时是不会得到坐标的 } }); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {//在定义的view的大小改变时,系统会自动调用这个方法 super.onSizeChanged(w, h, oldw, oldh); Config.CARD_WIDTH = (Math.min(w, h) - 10) / Config.LINES; addCards(Config.CARD_WIDTH, Config.CARD_WIDTH); startGame(); } private void addCards(int cardWidth, int cardHeight) {Card c; LinearLayout line; LinearLayout.LayoutParams lineLp; for (int y = 0; y < Config.LINES; y++) { line = new LinearLayout(getContext()); lineLp = new LinearLayout.LayoutParams(-1, cardHeight); addView(line, lineLp); for (int x = 0; x < Config.LINES; x++) { c = new Card(getContext()); line.addView(c, cardWidth, cardHeight); cardsMap[x][y] = c; } } } 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++) {//LINES=4 cardsMap[x][y].setNum(0); //最开始的时候,将所有的卡片的值都设为0, } }addRandomNum(); //一开始的时候会产生两个card addRandomNum(); } private void addRandomNum() {emptyPoints.clear(); for (int y = 0; y < Config.LINES; y++) { for (int x = 0; x < Config.LINES; x++) { if (cardsMap[x][y].getNum() <= 0) {//如果Card为空的话,则将这个card记录下来 emptyPoints.add(new Point(x, y)); } } }if (emptyPoints.size() > 0) { /* * Math.random()会随机产生一个0-1之间的小数,假如emptyPoints.size()等于16 * (int) (Math.random() * emptyPoints.size())会产生一个0到16之间的数。 * * */ Point p = emptyPoints.remove((int) (Math.random() * emptyPoints.size())); //从Card为0的卡片中随机移除一个 cardsMap[p.x][p.y].setNum(Math.random() > 0.1 ? 2 : 4); //被移除的card被2或者4替换,且出现2的几率更大一些 MainActivity.getMainActivity().getAnimLayer() .createScaleTo1(cardsMap[p.x][p.y]); //给新生成的Card添加动画效果 } } //滑动向左移动运算 private void swipeLeft() {boolean merge = false; for (int y = 0; y < Config.LINES; y++) {//LINES=4 for (int x = 0; x < Config.LINES; x++) {for (int x1 = x + 1; x1 < Config.LINES; x1++) {//向左滑动时,将全部的数组遍历一遍,如果找到不为0的,且其左边为0时 if (cardsMap[x1][y].getNum() > 0) {if (cardsMap[x][y].getNum() <= 0) {//cardsMap[x][y].getNum()获取card上面的数字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--; 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); //这个地方可以修改数字的增加原本是2 cardsMap[x1][y].setNum(0); MainActivity.getMainActivity().addScore( cardsMap[x][y].getNum()); merge = true; }break; } } } }if (merge) { addRandomNum(); checkComplete(); } } //滑动向右移动运算 private void swipeRight() {boolean merge = false; for (int y = 0; y < Config.LINES; y++) { for (int x = Config.LINES - 1; x >= 0; x--) {for (int x1 = x - 1; x1 >= 0; 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++; 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(); } } //滑动向上移动运算 private void swipeUp() {boolean merge = false; for (int x = 0; x < Config.LINES; x++) { for (int y = 0; y < Config.LINES; y++) {for (int y1 = y + 1; y1 < Config.LINES; y1++) { if (cardsMap[x][y1].getNum() > 0) {if (cardsMap[x][y].getNum() <= 0) { MainActivity .getMainActivity() .getAnimLayer() .createMoveAnim(cardsMap[x][y1], cardsMap[x][y], x, x, y1, y); cardsMap[x][y].setNum(cardsMap[x][y1].getNum()); cardsMap[x][y1].setNum(0); y--; merge = true; } else if (cardsMap[x][y].equals(cardsMap[x][y1])) { MainActivity .getMainActivity() .getAnimLayer() .createMoveAnim(cardsMap[x][y1], cardsMap[x][y], x, x, y1, y); cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2); cardsMap[x][y1].setNum(0); MainActivity.getMainActivity().addScore( cardsMap[x][y].getNum()); merge = true; }break; } } } }if (merge) { addRandomNum(); checkComplete(); } } //滑动向下移动运算 private void swipeDown() {boolean merge = false; for (int x = 0; x < Config.LINES; x++) { for (int y = Config.LINES - 1; y >= 0; y--) {for (int y1 = y - 1; y1 >= 0; y1--) { if (cardsMap[x][y1].getNum() > 0) {if (cardsMap[x][y].getNum() <= 0) { MainActivity .getMainActivity() .getAnimLayer() .createMoveAnim(cardsMap[x][y1], cardsMap[x][y], x, x, y1, y); cardsMap[x][y].setNum(cardsMap[x][y1].getNum()); cardsMap[x][y1].setNum(0); y++; merge = true; } else if (cardsMap[x][y].equals(cardsMap[x][y1])) { MainActivity .getMainActivity() .getAnimLayer() .createMoveAnim(cardsMap[x][y1], cardsMap[x][y], x, x, y1, y); cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2); cardsMap[x][y1].setNum(0); MainActivity.getMainActivity().addScore( cardsMap[x][y].getNum()); merge = true; }break; } } } }if (merge) { addRandomNum(); checkComplete(); } } private void checkComplete() {boolean complete = true; ALL: for (int y = 0; y < Config.LINES; y++) { for (int x = 0; x < Config.LINES; x++) { /* 1.卡片的数字为0时 3.卡片的数字不为0时,x在0-3之间,且x卡片的数字和左右相邻的卡片的数字相同时 4.卡片的数字不为0时,0 0 && cardsMap[x][y].equals(cardsMap[x - 1][y])) || (x < Config.LINES - 1 && cardsMap[x][y] .equals(cardsMap[x + 1][y])) || (y > 0 && cardsMap[x][y].equals(cardsMap[x][y - 1])) || (y < Config.LINES - 1 && cardsMap[x][y] .equals(cardsMap[x][y + 1]))) { complete = false; break ALL; //跳出循环 } } }if (complete) {//给出错误提示信息 new AlertDialog.Builder(getContext()) .setTitle(R.string.tishi) .setMessage(R.string.falses) .setPositiveButton(R.string.queding, new DialogInterface.OnClickListener() {@Override public void onClick(DialogInterface dialog, int which) { startGame(); } }).show(); } } private Card[][] cardsMap = new Card[Config.LINES][Config.LINES]; //point是一个二维的,数组中的x和y可以确定出来一个card,emptyPoints可以将这些card存起来 private List emptyPoints = new ArrayList(); }


代码的源码下载地址:http://download.csdn.net/detail/cuicanxingchen123456/9212117


【2048游戏拆解_附上我最真诚的心得】

    推荐阅读