Android實(shí)現(xiàn)瘋狂連連看游戲之加載界面圖片和實(shí)現(xiàn)游戲Activity(四)
正如在《我的Android進(jìn)階之旅------>Android瘋狂連連看游戲的實(shí)現(xiàn)之狀態(tài)數(shù)據(jù)模型(三)》一文中看到的,在AbstractBoard的代碼中,當(dāng)程序需要?jiǎng)?chuàng)建N個(gè)Piece對(duì)象時(shí),程序會(huì)直接調(diào)用ImageUtil的getPlayImages()方法去獲取圖片,該方法會(huì)隨機(jī)從res/drawable目錄中取得N張圖片。
下面是res/drawable目錄視圖:
為了讓getPlayImages()方法能隨機(jī)從res/drawable目錄中取得N張圖片,具體實(shí)現(xiàn)分為以下幾步:
- 通過(guò)反射來(lái)獲取R.drawable的所有Field(Android的每張圖片資源都會(huì)自動(dòng)轉(zhuǎn)換為R.drawable的靜態(tài)Field),并將這些Field值添加到一個(gè)List集合中。
- 從第一步得到的List集合中隨機(jī)“抽取”N/2個(gè)圖片ID。
- 將第二步得到的N/2個(gè)圖片ID全部復(fù)制一份,這樣就得到了N個(gè)圖片ID,而且每個(gè)圖片ID都可以找到與之配對(duì)的。
- 將第三步得到的N個(gè)圖片ID再次“隨機(jī)打亂”,并根據(jù)圖片ID加載相應(yīng)的Bitmap對(duì)象,最后把圖片ID及對(duì)應(yīng)的Bitmap封裝成PieceImage對(duì)象后返回。
下面是ImageUtil類的代碼:cn\oyp\link\utils\ImageUtil.java
package cn.oyp.link.utils; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import cn.oyp.link.R; import cn.oyp.link.view.PieceImage; /** * 圖片資源工具類, 主要用于讀取游戲圖片資源值<br/> * <br/> * 關(guān)于本代碼介紹可以參考一下博客: <a rel="external nofollow" rel="external nofollow" >歐陽(yáng)鵬的CSDN博客</a> <br/> */ public class ImageUtil { /** * 保存所有連連看圖片資源值(int類型) */ private static List<Integer> imageValues = getImageValues(); /** * 獲取連連看所有圖片的ID(約定所有圖片ID以p_開(kāi)頭) */ public static List<Integer> getImageValues() { try { // 得到R.drawable所有的屬性, 即獲取drawable目錄下的所有圖片 Field[] drawableFields = R.drawable.class.getFields(); List<Integer> resourceValues = new ArrayList<Integer>(); for (Field field : drawableFields) { // 如果該Field的名稱以p_開(kāi)頭 if (field.getName().indexOf("p_") != -1) { resourceValues.add(field.getInt(R.drawable.class)); } } return resourceValues; } catch (Exception e) { return null; } } /** * 隨機(jī)從sourceValues的集合中獲取size個(gè)圖片ID, 返回結(jié)果為圖片ID的集合 * * @param sourceValues * 從中獲取的集合 * @param size * 需要獲取的個(gè)數(shù) * @return size個(gè)圖片ID的集合 */ public static List<Integer> getRandomValues(List<Integer> sourceValues, int size) { // 創(chuàng)建一個(gè)隨機(jī)數(shù)生成器 Random random = new Random(); // 創(chuàng)建結(jié)果集合 List<Integer> result = new ArrayList<Integer>(); for (int i = 0; i < size; i++) { try { // 隨機(jī)獲取一個(gè)數(shù)字,大于、小于sourceValues.size()的數(shù)值 int index = random.nextInt(sourceValues.size()); // 從圖片ID集合中獲取該圖片對(duì)象 Integer image = sourceValues.get(index); // 添加到結(jié)果集中 result.add(image); } catch (IndexOutOfBoundsException e) { return result; } } return result; } /** * 從drawable目錄中中獲取size個(gè)圖片資源ID(以p_為前綴的資源名稱), 其中size為游戲數(shù)量 * * @param size * 需要獲取的圖片ID的數(shù)量 * @return size個(gè)圖片ID的集合 */ public static List<Integer> getPlayValues(int size) { if (size % 2 != 0) { // 如果該數(shù)除2有余數(shù),將size加1 size += 1; } // 再?gòu)乃械膱D片值中隨機(jī)獲取size的一半數(shù)量,即N/2張圖片 List<Integer> playImageValues = getRandomValues(imageValues, size / 2); // 將playImageValues集合的元素增加一倍(保證所有圖片都有與之配對(duì)的圖片),即N張圖片 playImageValues.addAll(playImageValues); // 將所有圖片ID隨機(jī)“洗牌” Collections.shuffle(playImageValues); return playImageValues; } /** * 將圖片ID集合轉(zhuǎn)換PieceImage對(duì)象集合,PieceImage封裝了圖片ID與圖片本身 * * @param context * @param resourceValues * @return size個(gè)PieceImage對(duì)象的集合 */ public static List<PieceImage> getPlayImages(Context context, int size) { // 獲取圖片ID組成的集合 List<Integer> resourceValues = getPlayValues(size); List<PieceImage> result = new ArrayList<PieceImage>(); // 遍歷每個(gè)圖片ID for (Integer value : resourceValues) { // 加載圖片 Bitmap bm = BitmapFactory.decodeResource(context.getResources(), value); // 封裝圖片ID與圖片本身 PieceImage pieceImage = new PieceImage(bm, value); result.add(pieceImage); } return result; } /** * 獲取選中標(biāo)識(shí)的圖片 * @param context * @return 選中標(biāo)識(shí)的圖片 */ public static Bitmap getSelectImage(Context context) { Bitmap bm = BitmapFactory.decodeResource(context.getResources(), R.drawable.selected); return bm; } }
前面已經(jīng)給出了游戲界面的布局文件,該布局文件需要一個(gè)Activity來(lái)負(fù)責(zé)顯示,除此之外,Activity還需要為游戲界面的按鈕、GameView組件的事件提供事件監(jiān)聽(tīng)器。
尤其是對(duì)于GameView組件,程序需要監(jiān)聽(tīng)用戶的觸摸動(dòng)作,當(dāng)用戶觸摸屏幕時(shí),程序需要獲取用戶觸摸的是哪個(gè)方塊,并判斷是否需要“消除”該方塊。為了判斷是否消除該方塊,程序需要進(jìn)行如下判斷:
如果程序之前已經(jīng)選擇了某個(gè)方塊,就判斷當(dāng)前觸碰的方塊是否能和之前的方塊“相連”,如果可以相連,則消除兩個(gè)方塊;如果不能相連,則把當(dāng)前方塊設(shè)置為選中方塊。
如果程序之前沒(méi)有選中方塊,直接將當(dāng)前方塊設(shè)置為選中方塊。
下面是Activity的代碼:cn\oyp\link\LinkActivity.java
package cn.oyp.link; import java.util.Timer; import java.util.TimerTask; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Vibrator; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.TextView; import cn.oyp.link.board.GameService; import cn.oyp.link.board.impl.GameServiceImpl; import cn.oyp.link.utils.GameConf; import cn.oyp.link.utils.LinkInfo; import cn.oyp.link.view.GameView; import cn.oyp.link.view.Piece; /** * 游戲Activity <br/> * <br/> * 關(guān)于本代碼介紹可以參考一下博客: <a rel="external nofollow" rel="external nofollow" >歐陽(yáng)鵬的CSDN博客</a> <br/> */ public class LinkActivity extends Activity { /** * 游戲配置對(duì)象 */ private GameConf config; /** * 游戲業(yè)務(wù)邏輯接口 */ private GameService gameService; /** * 游戲界面 */ private GameView gameView; /** * 開(kāi)始按鈕 */ private Button startButton; /** * 記錄剩余時(shí)間的TextView */ private TextView timeTextView; /** * 失敗后彈出的對(duì)話框 */ private AlertDialog.Builder lostDialog; /** * 游戲勝利后的對(duì)話框 */ private AlertDialog.Builder successDialog; /** * 定時(shí)器 */ private Timer timer = new Timer(); /** * 記錄游戲的剩余時(shí)間 */ private int gameTime; /** * 記錄是否處于游戲狀態(tài) */ private boolean isPlaying; /** * 振動(dòng)處理類 */ private Vibrator vibrator; /** * 記錄已經(jīng)選中的方塊 */ private Piece selectedPiece = null; /** * Handler類,異步處理 */ private Handler handler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 0x123: timeTextView.setText("剩余時(shí)間: " + gameTime); gameTime--; // 游戲剩余時(shí)間減少 // 時(shí)間小于0, 游戲失敗 if (gameTime < 0) { // 停止計(jì)時(shí) stopTimer(); // 更改游戲的狀態(tài) isPlaying = false; // 失敗后彈出對(duì)話框 lostDialog.show(); return; } break; } } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 初始化界面 init(); } /** * 初始化游戲的方法 */ private void init() { config = new GameConf(8, 9, 2, 10, GameConf.DEFAULT_TIME, this); // 得到游戲區(qū)域?qū)ο? gameView = (GameView) findViewById(R.id.gameView); // 獲取顯示剩余時(shí)間的文本框 timeTextView = (TextView) findViewById(R.id.timeText); // 獲取開(kāi)始按鈕 startButton = (Button) this.findViewById(R.id.startButton); // 獲取振動(dòng)器 vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); // 初始化游戲業(yè)務(wù)邏輯接口 gameService = new GameServiceImpl(this.config); // 設(shè)置游戲邏輯的實(shí)現(xiàn)類 gameView.setGameService(gameService); // 為開(kāi)始按鈕的單擊事件綁定事件監(jiān)聽(tīng)器 startButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View source) { startGame(GameConf.DEFAULT_TIME); } }); // 為游戲區(qū)域的觸碰事件綁定監(jiān)聽(tīng)器 this.gameView.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View view, MotionEvent e) { if (e.getAction() == MotionEvent.ACTION_DOWN) { gameViewTouchDown(e); } if (e.getAction() == MotionEvent.ACTION_UP) { gameViewTouchUp(e); } return true; } }); // 初始化游戲失敗的對(duì)話框 lostDialog = createDialog("Lost", "游戲失??! 重新開(kāi)始", R.drawable.lost) .setPositiveButton("確定", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { startGame(GameConf.DEFAULT_TIME); } }); // 初始化游戲勝利的對(duì)話框 successDialog = createDialog("Success", "游戲勝利! 重新開(kāi)始", R.drawable.success).setPositiveButton("確定", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { startGame(GameConf.DEFAULT_TIME); } }); } @Override protected void onPause() { // 暫停游戲 stopTimer(); super.onPause(); } @Override protected void onResume() { // 如果處于游戲狀態(tài)中 if (isPlaying) { // 以剩余時(shí)間重新開(kāi)始游戲 startGame(gameTime); } super.onResume(); } /** * 觸碰游戲區(qū)域的處理方法 * * @param event */ private void gameViewTouchDown(MotionEvent event) { // 獲取GameServiceImpl中的Piece[][]數(shù)組 Piece[][] pieces = gameService.getPieces(); // 獲取用戶點(diǎn)擊的x座標(biāo) float touchX = event.getX(); // 獲取用戶點(diǎn)擊的y座標(biāo) float touchY = event.getY(); // 根據(jù)用戶觸碰的座標(biāo)得到對(duì)應(yīng)的Piece對(duì)象 Piece currentPiece = gameService.findPiece(touchX, touchY); // 如果沒(méi)有選中任何Piece對(duì)象(即鼠標(biāo)點(diǎn)擊的地方?jīng)]有圖片), 不再往下執(zhí)行 if (currentPiece == null) return; // 將gameView中的選中方塊設(shè)為當(dāng)前方塊 this.gameView.setSelectedPiece(currentPiece); // 表示之前沒(méi)有選中任何一個(gè)Piece if (this.selectedPiece == null) { // 將當(dāng)前方塊設(shè)為已選中的方塊, 重新將GamePanel繪制, 并不再往下執(zhí)行 this.selectedPiece = currentPiece; this.gameView.postInvalidate(); return; } // 表示之前已經(jīng)選擇了一個(gè) if (this.selectedPiece != null) { // 在這里就要對(duì)currentPiece和prePiece進(jìn)行判斷并進(jìn)行連接 LinkInfo linkInfo = this.gameService.link(this.selectedPiece, currentPiece); // 兩個(gè)Piece不可連, linkInfo為null if (linkInfo == null) { // 如果連接不成功, 將當(dāng)前方塊設(shè)為選中方塊 this.selectedPiece = currentPiece; this.gameView.postInvalidate(); } else { // 處理成功連接 handleSuccessLink(linkInfo, this.selectedPiece, currentPiece, pieces); } } } /** * 觸碰游戲區(qū)域的處理方法 * * @param e */ private void gameViewTouchUp(MotionEvent e) { this.gameView.postInvalidate(); } /** * 以gameTime作為剩余時(shí)間開(kāi)始或恢復(fù)游戲 * * @param gameTime * 剩余時(shí)間 */ private void startGame(int gameTime) { // 如果之前的timer還未取消,取消timer if (this.timer != null) { stopTimer(); } // 重新設(shè)置游戲時(shí)間 this.gameTime = gameTime; // 如果游戲剩余時(shí)間與總游戲時(shí)間相等,即為重新開(kāi)始新游戲 if (gameTime == GameConf.DEFAULT_TIME) { // 開(kāi)始新的游戲游戲 gameView.startGame(); } isPlaying = true; this.timer = new Timer(); // 啟動(dòng)計(jì)時(shí)器 , 每隔1秒發(fā)送一次消息 this.timer.schedule(new TimerTask() { public void run() { handler.sendEmptyMessage(0x123); } }, 0, 1000); // 將選中方塊設(shè)為null。 this.selectedPiece = null; } /** * 成功連接后處理 * * @param linkInfo * 連接信息 * @param prePiece * 前一個(gè)選中方塊 * @param currentPiece * 當(dāng)前選擇方塊 * @param pieces * 系統(tǒng)中還剩的全部方塊 */ private void handleSuccessLink(LinkInfo linkInfo, Piece prePiece, Piece currentPiece, Piece[][] pieces) { // 它們可以相連, 讓GamePanel處理LinkInfo this.gameView.setLinkInfo(linkInfo); // 將gameView中的選中方塊設(shè)為null this.gameView.setSelectedPiece(null); this.gameView.postInvalidate(); // 將兩個(gè)Piece對(duì)象從數(shù)組中刪除 pieces[prePiece.getIndexX()][prePiece.getIndexY()] = null; pieces[currentPiece.getIndexX()][currentPiece.getIndexY()] = null; // 將選中的方塊設(shè)置null。 this.selectedPiece = null; // 手機(jī)振動(dòng)(100毫秒) this.vibrator.vibrate(100); // 判斷是否還有剩下的方塊, 如果沒(méi)有, 游戲勝利 if (!this.gameService.hasPieces()) { // 游戲勝利 this.successDialog.show(); // 停止定時(shí)器 stopTimer(); // 更改游戲狀態(tài) isPlaying = false; } } /** * 創(chuàng)建對(duì)話框的工具方法 * * @param title * 標(biāo)題 * @param message * 內(nèi)容 * @param imageResource * 圖片 * @return */ private AlertDialog.Builder createDialog(String title, String message, int imageResource) { return new AlertDialog.Builder(this).setTitle(title) .setMessage(message).setIcon(imageResource); } /** * 停止計(jì)時(shí) */ private void stopTimer() { // 停止定時(shí)器 this.timer.cancel(); this.timer = null; } }
該Activity用了兩個(gè)類,這兩個(gè)類在下一篇博客中再進(jìn)行相關(guān)描述。
GameConf:負(fù)責(zé)管理游戲的初始化設(shè)置信息。
GameService:負(fù)責(zé)游戲的邏輯實(shí)現(xiàn)。
關(guān)于具體的實(shí)現(xiàn)步驟,請(qǐng)參考下面的鏈接:
我的Android進(jìn)階之旅------>Android瘋狂連連看游戲的實(shí)現(xiàn)之游戲效果預(yù)覽(一)
我的Android進(jìn)階之旅------>Android瘋狂連連看游戲的實(shí)現(xiàn)之開(kāi)發(fā)游戲界面(二)
我的Android進(jìn)階之旅------>Android瘋狂連連看游戲的實(shí)現(xiàn)之狀態(tài)數(shù)據(jù)模型(三)
我的Android進(jìn)階之旅------>Android瘋狂連連看游戲的實(shí)現(xiàn)之實(shí)現(xiàn)游戲邏輯(五)
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Flutter實(shí)現(xiàn)底部和頂部導(dǎo)航欄
這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)底部和頂部導(dǎo)航欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07Android實(shí)現(xiàn)背景可滑動(dòng)登錄界面 (不壓縮背景彈出鍵盤)
這篇文章主要介紹了Android實(shí)現(xiàn)背景可滑動(dòng)登錄界面 (不壓縮背景彈出鍵盤),需要的朋友可以參考下2017-04-04android tv列表焦點(diǎn)記憶實(shí)現(xiàn)的方法
本篇文章主要介紹了android tv列表焦點(diǎn)記憶實(shí)現(xiàn)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04Android實(shí)現(xiàn)啟動(dòng)引導(dǎo)圖
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)啟動(dòng)引導(dǎo)圖,文中示例代碼介紹的非常詳細(xì),具有為大家詳細(xì)一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06Android中比較兩個(gè)圖片是否一致的問(wèn)題
這篇文章主要介紹了Android中比較兩個(gè)圖片是否一致的問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10android自定義開(kāi)關(guān)控件-SlideSwitch的實(shí)例
本篇文章主要介紹了android自定義開(kāi)關(guān)控件-SlideSwitch的實(shí)例,實(shí)現(xiàn)了手機(jī)控件開(kāi)關(guān)的功能,感興趣的小伙伴們可以參考一下。2016-11-11android studio 使用Mocklocation虛擬定位
這篇文章主要介紹了android studio 使用Mocklocation虛擬定位總結(jié),本文分步驟給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12android activity設(shè)置無(wú)標(biāo)題實(shí)現(xiàn)全屏
本文將詳細(xì)介紹Android如何設(shè)置Activity全屏和無(wú)標(biāo)題的實(shí)現(xiàn)方法,需要的朋友可以參考下2012-12-12解決android 顯示內(nèi)容被底部導(dǎo)航欄遮擋的問(wèn)題
今天小編就為大家分享一篇解決android 顯示內(nèi)容被底部導(dǎo)航欄遮擋的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07Android Studio去除界面默認(rèn)標(biāo)題欄的方法
這篇文章主要介紹了Android Studio去除界面默認(rèn)標(biāo)題欄的方法,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2007-09-09