Android開發(fā)之經(jīng)典游戲貪吃蛇
前言
這款游戲實現(xiàn)的思路和源碼參考了Google自帶的Snake的例子,其中修改了一些個人認為還不夠完善的地方,加入了一些新的功能,比如屏幕上的方向操作盤,暫停按鈕,開始按鈕,退出按鈕。另外,為了稍微增加些用戶體驗,除了游戲的主界面,本人自己新增了5個界面,分別是登陸界面,菜單界面,背景音樂設置界面,難度設置界面,還有個關于游戲的介紹界面。個人覺得在新手階段,參考現(xiàn)成的思路和實現(xiàn)方式是難以避免的。重要的是我們需要有自己的理解,讀懂代碼之后,需要思考代碼背后的實現(xiàn)邏輯,形成自己的思維。這樣在下次開發(fā)工作時,就不用參考別人自己也能涌現(xiàn)出解決的思路。
我覺得經(jīng)過自己的構思和實踐,做出一個可操作有界面的小作品還是挺有成就感的,在探索和思考的過程中時間過的很快。好了,下面切入正題,我考慮了下講述的順序,決定就以進入軟件后的界面順序來把。
由于篇幅的關系,布局的XML文件就不發(fā)了,而且我把導包的語句也省略了,反正像AS,eclipse這些工具都是可以智能導包的。
那么,首先是登陸界面,找了些網(wǎng)上的資源當背景。布局還是比較簡單的。
下圖中,上圖為效果圖,下圖為邏輯實現(xiàn)的流程圖。
[java] view plain copy // MainActivity.java package con.example.wang.game; public class MainActivity extends Activity implements OnClickListener{ Button button; EditText edit1,edit2; CheckBox checkbox; ProgressBar bar; SharedPreferences pref; SharedPreferences.Editor editor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button=(Button) findViewById(R.id.login_button); edit1=(EditText) findViewById(R.id.input1); edit2=(EditText) findViewById(R.id.input2); checkbox=(CheckBox) findViewById(R.id.remember_button); bar=(ProgressBar) findViewById(R.id.progress); pref= PreferenceManager.getDefaultSharedPreferences(this); boolean isRemember=pref.getBoolean("rem",false); //獲取代表是否保存密碼的變量值,這里初值設為false if(isRemember) { //如果記住密碼,則將賬號和密碼自動填充到文本框中 String account=pref.getString("account",""); String password=pref.getString("password",""); edit1.setText(account); edit2.setText(password); checkbox.setChecked(true); } button.setOnClickListener(this); } @Override public void onClick(View v){ new Thread(new Runnable(){ //開啟線程運行進度條,減少主線程的壓力,這里不用子線程也影響不大 @Override public void run() { for (int i = 0; i < 25; i++) { int progress = bar.getProgress(); progress = progress + 10; bar.setProgress(progress); } } }).start(); String account=edit1.getText().toString(); String password=edit2.getText().toString(); if(account.equals("admin") && password.equals("123456")) { editor = pref.edit(); //這個方法用于向SharedPreferences文件中寫數(shù)據(jù) if(checkbox.isChecked()) { editor.putBoolean("rem",true); editor.putString("account",account); editor.putString("password",password); } else { editor.clear(); } editor.commit(); //這個方法必須要有,不然數(shù)據(jù)不會被保存。生效后,就可以從該文件中讀取數(shù)據(jù)。 Intent intent=new Intent(MainActivity.this,SecondActivity.class); startActivity(intent); } else{ //如果用戶名或密碼不正確,這里會彈出一個提示框 Toast.makeText(MainActivity.this,"賬號或用戶名錯誤",Toast.LENGTH_SHORT).show(); } } }
這個邏輯還算比較簡單,實現(xiàn)了記住密碼的功能,這里的數(shù)據(jù)存儲使用的是SharedPreferences
。點擊登陸后,會進入一個菜單界面,這里設置幾個四個按鈕,分別做好監(jiān)聽就可以了,然后用Intent
在活動間跳轉就好了。
效果圖也分享一下。
[java] view plain copy // SecondActivity.java package com.example.wang.game; public class SecondActivity extends Activity implements OnClickListener{ ImageButton button1,button2,button3,button4; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); button1=(ImageButton) findViewById(R.id.button_start); button2=(ImageButton) findViewById(R.id.button_difficulty); button3=(ImageButton) findViewById(R.id.button_music); button4=(ImageButton) findViewById(R.id.button_about); button4.setOnClickListener(this); button3.setOnClickListener(this); button2.setOnClickListener(this); button1.setOnClickListener(this); } @Override public void onClick(View v){ switch(v.getId()) { //看下Intent的用法,還是挺方便的,這里用的都是顯式的方法 case R.id.button_about: Intent intent1 = new Intent(SecondActivity.this, AboutActivity.class); startActivity(intent1); break; case R.id.button_music: Intent intent2 = new Intent(SecondActivity.this, MusicActivity.class); startActivity(intent2); break; case R.id.button_difficulty: Intent intent3 = new Intent(SecondActivity.this, DifficultyActivity.class); startActivity(intent3); break; case R.id.button_start: Intent intent4 = new Intent(SecondActivity.this, GameActivity.class); startActivity(intent4); break; default: break; } } }
下面先講難度設置界面吧,這個和背景音樂開關其實差不多,所以以此為例,背景音樂開關界面就不啰嗦了。這里也是用的SharedPreferences
存儲數(shù)據(jù)。這里布局文件里把三個RadioButton
放入RadioGroup
,實現(xiàn)單選的效果。給三個按鈕設置監(jiān)聽,觸發(fā)事件后分別返回對應的三個變量,這三個變量控制的是貪吃蛇運行的速度。
參考下流程圖更好理解。
[java] view plain copy // DifficultyActivity.java package com.example.wang.game; public class DifficultyActivity extends Activity implements OnClickListener{ private SharedPreferences saved; private SharedPreferences.Editor editor; RadioButton button_jiandan,button_yiban,button_kunnan; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_difficulty); saved = PreferenceManager.getDefaultSharedPreferences(this); int level = saved.getInt("nandu",500); button_jiandan = (RadioButton) findViewById(R.id.button_difficulty1); button_yiban = (RadioButton) findViewById(R.id.button_difficulty2); button_kunnan = (RadioButton) findViewById(R.id.button_difficulty3); button_jiandan.setOnClickListener(this); button_yiban.setOnClickListener(this); button_kunnan.setOnClickListener(this); } @Override public void onClick(View v){ editor=saved.edit(); switch(v.getId()){ case R.id.button_difficulty1: if(button_jiandan.isChecked()){ editor.putInt("nandu",500); } break; case R.id.button_difficulty2: if(button_yiban.isChecked()){ editor.putInt("nandu",200); } break; case R.id.button_difficulty3: if(button_kunnan.isChecked()){ editor.putInt("nandu",100); } break; } editor.commit(); } }
其它的兩個輔助界面比較簡單,背景音樂開關界面也是通過SharedPreferences
文件存儲一個boolean
的值,true的話就播放音樂,false就不播放。關于游戲的介紹界面就加一些文字。上述這些都是輔助,下面是游戲的主體部分。
游戲界面的設計思路就是將手機屏幕分為多行多列的像素塊,以像素塊為最小單位,確定各點的坐標。這里每個像素塊的大小設置為32像素。我的手機模擬器的屏幕分辨率為768*1280,由公式可算出,我的游戲界面x軸上坐標最大為24,y軸上坐標最大為35。坐標完成后,這里會使用三種顏色不同的圖片來填充像素塊,這里就叫磚塊把。
根據(jù)java面向對象的邏輯,需要給各塊內容分類,蛇,蘋果,邊界的墻都是必不可少的元素。視圖的初始化也是圍繞著這三個元素展開的。其實這里蛇,蘋果和邊界墻就是由不同顏色的磚塊表示出來的。
該部分內容包含三個java文件,首先是磚塊的初始化。
[java] view plain copy // TileView.java package com.example.wang.game; public class TileView extends View { public static int mTileSize =32; public static int mXTileCount; //地圖上所能容納的格數(shù) public static int mYTileCount; public static int mXOffset; //偏移量 public static int mYOffset; Bitmap[] mTileArray; //放置圖片的數(shù)組 int[][] mTileGrid; //存放各坐標對應的圖片 public TileView(Context context, AttributeSet attrs,int defStyle){ super(context,attrs,defStyle); } public TileView(Context context, AttributeSet attrs){ super(context,attrs); } public TileView(Context context){ super(context); } //加載三幅小圖片 public void loadTile(int key, Drawable tile) { Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); tile.setBounds(0, 0, mTileSize, mTileSize); tile.draw(canvas); mTileArray[key] = bitmap; } //給地圖數(shù)組賦值 public void setTile(int tileindex, int x, int y) { mTileGrid[x][y] = tileindex; } public void resetTiles(int tilecount) { mTileArray = new Bitmap[tilecount]; } //我的游戲界面不是占滿整個屏幕,所以地圖遍歷的時候,y不是從0開始 public void clearTiles() { for (int x = 0; x < mXTileCount; x++) { for (int y = 2; y < mYTileCount-8; y++) { setTile(0, x, y); } } } //計算當前屏幕在X,Y軸上分別所能容納的最大磚塊數(shù)量 //這里輸出</span><span style="font-size:14px;">“mXTileCount"和”mYTileCount"的值后面會用到 @Override public void onSizeChanged(int w, int h, int oldw, int oldh){ //地圖數(shù)組初始化 mXTileCount = (int) Math.floor(w / mTileSize); mYTileCount = (int) Math.floor(h / mTileSize); // System.out.println("-------"+mXTileCount+"----------"); // System.out.println("-------"+mYTileCount+"----------"); //可能屏幕的長寬不能整除,所以夠分成一格的分成一格, 剩下不夠一格的分成兩份,左邊一份,右邊一份 mXOffset = ((w - (mTileSize * mXTileCount)) / 2); mYOffset = ((h - (mTileSize * mYTileCount)) / 2); // System.out.println("-------"+mXOffset+"----------"); // System.out.println("-------"+mYOffset+"----------"); mTileGrid = new int[mXTileCount][mYTileCount]; clearTiles(); } @Override public void onDraw(Canvas canvas){ super.onDraw(canvas); } }
其實上述這段程序就是實現(xiàn)了幾個方法,loadTile()
用于加載圖片,setTile()
和resetTile()
是把圖片與坐標聯(lián)系起來,而onSizedChanged()
是把手機屏幕像素塊化。這些方法都將為以下這個類服務。為了便于利用這些方法,以下這個類繼承自TileView
。
由于我加了一些按鈕,所以游戲界面生成時沒有占滿整個屏幕,所以在設置坐標和遍歷地圖時與源碼的數(shù)據(jù)相差較多。
[java] view plain copy // SnakeView.java package com.example.wang.game; public class SnakeView extends TileView{ static int mMoveDelay = 500; private long mLastMove; private static final int RED_STAR = 1; private static final int YELLOW_STAR = 2; private static final int GREEN_STAR = 3; private static final int UP = 1; private static final int DOWN = 2; private static final int RIGHT = 3; private static final int LEFT = 4; static int mDirection = RIGHT; static int mNextDirection = RIGHT; // 這里把游戲界面分為5種狀態(tài),便于邏輯實現(xiàn) public static final int PAUSE = 0; public static final int READY = 1; public static final int RUNNING = 2; public static final int LOSE = 3; public static final int QUIT = 4; public int mMode = READY; public int newMode; private TextView mStatusText; // 用于每個狀態(tài)下的文字提醒 public long mScore = 0; private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>(); // 存儲蛇的所有坐標的數(shù)組 private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>(); // 存儲蘋果的所有坐標的數(shù)組 private static final Random RNG = new Random(); //用于生成蘋果坐標的隨機數(shù) // private static final String TAG = "SnakeView"; //開啟線程,不斷調用更新和重繪。這里利用了Handler類來實現(xiàn)異步消息處理機制 MyHandler handler=new MyHandler(); class MyHandler extends Handler{ @Override public void handleMessage(Message msg) { SnakeView.this.update(); //不斷調用update()方法 SnakeView.this.invalidate(); //請求重繪,不斷調用ondraw()方法 } //調用sleep后,在一段時間后再sendmessage進行UI更新 public void sleep(int delayMillis) { this.removeMessages(0); //清空消息隊列 sendMessageDelayed(obtainMessage(0), delayMillis); } } //這是三個構造方法,別忘了加上下面這個初始化方法 public SnakeView(Context context, AttributeSet attrs, int defStyle){ super(context,attrs,defStyle); initNewGame(); } public SnakeView(Context context, AttributeSet attrs){ super(context,attrs); setFocusable(true); initNewGame(); } public SnakeView(Context context){ super(context); } //添加蘋果的方法,最后將生成的蘋果坐標存儲在上面定義的數(shù)組中 private void addRandomApple() { Coordinate newCoord = null; boolean found = false; while (!found) { // 這里設定了蘋果坐標能隨機生成的范圍,并生成隨機坐標。這里Google源碼中是直接使用變量 // mXTileCount和mYTileCount,我編譯時會報錯,因為隨機數(shù)不能生成負數(shù),而直接使用這兩個變量程序不能 // 識別這個變量減去一個數(shù)后是否會是負數(shù),所以我把TileView里輸出的確切值放了進去 int newX = 1 + RNG.nextInt(24-2); int newY = 3 + RNG.nextInt(35-12); newCoord = new Coordinate(newX, newY); boolean collision = false; int snakelength = mSnakeTrail.size(); //遍歷snake, 看新添加的apple是否在snake體內, 如果是,重新生成坐標 for (int index = 0; index < snakelength; index++) { if (mSnakeTrail.get(index).equals(newCoord)) { collision = true; } } found = !collision; } // if (newCoord == null) { // Log.e(TAG, "Somehow ended up with a null newCoord!"); // } mAppleList.add(newCoord); } //繪制邊界的墻 private void updateWalls() { for (int x = 0; x < mXTileCount; x++) { setTile(GREEN_STAR, x, 2); setTile(GREEN_STAR, x, mYTileCount - 8); } for (int y = 2; y < mYTileCount - 8; y++) { setTile(GREEN_STAR, 0, y); setTile(GREEN_STAR, mXTileCount - 1, y); } } //更新蛇的運動軌跡 private void updateSnake(){ boolean growSnake = false; Coordinate head = mSnakeTrail.get(0); Coordinate newHead = new Coordinate(1, 1); mDirection = mNextDirection; switch (mDirection) { case RIGHT: { newHead = new Coordinate(head.x + 1, head.y); break; } case LEFT: { newHead = new Coordinate(head.x - 1, head.y); break; } case UP: { newHead = new Coordinate(head.x, head.y - 1); break; } case DOWN: { newHead = new Coordinate(head.x, head.y + 1); break; } } //檢測是否撞墻 if ((newHead.x < 1) || (newHead.y < 3) || (newHead.x > mXTileCount - 2) || (newHead.y > mYTileCount - 9)) { setMode(LOSE); return; } //檢測蛇頭是否撞到自己 int snakelength = mSnakeTrail.size(); for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) { Coordinate c = mSnakeTrail.get(snakeindex); if (c.equals(newHead)) { setMode(LOSE); return; } } //檢測蛇是否吃到蘋果 int applecount = mAppleList.size(); for (int appleindex = 0; appleindex < applecount; appleindex++) { Coordinate c = mAppleList.get(appleindex); if (c.equals(newHead)) { mAppleList.remove(c); addRandomApple(); mScore++; mMoveDelay *= 0.95; //蛇每遲到一個蘋果,延時就會減少,蛇的速度就會加快 growSnake = true; } } mSnakeTrail.add(0,newHead); if(!growSnake) { mSnakeTrail.remove(mSnakeTrail.size() - 1); } //蛇頭和蛇身分別設置不同的圖片 int index=0; for(Coordinate c:mSnakeTrail) { if(index == 0) { setTile(RED_STAR, c.x, c.y); } else { setTile(YELLOW_STAR,c.x,c.y); } index++; } } 給蘋果加載對應的圖片 private void updateApples() { for (Coordinate c : mAppleList) { setTile(YELLOW_STAR, c.x, c.y); } } // 該方法很重要,用于更新蛇,蘋果和墻的坐標 // 這里設置了更新的時間間隔,我發(fā)現(xiàn)不加這個延時的話蛇運動時容易出現(xiàn)一下跳很多格的情況 public void update(){ if(mMode == RUNNING) { long now = System.currentTimeMillis(); if (now - mLastMove > mMoveDelay) { clearTiles(); updateWalls(); updateSnake(); updateApples(); mLastMove = now; } handler.sleep(mMoveDelay); } } //圖像初始化,引入圖片資源 private void initSnakeView() { setFocusable(true); //添加焦點 Resources r = this.getContext().getResources(); //添加幾種不同的tile resetTiles(4); //從文件中加載圖片 loadTile(RED_STAR, r.getDrawable(R.drawable.redstar)); loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar)); loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar)); update(); } // 數(shù)據(jù)初始化方法,定義蛇的起始坐標,運動方向和得分變量。給數(shù)組添加坐標的時候注意順序,因為有蛇頭和蛇尾的區(qū)別 public void initNewGame() { mSnakeTrail.clear(); mAppleList.clear(); //snake初始狀態(tài)時的個數(shù)和位置,方向 mSnakeTrail.add(new Coordinate(8, 7)); mSnakeTrail.add(new Coordinate(7, 7)); mSnakeTrail.add(new Coordinate(6, 7)); mSnakeTrail.add(new Coordinate(5, 7)); mSnakeTrail.add(new Coordinate(4, 7)); mSnakeTrail.add(new Coordinate(3, 7)); mDirection = RIGHT; mNextDirection = RIGHT; // 這個變量必須初始化,不然每次游戲結束重新開始后,蛇初始的方向將不是向右,而是你游戲結束時蛇的方向, // 如果死的時候,蛇的方向向左,那么再次點擊開始時會無法繪出蛇的圖像 addRandomApple(); mScore=0; } // 根據(jù)各個數(shù)組中的數(shù)據(jù),遍歷地圖設置各點的圖片 public void onDraw(Canvas canvas){ super.onDraw(canvas); Paint paint=new Paint(); initSnakeView(); //遍歷地圖繪制界面 for (int x = 0; x < mXTileCount; x++) { for (int y = 0; y < mYTileCount; y++) { if (mTileGrid[x][y] > 0) { // 被加了圖片的點mTileGird是大于0的 canvas.drawBitmap(mTileArray[mTileGrid[x][y]], mXOffset + x * mTileSize, mYOffset + y * mTileSize, paint); } } } } //把蛇和蘋果各點對應的坐標利用一個一維數(shù)組儲存起來 private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) { int count = cvec.size(); int[] rawArray = new int[count * 2]; for (int index = 0; index < count; index++) { Coordinate c = cvec.get(index); rawArray[2 * index] = c.x; rawArray[2 * index + 1] = c.y; } return rawArray; } //將當前所有的游戲數(shù)據(jù)全部保存 public Bundle saveState() { Bundle map = new Bundle(); map.putIntArray("mAppleList", coordArrayListToArray(mAppleList)); map.putInt("mDirection", Integer.valueOf(mDirection)); map.putInt("mNextDirection", Integer.valueOf(mNextDirection)); map.putInt("mMoveDelay", Integer.valueOf(mMoveDelay)); map.putLong("mScore", Long.valueOf(mScore)); map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail)); return map; } //是coordArrayListToArray()的逆過程,用來讀取數(shù)組中的坐標數(shù)據(jù) private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) { ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>(); int coordCount = rawArray.length; for (int index = 0; index < coordCount; index += 2) { Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]); coordArrayList.add(c); } return coordArrayList; } //saveState()的逆過程,用于恢復游戲數(shù)據(jù) public void restoreState(Bundle icicle) { setMode(PAUSE); mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList")); mDirection = icicle.getInt("mDirection"); mNextDirection = icicle.getInt("mNextDirection"); mMoveDelay = icicle.getInt("mMoveDelay"); mScore = icicle.getLong("mScore"); mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail")); } // 設置鍵盤監(jiān)聽,在模擬器中可以使用電腦鍵盤控制蛇的方向 public boolean onKeyDown(int keyCode, KeyEvent event){ if(keyCode == KeyEvent.KEYCODE_DPAD_UP){ if(mDirection != DOWN) { mNextDirection = UP; } return (true); } if(keyCode == KeyEvent.KEYCODE_DPAD_DOWN){ if(mDirection != UP) { mNextDirection = DOWN; } return (true); } if(keyCode == KeyEvent.KEYCODE_DPAD_RIGHT){ if(mDirection != LEFT) { mNextDirection = RIGHT; } return (true); } if(keyCode == KeyEvent.KEYCODE_DPAD_LEFT){ if(mDirection != RIGHT) { mNextDirection = LEFT; } return (true); } return super.onKeyDown(keyCode,event); } public void setTextView(TextView newView) { mStatusText = newView; } // 設置不同狀態(tài)下提示文字的顯示內容和可見狀態(tài) public void setMode(int newMode) { this.newMode=newMode; int oldMode = mMode; mMode = newMode; if (newMode == RUNNING & oldMode != RUNNING) { mStatusText.setVisibility(View.INVISIBLE); update(); return; } // 這里定義了一個空字符串,用于放入各個狀態(tài)下的提醒文字 Resources res = getContext().getResources(); CharSequence str = ""; if (newMode == PAUSE) { str = res.getText(R.string.mode_pause); } if (newMode == READY) { str = res.getText(R.string.mode_ready); } if (newMode == LOSE) { str = res.getString(R.string.mode_lose_prefix) + mScore + res.getString(R.string.mode_lose_suffix); } if (newMode == QUIT){ str = res.getText(R.string.mode_quit); } mStatusText.setText(str); mStatusText.setVisibility(View.VISIBLE); } //記錄坐標位置 private class Coordinate { public int x; public int y; public Coordinate(int newX, int newY) { x = newX; y = newY; } //觸碰檢測,看蛇是否吃到蘋果 public boolean equals(Coordinate other) { if (x == other.x && y == other.y) { return true; } return false; } // 這個方法沒研究過起什么作用,我注釋掉對程序的運行沒有影響 @Override public String toString() { return "Coordinate: [" + x + "," + y + "]"; } } }
以上是自定義View
的實現(xiàn),實現(xiàn)了繪制游戲界面的主邏輯。將SnakeView
作為一個UI控件插入到該界面的布局中。
下面開啟活動,顯示界面。
[java] view plain copy // GameActivity.java package com.example.wang.game; public class GameActivity extends Activity implements OnClickListener{ private SharedPreferences saved; private static String ICICLE_KEY = "snake-view"; // 個人認為這個變量就是一個中間值,在該類的最后一個方法中傳入該變量,完成操作。 private SnakeView mSnakeView; private ImageButton change_stop,change_start,change_quit; private ImageButton mLeft; private ImageButton mRight; private ImageButton mUp; private ImageButton mDown; private static final int UP = 1; private static final int DOWN = 2; private static final int RIGHT = 3; private static final int LEFT = 4; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_game); mSnakeView = (SnakeView) findViewById(R.id.snake); //給自定義View實例化,把這個布局當一個UI控件一樣插入進來 mSnakeView.setTextView((TextView) findViewById(R.id.text_show)); change_stop = (ImageButton) findViewById(R.id.game_stop); change_start = (ImageButton) findViewById(R.id.game_start); change_quit = (ImageButton) findViewById(R.id.game_quit); mLeft = (ImageButton) findViewById(R.id.left); mRight = (ImageButton) findViewById(R.id.right); mUp = (ImageButton) findViewById(R.id.up); mDown = (ImageButton) findViewById(R.id.down); change_start = (ImageButton) findViewById(R.id.game_start); change_stop = (ImageButton) findViewById(R.id.game_stop); change_quit = (ImageButton) findViewById(R.id.game_quit); saved = PreferenceManager.getDefaultSharedPreferences(this); boolean playMusic = saved.getBoolean("ifon" ,true); // 獲取背景音樂開關的狀態(tài)變量,在設置開關界面存儲,在這里讀取 if(playMusic) { // 如果設置背景音樂打開,則開啟服務,播放音樂 Intent intent_service = new Intent(GameActivity.this, MusicService.class); startService(intent_service); } SnakeView.mMoveDelay=saved.getInt("nandu",500); // 獲取當前設置的代表游戲難度的變量,在難度設置界面保存,在這里讀取 // 判斷是否有保存數(shù)據(jù),如果數(shù)據(jù)為空就準備重新開始游戲 if (savedInstanceState == null) { mSnakeView.setMode(SnakeView.READY); } else { // 暫停后的恢復 Bundle map = savedInstanceState.getBundle(ICICLE_KEY); if (map != null) { mSnakeView.restoreState(map); } else { mSnakeView.setMode(SnakeView.PAUSE); } } mDown.setOnClickListener(this); mUp.setOnClickListener(this); mRight.setOnClickListener(this); mLeft.setOnClickListener(this); change_start.setOnClickListener(this); change_stop.setOnClickListener(this); change_quit.setOnClickListener(this); } @Override public void onDestroy(){ super.onDestroy(); saved = PreferenceManager.getDefaultSharedPreferences(this); boolean playMusic = saved.getBoolean("ifon" ,true); if(playMusic) { Intent intent_service = new Intent(GameActivity.this, MusicService.class); stopService(intent_service); } } // 給開始,暫停,退出,上下左右按鈕設置監(jiān)聽。根據(jù)當前的狀態(tài)來決定界面的更新操作 public void onClick(View v) { switch (v.getId()) { case R.id.game_start: // 重新開始游戲,這里延時變量必須初始化,不然每次游戲重新開始之后,蛇的運動速度不會初始化 if ( mSnakeView.mMode == SnakeView.READY || mSnakeView.mMode == SnakeView.LOSE) { SnakeView.mMoveDelay=saved.getInt("nandu",500); mSnakeView.initNewGame(); mSnakeView.setMode(SnakeView.RUNNING); mSnakeView.update(); } // 暫停后開始游戲,繼續(xù)暫停前的界面 if ( mSnakeView.mMode == SnakeView.PAUSE) { mSnakeView.setMode(SnakeView.RUNNING); mSnakeView.update(); } break; case R.id.game_stop: // 暫停 if(mSnakeView.mMode == SnakeView.RUNNING) { mSnakeView.setMode(SnakeView.PAUSE); } break; case R.id.game_quit: // 退出,返回菜單界面 mSnakeView.setMode(SnakeView.QUIT); finish(); break; // 使界面上的方向按鈕起作用 case R.id.left: if (SnakeView.mDirection != RIGHT) { SnakeView.mNextDirection = LEFT; } break; case R.id.right: if (SnakeView.mDirection != LEFT) { SnakeView.mNextDirection = RIGHT; } break; case R.id.up: if (SnakeView.mDirection != DOWN) { SnakeView.mNextDirection = UP; } break; case R.id.down: if (SnakeView.mDirection != UP) { SnakeView.mNextDirection = DOWN; } break; default: break; } } @Override protected void onPause() { super.onPause(); mSnakeView.setMode(SnakeView.PAUSE); } @Override public void onSaveInstanceState(Bundle outState) { //保存游戲狀態(tài) outState.putBundle(ICICLE_KEY, mSnakeView.saveState()); } }
下面是游戲效果圖,運行界面和暫停界面。我把邏輯流程圖也貼出來,有什么問題大家可以留言,多多交流!
本文通過帶大家回憶經(jīng)典游戲的同時,學習了利用java開發(fā)Android游戲——貪吃蛇,希望本文在大家學習Android開發(fā)有所幫助。
相關文章
Android Studio 利用Splash制作APP啟動界面的方法
這篇文章主要介紹了Android Studio 利用Splash制作APP啟動界面,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05Android中多個ContentProvider的初始化順序詳解
在日常Android開發(fā)中經(jīng)常會寫一些sdk來供他人或者自己調用,一般這些sdk都涉及到初始化,下面這篇文章主要給大家介紹了關于Android中多個ContentProvider的初始化順序的相關資料,需要的朋友可以參考下2022-04-04android surfaceView實現(xiàn)播放視頻功能
這篇文章主要為大家詳細介紹了android surfaceView實現(xiàn)播放視頻功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05