Android開發(fā)之經(jīng)典游戲貪吃蛇
前言
這款游戲?qū)崿F(xiàn)的思路和源碼參考了Google自帶的Snake的例子,其中修改了一些個(gè)人認(rèn)為還不夠完善的地方,加入了一些新的功能,比如屏幕上的方向操作盤,暫停按鈕,開始按鈕,退出按鈕。另外,為了稍微增加些用戶體驗(yàn),除了游戲的主界面,本人自己新增了5個(gè)界面,分別是登陸界面,菜單界面,背景音樂設(shè)置界面,難度設(shè)置界面,還有個(gè)關(guān)于游戲的介紹界面。個(gè)人覺得在新手階段,參考現(xiàn)成的思路和實(shí)現(xiàn)方式是難以避免的。重要的是我們需要有自己的理解,讀懂代碼之后,需要思考代碼背后的實(shí)現(xiàn)邏輯,形成自己的思維。這樣在下次開發(fā)工作時(shí),就不用參考別人自己也能涌現(xiàn)出解決的思路。
我覺得經(jīng)過自己的構(gòu)思和實(shí)踐,做出一個(gè)可操作有界面的小作品還是挺有成就感的,在探索和思考的過程中時(shí)間過的很快。好了,下面切入正題,我考慮了下講述的順序,決定就以進(jìn)入軟件后的界面順序來把。
由于篇幅的關(guān)系,布局的XML文件就不發(fā)了,而且我把導(dǎo)包的語句也省略了,反正像AS,eclipse這些工具都是可以智能導(dǎo)包的。
那么,首先是登陸界面,找了些網(wǎng)上的資源當(dāng)背景。布局還是比較簡(jiǎn)單的。
下圖中,上圖為效果圖,下圖為邏輯實(shí)現(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); //獲取代表是否保存密碼的變量值,這里初值設(shè)為false
if(isRemember) {
//如果記住密碼,則將賬號(hào)和密碼自動(dòng)填充到文本框中
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(){ //開啟線程運(yùn)行進(jìn)度條,減少主線程的壓力,這里不用子線程也影響不大
@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(); //這個(gè)方法用于向SharedPreferences文件中寫數(shù)據(jù)
if(checkbox.isChecked()) {
editor.putBoolean("rem",true);
editor.putString("account",account);
editor.putString("password",password);
}
else {
editor.clear();
}
editor.commit(); //這個(gè)方法必須要有,不然數(shù)據(jù)不會(huì)被保存。生效后,就可以從該文件中讀取數(shù)據(jù)。
Intent intent=new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
}
else{ //如果用戶名或密碼不正確,這里會(huì)彈出一個(gè)提示框
Toast.makeText(MainActivity.this,"賬號(hào)或用戶名錯(cuò)誤",Toast.LENGTH_SHORT).show();
}
}
}
這個(gè)邏輯還算比較簡(jiǎn)單,實(shí)現(xiàn)了記住密碼的功能,這里的數(shù)據(jù)存儲(chǔ)使用的是SharedPreferences。點(diǎn)擊登陸后,會(huì)進(jìn)入一個(gè)菜單界面,這里設(shè)置幾個(gè)四個(gè)按鈕,分別做好監(jiān)聽就可以了,然后用Intent在活動(dòng)間跳轉(zhuǎn)就好了。
效果圖也分享一下。

[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;
}
}
}
下面先講難度設(shè)置界面吧,這個(gè)和背景音樂開關(guān)其實(shí)差不多,所以以此為例,背景音樂開關(guān)界面就不啰嗦了。這里也是用的SharedPreferences存儲(chǔ)數(shù)據(jù)。這里布局文件里把三個(gè)RadioButton放入RadioGroup,實(shí)現(xiàn)單選的效果。給三個(gè)按鈕設(shè)置監(jiān)聽,觸發(fā)事件后分別返回對(duì)應(yīng)的三個(gè)變量,這三個(gè)變量控制的是貪吃蛇運(yùn)行的速度。
參考下流程圖更好理解。


