Android小專案–2048小游戲
文章目錄
- Android小專案--2048小游戲
- 1.摘要
- 2.實作的功能
- 3.完成的界面展示
- 4.功能具體實作的程序
- 1. 游戲面板GameView的設計(采用GridLayout布局)
- 2. Cell類的設計:Cell用來表示游戲中的小格子
- 3. 將cell添加到GameView中:
- 4. 游戲模式的改變:
- 5. 記錄當前分數及歷史最高分數
- 6. 判斷游戲是否結束
- 5. 總結
1.摘要
現如今,電子游戲已慢慢滲透進人們生活中,并在扮演著越來越重的角色,2048小游戲屬于益智類小游戲,它做到了娛樂性、趣味性、教育性相統一,益智類的游戲即是需要去開動大腦去思考從而獲得游戲的勝利,簡單的益智類游戲可以使玩家在娛樂中不斷的開發大腦,這樣一來就實作了在娛樂中學習,
這篇文章主要為大家介紹了Android實作2048小游戲的相關內容,感興趣的小伙伴們可以參考一下!
2.實作的功能
- 確認布局
- UI界面
- 2048游戲邏輯的實作
- 游戲界面:
- 基本的4×4格子
- 擴展的5×5格子
- 擴展的6×6格子
- 記錄當前得分和歷史最高分數
- 游戲模式:
- 經典模式:達到2048即為“成功”,游戲結束,
- 無限模式:沒有2048的上限,在沒有失敗的情況下,玩家可以一直玩下去.
- 最后游戲結束時“You Win!”或“You Lose!”的判定
3.完成的界面展示
!!



