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游戏拆解_附上我最真诚的心得】
推荐阅读
- 游戏IP(立足于玩家情感的粉丝经济)
- 人生游戏--是游戏,还是人生()
- 「按键精灵安卓版」关于全分辨率脚本的一些理解(非游戏app)
- (小说)月流水几亿的火爆游戏养成记
- 游戏治愈了我无聊之症
- 生活与游戏日记(第182篇)(〔成长瞬间〕关注解决问题2019.10)
- 我妈让我不要玩游戏多做点事
- 牛逼!C++开发的穿越丛林真人游戏,游戏未上线就有百万人气
- 《暴走江湖》-单机休闲挂机游戏
- 游戏|2022年如何学习前端前沿技术,破卷而出()