[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();
}
}
其它的兩個(gè)輔助界面比較簡(jiǎn)單,背景音樂開關(guān)界面也是通過SharedPreferences文件存儲(chǔ)一個(gè)boolean的值,true的話就播放音樂,false就不播放。關(guān)于游戲的介紹界面就加一些文字。上述這些都是輔助,下面是游戲的主體部分。
游戲界面的設(shè)計(jì)思路就是將手機(jī)屏幕分為多行多列的像素塊,以像素塊為最小單位,確定各點(diǎn)的坐標(biāo)。這里每個(gè)像素塊的大小設(shè)置為32像素。我的手機(jī)模擬器的屏幕分辨率為768*1280,由公式可算出,我的游戲界面x軸上坐標(biāo)最大為24,y軸上坐標(biāo)最大為35。坐標(biāo)完成后,這里會(huì)使用三種顏色不同的圖片來填充像素塊,這里就叫磚塊把。
根據(jù)java面向?qū)ο蟮倪壿?,需要給各塊內(nèi)容分類,蛇,蘋果,邊界的墻都是必不可少的元素。視圖的初始化也是圍繞著這三個(gè)元素展開的。其實(shí)這里蛇,蘋果和邊界墻就是由不同顏色的磚塊表示出來的。
該部分內(nèi)容包含三個(gè)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; //存放各坐標(biāo)對(duì)應(yīng)的圖片
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];
}
//我的游戲界面不是占滿整個(gè)屏幕,所以地圖遍歷的時(shí)候,y不是從0開始
public void clearTiles() {
for (int x = 0; x < mXTileCount; x++) {
for (int y = 2; y < mYTileCount-8; y++) {
setTile(0, x, y);
}
}
}
//計(jì)算當(dāng)前屏幕在X,Y軸上分別所能容納的最大磚塊數(shù)量
//這里輸出</span><span style="font-size:14px;">“mXTileCount"和”mYTileCount"的值后面會(huì)用到
@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+"----------");
//可能屏幕的長(zhǎng)寬不能整除,所以夠分成一格的分成一格, 剩下不夠一格的分成兩份,左邊一份,右邊一份
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);
}
}
其實(shí)上述這段程序就是實(shí)現(xiàn)了幾個(gè)方法,loadTile()用于加載圖片,setTile()和resetTile()是把圖片與坐標(biāo)聯(lián)系起來,而onSizedChanged()是把手機(jī)屏幕像素塊化。這些方法都將為以下這個(gè)類服務(wù)。為了便于利用這些方法,以下這個(gè)類繼承自TileView。
由于我加了一些按鈕,所以游戲界面生成時(shí)沒有占滿整個(gè)屏幕,所以在設(shè)置坐標(biāo)和遍歷地圖時(shí)與源碼的數(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),便于邏輯實(shí)現(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; // 用于每個(gè)狀態(tài)下的文字提醒
public long mScore = 0;
private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>(); // 存儲(chǔ)蛇的所有坐標(biāo)的數(shù)組
private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>(); // 存儲(chǔ)蘋果的所有坐標(biāo)的數(shù)組
private static final Random RNG = new Random(); //用于生成蘋果坐標(biāo)的隨機(jī)數(shù)
// private static final String TAG = "SnakeView";
//開啟線程,不斷調(diào)用更新和重繪。這里利用了Handler類來實(shí)現(xiàn)異步消息處理機(jī)制
MyHandler handler=new MyHandler();
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
SnakeView.this.update(); //不斷調(diào)用update()方法
SnakeView.this.invalidate(); //請(qǐng)求重繪,不斷調(diào)用ondraw()方法
}
//調(diào)用sleep后,在一段時(shí)間后再sendmessage進(jìn)行UI更新
public void sleep(int delayMillis) {
this.removeMessages(0); //清空消息隊(duì)列
sendMessageDelayed(obtainMessage(0), delayMillis);
}
}
//這是三個(gè)構(gòu)造方法,別忘了加上下面這個(gè)初始化方法
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);
}
//添加蘋果的方法,最后將生成的蘋果坐標(biāo)存儲(chǔ)在上面定義的數(shù)組中
private void addRandomApple() {
Coordinate newCoord = null;
boolean found = false;
while (!found) {
// 這里設(shè)定了蘋果坐標(biāo)能隨機(jī)生成的范圍,并生成隨機(jī)坐標(biāo)。這里Google源碼中是直接使用變量
// mXTileCount和mYTileCount,我編譯時(shí)會(huì)報(bào)錯(cuò),因?yàn)殡S機(jī)數(shù)不能生成負(fù)數(shù),而直接使用這兩個(gè)變量程序不能
// 識(shí)別這個(gè)變量減去一個(gè)數(shù)后是否會(huì)是負(fù)數(shù),所以我把TileView里輸出的確切值放了進(jìn)去
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體內(nèi), 如果是,重新生成坐標(biāo)
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);
}
}
//更新蛇的運(yùn)動(dòng)軌跡
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;
}
}
//檢測(cè)是否撞墻
if ((newHead.x < 1) || (newHead.y < 3) || (newHead.x > mXTileCount - 2)
|| (newHead.y > mYTileCount - 9)) {
setMode(LOSE);
return;
}
//檢測(cè)蛇頭是否撞到自己
int snakelength = mSnakeTrail.size();
for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
Coordinate c = mSnakeTrail.get(snakeindex);
if (c.equals(newHead)) {
setMode(LOSE);
return;
}
}
//檢測(cè)蛇是否吃到蘋果
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; //蛇每遲到一個(gè)蘋果,延時(shí)就會(huì)減少,蛇的速度就會(huì)加快
growSnake = true;
}
}
mSnakeTrail.add(0,newHead);
if(!growSnake) {
mSnakeTrail.remove(mSnakeTrail.size() - 1);
}
//蛇頭和蛇身分別設(shè)置不同的圖片
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++;
}
}
給蘋果加載對(duì)應(yīng)的圖片
private void updateApples() {
for (Coordinate c : mAppleList) {
setTile(YELLOW_STAR, c.x, c.y);
}
}
// 該方法很重要,用于更新蛇,蘋果和墻的坐標(biāo)
// 這里設(shè)置了更新的時(shí)間間隔,我發(fā)現(xiàn)不加這個(gè)延時(shí)的話蛇運(yùn)動(dòng)時(shí)容易出現(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); //添加焦點(diǎn)
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ù)初始化方法,定義蛇的起始坐標(biāo),運(yùn)動(dòng)方向和得分變量。給數(shù)組添加坐標(biāo)的時(shí)候注意順序,因?yàn)橛猩哳^和蛇尾的區(qū)別
public void initNewGame() {
mSnakeTrail.clear();
mAppleList.clear();
//snake初始狀態(tài)時(shí)的個(gè)數(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; // 這個(gè)變量必須初始化,不然每次游戲結(jié)束重新開始后,蛇初始的方向?qū)⒉皇窍蛴?,而是你游戲結(jié)束時(shí)蛇的方向,
// 如果死的時(shí)候,蛇的方向向左,那么再次點(diǎn)擊開始時(shí)會(huì)無法繪出蛇的圖像
addRandomApple();
mScore=0;
}
// 根據(jù)各個(gè)數(shù)組中的數(shù)據(jù),遍歷地圖設(shè)置各點(diǎn)的圖片
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) { // 被加了圖片的點(diǎn)mTileGird是大于0的
canvas.drawBitmap(mTileArray[mTileGrid[x][y]], mXOffset + x * mTileSize, mYOffset + y * mTileSize, paint);
}
}
}
}
//把蛇和蘋果各點(diǎn)對(duì)應(yīng)的坐標(biāo)利用一個(gè)一維數(shù)組儲(chǔ)存起來
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;
}
//將當(dāng)前所有的游戲數(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ù)組中的坐標(biāo)數(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()的逆過程,用于恢復(fù)游戲數(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"));
}
// 設(shè)置鍵盤監(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;
}
// 設(shè)置不同狀態(tài)下提示文字的顯示內(nè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;
}
// 這里定義了一個(gè)空字符串,用于放入各個(gè)狀態(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);
}
//記錄坐標(biāo)位置
private class Coordinate {
public int x;
public int y;
public Coordinate(int newX, int newY) {
x = newX;
y = newY;
}
//觸碰檢測(cè),看蛇是否吃到蘋果
public boolean equals(Coordinate other) {
if (x == other.x && y == other.y) {
return true;
}
return false;
}
// 這個(gè)方法沒研究過起什么作用,我注釋掉對(duì)程序的運(yùn)行沒有影響
@Override
public String toString() {
return "Coordinate: [" + x + "," + y + "]";
}
}
}
以上是自定義View的實(shí)現(xiàn),實(shí)現(xiàn)了繪制游戲界面的主邏輯。將SnakeView作為一個(gè)UI控件插入到該界面的布局中。
下面開啟活動(dòng),顯示界面。
[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"; // 個(gè)人認(rèn)為這個(gè)變量就是一個(gè)中間值,在該類的最后一個(gè)方法中傳入該變量,完成操作。
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實(shí)例化,把這個(gè)布局當(dāng)一個(gè)UI控件一樣插入進(jìn)來
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); // 獲取背景音樂開關(guān)的狀態(tài)變量,在設(shè)置開關(guān)界面存儲(chǔ),在這里讀取
if(playMusic) { // 如果設(shè)置背景音樂打開,則開啟服務(wù),播放音樂
Intent intent_service = new Intent(GameActivity.this, MusicService.class);
startService(intent_service);
}
SnakeView.mMoveDelay=saved.getInt("nandu",500); // 獲取當(dāng)前設(shè)置的代表游戲難度的變量,在難度設(shè)置界面保存,在這里讀取
// 判斷是否有保存數(shù)據(jù),如果數(shù)據(jù)為空就準(zhǔn)備重新開始游戲
if (savedInstanceState == null) {
mSnakeView.setMode(SnakeView.READY);
} else {
// 暫停后的恢復(fù)
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);
}
}
// 給開始,暫停,退出,上下左右按鈕設(shè)置監(jiān)聽。根據(jù)當(dāng)前的狀態(tài)來決定界面的更新操作
public void onClick(View v) {
switch (v.getId()) {
case R.id.game_start:
// 重新開始游戲,這里延時(shí)變量必須初始化,不然每次游戲重新開始之后,蛇的運(yùn)動(dòng)速度不會(huì)初始化
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());
}
}
下面是游戲效果圖,運(yùn)行界面和暫停界面。我把邏輯流程圖也貼出來,有什么問題大家可以留言,多多交流!