4.功能具體實作的程序
1. 游戲面板GameView的設計(采用GridLayout布局)
- 先自定義一個GameView類,繼承GridLayout,添加兩個構造方法(GridLayout布局是Android 4.0新增的布局,引入該布局極大地方便了Grid型別的布局開發,不熟悉該布局的讀者朋友可以在Android開發者網站上尋找相關的開發資料進行學習)
public class GameView extends GridLayout {
//兩個必要的構造方法
public GameView(Context context) {
super(context);
initView();
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
}
- 在initView()中定義格子的寬和高,并且添加觸摸事件監聽
public void initView(int mode) {
gameMode = mode;
canSwipe = true;
// 移除所有視圖,以便更改游戲難度
removeAllViews();
// 初始化格子
if (mode == Constant.MODE_CLASSIC) {
// 經典模式
gridColumnCount = Config.GRIDColumnCount;
} else if (mode == Constant.MODE_INFINITE) {
// 無限模式
gridColumnCount = 6;
}
cells = new Cell[gridColumnCount][gridColumnCount];
// 設定界面大小
setColumnCount(gridColumnCount);
// 獲取格子的寬
int cellWidth = getCellSize();
// 獲取格子的高
int cellHeight = getCellSize();
addCell(cellWidth, cellHeight);
startGame();
setOnTouchListener((v, event) -> {
// 通知父控制元件不要攔截此控制元件的onTouch事件
v.getParent().requestDisallowInterceptTouchEvent(true);
if (canSwipe) {
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;
// 判斷滑動方向
int orientation = getOrientation(offsetX, offsetY);
switch (orientation) {
case 0:
// 向右滑動
swipeRight();
break;
case 1:
// 向左滑動
swipeLeft();
break;
case 2:
// 向下滑動
swipeDown();
break;
case 3:
// 向上滑動
swipeUp();
break;
default:
break;
}
default:
break;
}
}
return true;
});
}
- 添加滑動事件(這里舉例上滑):
- 簡要說一下此處實作的游戲玩法演算法:首先用for回圈一行一行地去遍歷每一個cell,然后從當前的位置往右去遍歷,判斷如果獲取到了某一個值不是空的,此時有兩種情況,一是當前位置上的值是空的,此時把獲取到的值放到當前位置上,同時把獲取到的位置上的數字清掉;二是當前位置上的值不是空的,并且獲取到的值和當前位置上的值相同,則把合并這兩個卡片,把當前位置上的值乘以二,同時把獲取到的位置上的數字清掉,
- 還有一種情況是,如果我們當前位置上是空的,然后把右邊的值放到當前的位置上去了,此時繼續往后邊(右邊)去遍歷,后邊的位置還是空的,然后右邊又有一個數字和之前放過去的數字是一樣的情況的話,也是把它放到這個空位置上去了,這時會發生一個狀況:這兩張數字實際是一樣的,但是它們并不合并,為了避免這種情況的發生,我們再讓它去遍歷一次,即讓x-- ,這樣這個問題就解決了,
//上滑事件
private void swipeUp() {
// 判斷是否需要添加數字
boolean needAddDigital = false;
for (int i = 0; i < gridColumnCount; i++) {
for (int j = 0; j < gridColumnCount; j++) {
// 獲取當前位置數字
int currentDigital = cells[j][i].getDigital();
someData.add(currentDigital);
if (currentDigital != 0) {
// 記錄數字
if (recordPreviousDigital == -1) {
recordPreviousDigital = currentDigital;
} else {
// 記錄的之前的數字和當前數字不同
if (recordPreviousDigital != currentDigital) {
// 加入記錄的數字
dataAfterSwipe.add(recordPreviousDigital);
recordPreviousDigital = currentDigital;
} else {// 記錄的之前的數字和當前的數字相同
// 加入*2
dataAfterSwipe.add(recordPreviousDigital * 2);
// 記錄得分
recordScore(recordPreviousDigital * 2);
// 重置記錄數字
recordPreviousDigital = -1;
}
}
}
}
if (recordPreviousDigital != -1) {
dataAfterSwipe.add(recordPreviousDigital);
}
// 補0
for (int p = dataAfterSwipe.size(); p < gridColumnCount; p++) {
dataAfterSwipe.add(0);
}
// 若原始資料和移動后的資料不同,視為界面發生改變
if (!someData.equals(dataAfterSwipe)) {
needAddDigital = true;
}
someData.clear();
// 重新設定格子資料
for (int k = 0; k < dataAfterSwipe.size(); k++) {
cells[k][i].setDigital(dataAfterSwipe.get(k));
}
// 重置資料
recordPreviousDigital = -1;
dataAfterSwipe.clear();
}
if (needAddDigital) {
// 添加一個亂數字(2或4)
addDigital(false);
playSound();
}
judgeOverOrAccomplish();
}
- 判斷滑動方向:
/**
* 注:先依據在軸上滑動距離的大小,判斷在哪個軸上滑動
* @param offsetX 在X軸上的移動距離
* @param offsetY 在Y軸上的移動距離
* @return 滑動方向
* 注:0右滑、1左滑、2下滑、3上滑、-1未構成滑動
*/
private int getOrientation(float offsetX, float offsetY) {
// X軸移動
if (Math.abs(offsetX) > Math.abs(offsetY)) {
if (offsetX > MIN_DIS) {
return 0;
} else if (offsetX < -MIN_DIS) {
return 1;
} else {
return -1;
}
} else {// Y軸移動
if (offsetY > MIN_DIS) {
return 2;
} else if (offsetY < -MIN_DIS) {
return 3;
} else {
return -1;
}
}
}
2. Cell類的設計:Cell用來表示游戲中的小格子
- Cell表示游戲中移動的小格子,格子的顏色、顯示數字等屬性都在物件中進行設定,Cell類如下:
public class Cell extends FrameLayout {
//顯示數字的TextView
private TextView cellShowText;
//顯示的數字
private int digital;
public Cell(Context context) {
super(context);
}
public Cell(@NonNull Context context, int leftMargin, int topMargin, int bottomMargin) {
super(context);
init(context, leftMargin, topMargin, bottomMargin);
}
//初始化
private void init(@NonNull Context context, int leftMargin, int topMargin, int bottomMargin) {
cellShowText = new TextView(context);
// 不同難度設定不同字體大小
switch (Config.GRIDColumnCount) {
case 4:
cellShowText.setTextSize(36);
break;
case 5:
cellShowText.setTextSize(28);
break;
case 6:
cellShowText.setTextSize(20);
break;
default:
cellShowText.setTextSize(36);
break;
}
cellShowText.setGravity(Gravity.CENTER);
// 抗鋸齒
cellShowText.getPaint().setAntiAlias(true);
// 粗體
cellShowText.getPaint().setFakeBoldText(true);
// 顏色
cellShowText.setTextColor(ContextCompat.getColor(context, R.color.colorWhite));
// 填充整個父容器
LayoutParams params = new LayoutParams(-1, -1);
params.setMargins(leftMargin, topMargin, 0, bottomMargin);
addView(cellShowText, params);
setDigital(0);
}
//獲取卡片
public TextView getItemCell() {
return cellShowText;
}
//獲取數字
public int getDigital() {
return digital;
}
//設定數字
public void setDigital(int digital) {
this.digital = digital;
cellShowText.setBackgroundResource(getBackgroundResource(digital));
if (digital <= 0) {
cellShowText.setText("");
} else {
cellShowText.setText(String.valueOf(digital));
}
}
//根據數字獲取相應的背景
private int getBackgroundResource(int number) {
switch (number) {
case 0:
return R.drawable.bg_cell_0;
case 2:
return R.drawable.bg_cell_2;
case 4:
return R.drawable.bg_cell_4;
case 8:
return R.drawable.bg_cell_8;
case 16:
return R.drawable.bg_cell_16;
case 32:
return R.drawable.bg_cell_32;
case 64:
return R.drawable.bg_cell_64;
case 128:
return R.drawable.bg_cell_128;
case 256:
return R.drawable.bg_cell_256;
case 512:
return R.drawable.bg_cell_512;
case 1024:
return R.drawable.bg_cell_1024;
case 2048:
return R.drawable.bg_cell_2048;
default:
return R.drawable.bg_cell_default;
}
}
}
3. 將cell添加到GameView中:
- 游戲初始化需要根據難度向GameView添加所有的Cell
/**
* 初始化向布局中添加空格子
* @param cellWidth 格子寬
* @param cellHeight 格子高
*/
private void addCell(int cellWidth, int cellHeight) {
Cell cell;
for (int i = 0; i < gridColumnCount; i++) {
for (int j = 0; j < gridColumnCount; j++) {
if (i == gridColumnCount - 1) {
// 為最底下的格子加上bottomMargin
cell = new Cell(getContext(), 16, 16, 16);
} else {
cell = new Cell(getContext(), 16, 16, 0);
}
cell.setDigital(0);
addView(cell, cellWidth, cellHeight);
cells[i][j] = cell;
}
}
}
- 所有格子需要獲取數字,最初全部設為0,即所有格子為空
//獲取空格子
private void getEmptyCell() {
// 清空
emptyCellPoint.clear();
// 遍歷所有格子,記錄所有空格子的坐標位置
for (int i = 0; i < gridColumnCount; i++) {
for (int j = 0; j < gridColumnCount; j++) {
// 空格子
if (cells[i][j].getDigital() <= 0) {
emptyCellPoint.add(new Point(i, j));
}
}
}
}
- 以4:6的概率隨機獲取一個數字2或4
public void addDigital(boolean isCheat) {
getEmptyCell();
if (emptyCellPoint.size() > 0) {
// 隨機取出一個空格子的坐標位置
Point point = emptyCellPoint.get((int) (Math.random() * emptyCellPoint.size()));
cells[point.x][point.y].setDigital(Math.random() > 0.4 ? 2 : 4);
// 設定影片
setAppearAnim(cells[point.x][point.y]);
}
}
4. 游戲模式的改變:
- 擴展功能:無限模式和經典模式的切換:
//打開切換模式對話框
private void showChangeModeDialog() {
String subject = "";
if (Config.CurrentGameMode == Constant.MODE_CLASSIC) {
subject = "無限模式";
} else if (Config.CurrentGameMode == Constant.MODE_INFINITE) {
subject = "經典模式";
}
CommonDialog dialog = new CommonDialog(this, R.style.CustomDialog);
dialog.setCancelable(true);
dialog.setTitle(getResources().getString(R.string.tip))
.setMessage("是否要切換到" + subject)
.setOnPositiveClickedListener("", v -> {
if (Config.CurrentGameMode == Constant.MODE_CLASSIC) {
Toast.makeText(GameActivity.this, "已進入無限模式", Toast.LENGTH_SHORT).show();
enterInfiniteMode();
} else {
Toast.makeText(GameActivity.this, "已進入經典模式", Toast.LENGTH_SHORT).show();
enterClassicsMode();
}
dialog.cancel();
})
.setOnNegativeClickListener("", v -> dialog.cancel())
.show();
}
// 進入無限模式
private void enterInfiniteMode() {
Config.haveCheat = false;
Config.CurrentGameMode = Constant.MODE_INFINITE;
// 保存游戲模式
ConfigManager.putCurrentGameMode(this, Constant.MODE_INFINITE);
titleDescribe.setText(getResources().getString(R.string.game_mode_infinite));
bestScores.setText(String.valueOf(ConfigManager.getBestScoreWithinInfinite(this)));
bestScoresRank.setText(getResources().getText(R.string.tv_best_score_infinite));
currentScores.setText(String.valueOf(ConfigManager.getCurrentScoreWithinInfinite(this)));
modeDescribe.setText(getResources().getString(R.string.tv_describe_infinite));
setTextStyle(titleDescribe);
gameView.initView(Constant.MODE_INFINITE);
}
//進入經典模式
private void enterClassicsMode() {
Config.haveCheat = false;
Config.CurrentGameMode = Constant.MODE_CLASSIC;
// 保存游戲模式
ConfigManager.putCurrentGameMode(this, Constant.MODE_CLASSIC);
titleDescribe.setText(getResources().getString(R.string.game_mode_classics));
// 讀取到歷史最高分
bestScores.setText(String.valueOf(Config.BestScore));
bestScoresRank.setText(getString(R.string.best_score_rank, Config.GRIDColumnCount));
currentScores.setText(String.valueOf(ConfigManager.getCurrentScore(this)));
modeDescribe.setText(getResources().getString(R.string.tv_describe));
setTextStyle(titleDescribe);
gameView.initView(Constant.MODE_CLASSIC);
}
5. 記錄當前分數及歷史最高分數
//記錄得分
private void recordScore(int score) {
currentScores.setText(String.valueOf(score));
// 當前分數大于最高分
if (Config.CurrentGameMode == Constant.MODE_CLASSIC) {
if (score > ConfigManager.getBestScore(this)) {
updateBestScore(score);
}
} else if (Config.CurrentGameMode == Constant.MODE_INFINITE) {
if (score > ConfigManager.getBestScoreWithinInfinite(this)) {
updateBestScore(score);
}
}
}
//更新歷史最高分
private void updateBestScore(int newScore) {
bestScores.setText(String.valueOf(newScore));
if (Config.CurrentGameMode == Constant.MODE_CLASSIC) {
Config.BestScore = newScore;
ConfigManager.putBestScore(this, newScore);
} else if (Config.CurrentGameMode == Constant.MODE_INFINITE) {
Config.BestScoreWithinInfinite = newScore;
ConfigManager.putBestScoreWithinInfinite(this, newScore);
}
}
6. 判斷游戲是否結束
- 每次獲取數字時需要判斷游戲是否結束
private void judgeOverOrAccomplish() {
// 判斷游戲結束的標識
boolean isOver = true;
// 判斷游戲是否結束:格子都不為空且相鄰的格子數字不同
over:
for (int i = 0; i < gridColumnCount; i++) {
for (int j = 0; j < gridColumnCount; j++) {
// 有空格子,游戲還可以繼續
if (cells[i][j].getDigital() == 0) {
isOver = false;
break over;
}
// 判斷左右上下有沒有相同的
if (j < gridColumnCount - 1) {
if (cells[i][j].getDigital() == cells[i][j + 1].getDigital()) {
isOver = false;
break over;
}
}
if (i < gridColumnCount - 1) {
if (cells[i][j].getDigital() == cells[i + 1][j].getDigital()) {
isOver = false;
break over;
}
}
}
}
// 游戲結束,彈出提示框
if (isOver) {
canSwipe = false;
sendGameOverMsg(ACTION_LOSE);
}
// 經典模式下才判贏
if (gameMode == 0) {
// 判斷是否達成游戲目標
for (int i = 0; i < gridColumnCount; i++) {
for (int j = 0; j < gridColumnCount; j++) {
// 有一個格子數字到達2048則視為達成目標
if (cells[i][j].getDigital() == 2048) {
canSwipe = false;
int currentTime = ConfigManager.getGoalTime(getContext()) + 1;
ConfigManager.putGoalTime(getContext(), currentTime);
Config.GetGoalTime = currentTime;
sendGameOverMsg(ACTION_WIN);
}
}
}
}
}
5. 總結
- 涉及到的知識點匯總:
- Sqlite
- SharedPreferences
- GestureOverlayView
- Animation
- Spannable
- Handler
- BroadcastReceiver
- Timer
- 自定義View
- 本文代碼是根據極客學院2048小游戲代碼視頻一步步撰寫并完善實作,程序中遇到的困難主要是思考游戲思路和鍵盤事件的理解(即手指滑動時的事件),游戲思路在上文中滑動事件前已有詳細解釋,這里給出一個不錯的講解鍵盤事件的鏈接,有遇到相同困難的同學可以查看,
- 鍵盤事件講解鏈接
作者:宋迎新
參考文章:參考文章
原文鏈接:原文鏈接
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/242904.html
標籤:其他
上一篇:安卓學習筆記35:廣播接收者
下一篇:5張圖片解決android studio報錯:Failed to resolve: com.android.support:appcompat-v7:29
