前言
前段时间对java的基础学习之后,开始着手于安卓的学习。感谢极客学院的视频教程中关于2048游戏开发的完整教程。我在此项目的基础上对界面进行了优化,添加了颜色,新游戏按钮,增加了当前成绩和最好成绩模块等。现将这个项目中的一些知识点和遇到问题进行总结,欢迎各位进行交流~~~。下面就是我初步完成之后的游戏界面。
关于程序运行逻辑
首先建立了一个Card类,这个类的作用是设置每个格子的属性。主要设置了字体大小,背景颜色,字体居中,设置了边界。我这边将上下左右都设置为了5。除此之外还有几个public函数主要是getNum()和setNum(int num)。在setNum(int num)中我设置了当num大于4的时候,字体颜色设置为白色。主要代码如下:
package com.example.game2048; import android.content.Context; import android.view.Gravity; import android.widget.FrameLayout; import android.widget.TextView; public class Card extends FrameLayout { public Card(Context context) { super(context); lable = new TextView(getContext()); lable.setTextSize(30); lable.setBackgroundColor(0x33ffffff); lable.setGravity(Gravity.CENTER); LayoutParams lp = new LayoutParams(-1, -1); lp.setMargins(5, 5, 5, 5); addView(lable, lp); setNum(0); } private int num = 0; public int getNum() { return num; } public void setNum(int num) { this.num = num; if (num <= 0) lable.setText(""); else { if (num <= 4) { lable.setTextColor(0xff000000); } else { lable.setTextColor(0xffffffff); } lable.setTextSize(30); lable.setText(num + ""); } } public boolean equals(Card o) { return getNum() == o.getNum(); } private TextView lable; }
关于游戏界面的初始化:建立一个GameView类用于实现程序的运行逻辑。其中通过函数initGameView()来对GridLayout进行划分为4*4的方格,通过设置setColumnCount(4);来实现四列。再通过函数setOnTouchListener来检测用户的触屏操作,通过记录用户按下屏幕的(x,y)和结束时的(x,y),通过计算x,y的偏移量来判断用户的行为。当x的偏移量大于y的偏移量时,主要就是判断左右;反之就是判断前后。其代码如下:
private void initGameView() {
setColumnCount(4);
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();
}
}
colorChanged();
break;
default:
break;
}
return true;
}
});
}
关于Card的添加:通过重写GridLayout中的函数onSizeChanged(int w, int h, int oldw, int oldh)来进行Card添加。选取GridLayout中长宽较小的一个除以四得到每一个Card的边长大小;在addCard中通过两层循环完成Card的添加,每添加一个调用addView函数。其代码实现如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int cardWidth = (Math.min(w, h) - 10) / 4;
addCards(cardWidth, cardWidth);
startGame();
}
private void addCards(int cardWidth, int cardHeight) {
Card c;
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
c = new Card(getContext());
c.setNum(0);
addView(c, cardWidth, cardHeight);
cardMap[x][y] = c;
}
}
}
关于添加随机数:通过函数emptyPoints.remove((int) (Math.random() * emptyPoints.size()));来实现产生数字的坐标点,然后通过三目运算符来设置在此坐标上生成的数字是2还是4,这里2和4产生的比例是9:1,其代码如下:
private void addRandomNum() {
emptyPoints.clear();
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
if (cardMap[x][y].getNum() <= 0) {
emptyPoints.add(new Point(x, y));
}
}
}
Point p = emptyPoints
.remove((int) (Math.random() * emptyPoints.size()));
cardMap[p.x][p.y].setNum(Math.random() > 0.1 ? 2 : 4);
}
关于方向操作的处理:方向操作主要是通过三层循环,将每行(列)的数字向此方向移动,每次移动一位,如果可以合并就进行合并,并删除后一个位置上的数据,以此类推,直到所有数字都移动到此方向并且都不能合并为止,如果有合并的产生就表示有新的空余位置出现,就需要再生成一个新的数据,此外还需要检测游戏的进展(1)如果出现2048,出现游戏成功的界面,此时弹出一个对话框显示你的成绩,此时用户可以选择继续游戏和再来一局;(2)如果没有可以移动的数据时,表示游戏结束,弹出游戏失败的对话框,并可以选择重新开始。其代码如下(以向左滑动为例):
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 (cardMap[x1][y].getNum() > 0) {
if (cardMap[x][y].getNum() <= 0) {
cardMap[x][y].setNum(cardMap[x1][y].getNum());
cardMap[x1][y].setNum(0);
merge = true;
x--;
} else if (cardMap[x][y].equals(cardMap[x1][y])) {
cardMap[x][y].setNum(cardMap[x][y].getNum() * 2);
cardMap[x1][y].setNum(0);
MainActivity.getMainActivity().addScore(
cardMap[x][y].getNum());
merge = true;
}
break;
}
}
}
}
if (merge) {
addRandomNum();
checkComplete();
}
}
private void checkComplete() {
boolean failcomplete = true, successcomplete = false;
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]))) {
failcomplete = false;
break ALL;
}
}
}
if (failcomplete) {
new AlertDialog.Builder(getContext())
.setTitle("你好")
.setMessage("游戏已结束")
.setPositiveButton("重来一次",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
// TODO Auto-generated method stub
startGame();
}
})
.setNegativeButton("退出",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
// TODO Auto-generated method stub
MainActivity.getMainActivity().finish();
}
}).show();
}
ALL: for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
if (cardMap[x][y].getNum() == 2048) {
successcomplete = true;
count++;
break ALL;
}
}
}
if (successcomplete && (count == 1)) {
new AlertDialog.Builder(getContext())
.setTitle("恭喜")
.setMessage(
"你已完成2048,得分为:"
+ MainActivity.getMainActivity().getScore()
+ ",游戏结束")
.setPositiveButton("再来一次",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
// TODO Auto-generated method stub
startGame();
}
})
.setNegativeButton("继续游戏",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
// TODO Auto-generated method stub
}
}).show();
}
}
关于颜色的设置:同过调用setBackgroundColor就可以对一个卡片进行颜色设置我们通过两层循环就可以实现对所有非0的卡片进行配色。其代码如下:
private void colorChanged() {
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) {
cardMap[x][y].setBackgroundColor(Num2color(cardMap[x][y]
.getNum()));
}
}
}
private int Num2color(int num) {
int color = 0;
switch (num) {
case 2:
color = 0xffeee4da;
break;
case 4:
color = 0xffede0c8;
break;
case 8:
color = 0xfff2b179;
break;
case 16:
color = 0xfff59563;
break;
case 32:
color = 0xfff67c5f;
break;
case 64:
color = 0xfff65e3b;
break;
case 128:
color = 0xffedcf72;
break;
case 256:
color = 0xffffff00;
break;
case 512:
color = 0xffccff00;
break;
case 1024:
color = 0xff99ff00;
break;
case 2048:
color = 0xff66ff00;
break;
case 4096:
color = 0xff663333;
break;
case 8192:
color = 0xff0000ff;
break;
default:
color = 0x33ffffff;
break;
}
return color;
}
以上就是此项目主要的功能的实现逻辑。
- 关于布局安排
2.1 游戏界面布局
从游戏的界面上可以看出界面布局主要分为三个部分,大框架是LinearLayout,三个部分是数据显示部分,游戏操作部分,和按钮部分。主要布局如下所示
2.2 当前成绩与最好成绩的布局
如上图所示,我在游戏界面上方建了一个水平布局的LinearLayout,中间又添加了两个竖直布局的LiearLayout,在这两个LinearLayout中添加了两个TextView分别用于显示分数和最佳成绩,然后分别对两个LinearLayout进行上色,再通过xml中android:layout_marginRight=”10dp”和android:layout_marginLeft=”10dp”两句,将两个LinearLayout之间的距离设置为20dp。这样就实现了分数显示部分的布局。
2.3 按钮“新游戏”的功能实现
按钮的作用是重新开始游戏,即清当前页面,然后重新开始游戏界面,其实就是GameView中函数startGame()的功能。所以我们需要在案件检测的onclick中添加函数startGame(),但是按键检测只能在activity中,即本项目的MainActivity中实现。那么如何调用GameView中的函数呢。
首先我在GameView中定义了:
public static GameView gameView = null;
然后在GameView的三个构造方法中加入gameView = this;
public GameView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
gameView = this;
initGameView();
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
gameView = this;
initGameView();
}
public GameView(Context context) {
super(context);
gameView = this;
initGameView();
}
再将函数startGame()变成public的形式,这样就可以在外部调用了。
public void startGame()
最后在主activity的oncreate中监听按钮事件即可:
findViewById(R.id.Restart).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
GameView.getGameView().startGame();
}
});
下一步计划
a.下来的计划是设置一个成绩记录的界面,当每次完成一个2048时提醒一次,也可以记录最大分值。
b.软件图标
涉及的知识大概有activity的跳转,数据库。
源代码
欢迎交流~~~