本文通過帶大家回憶經(jīng)典游戲的同時(shí),學(xué)習(xí)了利用java開發(fā)Android游戲——貪吃蛇,希望本文在大家學(xué)習(xí)Android開發(fā)有所幫助。
- Android仿開心消消樂大樹星星無限循環(huán)效果
- Android游戲源碼分享之2048
- Unity3D游戲引擎實(shí)現(xiàn)在Android中打開WebView的實(shí)例
- Android游戲開發(fā)實(shí)踐之人物移動(dòng)地圖的平滑滾動(dòng)處理
- Android 游戲開發(fā)之Canvas畫布的介紹及方法
- Android游戲開發(fā)之碰撞檢測(cè)(矩形碰撞、圓形碰撞、像素碰撞)
- Android五子棋游戲程序完整實(shí)例分析
- 以一個(gè)著色游戲展開講解Android中區(qū)域圖像填色的方法
- Android高仿2048小游戲?qū)崿F(xiàn)代碼
- Android開心消消樂代碼實(shí)例詳解
相關(guān)文章
TextView實(shí)現(xiàn)圖文混合編排的方法
這篇文章主要為大家詳細(xì)介紹了TextView實(shí)現(xiàn)圖文混合編排的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
Android studio 出現(xiàn)錯(cuò)誤Run with --stacktrace option to get the s
這篇文章主要介紹了 Android studio 出現(xiàn)錯(cuò)誤Run with --stacktrace option to get the stack trace. Run with --info or --debu的相關(guān)資料,需要的朋友可以參考下2016-11-11
Android小掛件(APP Widgets)設(shè)計(jì)指導(dǎo)
這篇文章主要為大家詳細(xì)介紹了Android小掛件APP Widgets設(shè)計(jì)指導(dǎo),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
究其為啥需要多線程的本質(zhì)就是異步處理,直觀一點(diǎn)說就是不要讓用戶感覺到“很卡”為了提高用戶體驗(yàn)?zāi)鞘潜仨氁褂玫?/div> 2013-06-06
Android實(shí)現(xiàn)自定義滑動(dòng)刻度尺方法示例
這篇文章主要給大家介紹了關(guān)于Android實(shí)現(xiàn)自定義滑動(dòng)刻度尺的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04最新評(píng)論

