代碼組成
本項目主要分類三個Activity類:
- MainActivity: 主活動類游戲初始界面
- GameActivity:游戲界面
- GameLevelActivity:關卡選擇界面
三個活動類對應的三個布局:
- activity_main.xml: 主活動布局。
- act_game_activity.xml:游戲活動布局。
- act_xuan_guan_qia.xml: 選擇關卡布局
其他輔助類:
- GameBitmaps: 用來加載圖片
- GameLevels:用來存放關卡信息和返回關卡信息數組
- GameState:用來使用StringBuffer存儲當前關卡狀態
- GameView:自定義的View類,繪制游戲界面,監聽touch動作,對行為進行邏輯判斷
- TCell: 自定義的類,用於表示旗幟位置
代碼調用關系
MainActivity
public class MainActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnGameIntro = (Button) findViewById(R.id.btn_game_intro);
btnGameIntro.setOnClickListener(
new View.OnClickListener(){
@Override
public void onClick(View view){
Intent intent = new Intent(MainActivity.this, GameIntroActivity.class);
startActivity(intent);
Toast.makeText(MainActivity.this, "按了游戲簡介按鈕", Toast.LENGTH_SHORT).show();
}
}
);
Button btnExitGame = (Button) findViewById(R.id.btn_exit);
btnExitGame.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
finish();
}
});
Button btnStartGame = (Button) findViewById(R.id.btn_start_game);
btnStartGame.setOnClickListener(
new View.OnClickListener(){
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, GameLevelActivity.class);
startActivity(intent);
}
});
}
}
在主活動類的onCreate方法中構建其布局layout文件,在通過三個Button類和findViewById方法與布局中三個Button綁定,並對三個Button建立Click監聽器。
其中id為start_game按鈕的監聽器回調函數是用來調用另一個GameLevelActivity。
GameLevelActivity
public class GameLevelActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_xuan_guan_qia);
GridView gv_levels = (GridView) findViewById(R.id.gv_levels);
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this, R.layout.gv_levels_item_textview, GameLevels.getLevelList());
gv_levels.setAdapter(arrayAdapter);
gv_levels.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(GameLevelActivity.this, GameActivity.class);
intent.putExtra("Selected_level", position+1);
startActivity(intent);
}
});
}
}
首先構建act_xuan_guan_qia布局,每個關卡采用Grid網格布局,使用ArrayAdapter對每個Grid中的Textview內容進行賦值,最后對每個Grid建立監聽器,回調函數是啟動GameActivity活動類,並將所選關卡的值傳給GameActivity。
GameActivity
public class GameActivity extends Activity {
public static Toast toast;
public static Toast toast1;
public static Toast toast2;
public static final String KEY_SELECTED_LEVEL = "Selected_level";
private GameState mCurrentState;
private GameView mGameView;
private int selected_level;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
selected_level = getIntent().getIntExtra(KEY_SELECTED_LEVEL,1);
mCurrentState = new GameState(GameLevels.getLevel(selected_level));
setContentView(R.layout.act_game_activity);
toast = toast.makeText(this,"恭喜你,通關了!", toast.LENGTH_LONG);
toast1 = toast1.makeText(this, "已經是第一關了!", toast1.LENGTH_SHORT);
toast2 = toast2.makeText(this,"已經是最后一關了!",toast2.LENGTH_SHORT);
mGameView = (GameView) findViewById(R.id.game_board);
mGameView.setText(selected_level);
Button mBtnPrvLevel = (Button) findViewById(R.id.btn_prv_level);
GameLevels.OriginalLevels.size();
mBtnPrvLevel.setOnClickListener(
new View.OnClickListener(){
@Override
public void onClick(View view) {
if (selected_level == 1) {
toast1.show();
} else {
mGameView.goto_level(--selected_level);
mGameView.setText(selected_level);
}
}
}
);
Button mBtnNextLevel = (Button) findViewById(R.id.btn_next_level);
mBtnNextLevel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (selected_level == GameLevels.OriginalLevels.size()){
toast2.show();
} else {
mGameView.goto_level(++selected_level);
mGameView.setText(selected_level);
}
}
});
Button mBtnReset = (Button) findViewById(R.id.btn_reset);
mBtnReset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mGameView.goto_level(selected_level);
mGameView.setText(selected_level);
}
});
Button btnExitGame = (Button) findViewById(R.id.btn_exit);
btnExitGame.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
finish();
}
});
}
public GameState getCurrentState(){
return mCurrentState;
}
public void setmCurrentState(int level){
String[] test = GameLevels.getLevel(level);
mCurrentState = new GameState(GameLevels.getLevel(level));
}
}
- 構建act_game_activity布局
- GameState mCurrentState用來獲取所選關卡的字符串數組
- GameView mGameView用於實例化自定義的View類,來調用其中的方法
- 創建toast信息
- 對四個Button建立監聽器,實現上下關切換和重置退出功能。
核心代碼分析
核心代碼都在GameView中,包含游戲界面的繪制和游戲操作的邏輯判斷。
public class GameView extends View {
private SoundPool mSoundPool;
private final int mSoundOneStep;
private float mCellWidth;
public static final int CELL_NUM_PER_LINE = 12;
private float mVolume = (float) 0.5;
private int mManRow;
private int mManColumn;
private int mBoxRow;
private int mBoxColumn;
public String text;
private List<TCell> mFlagCells = new ArrayList<>();
private GameActivity mGameActivity;
public GameView(Context context, AttributeSet attrs) {
super(context,attrs);
mGameActivity = (GameActivity) context;
mSoundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
mSoundOneStep = mSoundPool.load(mGameActivity, R.raw.onestep, 1);
/* get_gongren_chushi_weizhi();
get_XiangZi_ChuShi_WeiZhi();*/
get_flag_weizhi();
GameBitmaps.loadGameBitmaps(getResources());
}
- onDraw用來繪制游戲界面,首先通過兩個for循環繪制了一個12 x 12網格,再調用drawGameBoard繪制游戲板塊,即根據StringBuffer信息繪制圖片(使用switch case),再調用drawtt繪制一句話顯示當前關卡,當箱子都到達旗幟時,即B的case數為零,調用toast.show( )。
//當GameView實例的尺寸發生變化,就會調用onSizeChanged
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCellWidth = w / CELL_NUM_PER_LINE;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//背景色
Paint background = new Paint();
background.setColor(getResources().getColor(R.color.ivory));
canvas.drawRect(0, 0, getWidth(), getHeight(), background);
//繪制游戲區域
Paint linePaint = new Paint();
linePaint.setColor(Color.BLACK);
for (int r = 0; r <= CELL_NUM_PER_LINE; r++)
canvas.drawLine(0, r * mCellWidth, getWidth(), r * mCellWidth, linePaint);
for (int c = 0; c <= CELL_NUM_PER_LINE; c++)
canvas.drawLine(c * mCellWidth, 0, c * mCellWidth, CELL_NUM_PER_LINE * mCellWidth, linePaint);
drawGameBoard(canvas);
drawtt(canvas);
}
private void drawGameBoard(Canvas canvas){
int count =0;
Rect srcRect;
Rect destRect;
StringBuffer[] labelInCells = mGameActivity.getCurrentState().getLabelInCells();
for (TCell tCell: mFlagCells) {
if (labelInCells[tCell.row].charAt(tCell.column) == 'B')
labelInCells[tCell.row].setCharAt(tCell.column,'R');
srcRect = new Rect(0, 0, GameBitmaps.flagBitmap.getWidth(), GameBitmaps.flagBitmap.getHeight());
destRect=getRect(tCell.row, tCell.column);
canvas.drawBitmap(GameBitmaps.flagBitmap, srcRect, destRect, null);
}
for (int r=0;r<labelInCells.length;r++)
for (int c=0;c<labelInCells[r].length();c++){
destRect=getRect(r,c);
switch (labelInCells[r].charAt(c)){
case 'W':
srcRect = new Rect(0,0,GameBitmaps.wallBitmap.getWidth(),GameBitmaps.wallBitmap.getHeight());
canvas.drawBitmap(GameBitmaps.wallBitmap, srcRect, destRect, null);
break;
case 'B':
srcRect = new Rect(0, 0, GameBitmaps.boxBitmap.getWidth(), GameBitmaps.boxBitmap.getHeight());
canvas.drawBitmap(GameBitmaps.boxBitmap, srcRect, destRect, null);
mBoxRow = r;mBoxColumn = c;
count++;
break;
case 'F':
srcRect = new Rect(0, 0, GameBitmaps.flagBitmap.getWidth(), GameBitmaps.flagBitmap.getHeight());
canvas.drawBitmap(GameBitmaps.flagBitmap, srcRect, destRect, null);
break;
case 'M':
srcRect = new Rect(0, 0, GameBitmaps.manBitmap.getWidth(), GameBitmaps.manBitmap.getHeight());
canvas.drawBitmap(GameBitmaps.manBitmap, srcRect, destRect, null);
mManRow = r; mManColumn = c;
break;
case 'R':
srcRect = new Rect(0, 0, GameBitmaps.boxBitmap.getWidth(), GameBitmaps.boxBitmap.getHeight());
canvas.drawBitmap(GameBitmaps.boxBitmap, srcRect, destRect, null);
break;
}
}
if (count == 0) toast.show();
}
protected void drawtt(Canvas canvas) {
Paint mPaint = new Paint();
mPaint.setStrokeWidth(3);
mPaint.setTextSize(100);
mPaint.setColor(Color.BLUE);
mPaint.setTextAlign(Paint.Align.LEFT);
Rect bounds = new Rect();
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
//int baseline = (getMeasuredHeight() - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
canvas.drawText(text,300, 1700, mPaint);
}
public void setText(int level){
this.text = "當前的關卡為第"+level+"關";
}
private Rect getRect(int row, int column) {
int left = (int)(column * mCellWidth);
int top = (int) (row * mCellWidth);
int right = (int)((column + 1) * mCellWidth);
int bottom = (int)((row + 1) * mCellWidth);
return new Rect(left, top, right, bottom);
}
- touch_blow_to_man 等四個方法:判斷觸摸點與小人的位置關系
private boolean touch_blow_to_man(int touch_x, int touch_y, int manRow, int manColumn) {
int belowRow = manRow + 1;
Rect belowRect = getRect(belowRow, manColumn);
return belowRect.contains(touch_x, touch_y);
}
private boolean touch_right_to_man(int touch_x, int touch_y, int manRow, int manColumn) {
int rightColumn = manColumn + 1;
Rect rightRect = getRect(manRow, rightColumn);
return rightRect.contains(touch_x, touch_y);
}
private boolean touch_up_to_man(int touch_x, int touch_y, int manRow, int manColumn) {
int upRow = manRow - 1;
Rect upRect = getRect(upRow, manColumn);
return upRect.contains(touch_x, touch_y);
}
private boolean touch_left_to_man(int touch_x, int touch_y, int manRow, int manColumn) {
int leftColumn = manColumn - 1;
Rect leftRect = getRect(manRow, leftColumn);
return leftRect.contains(touch_x, touch_y);
}
- isBoxBlowMan等四個方法:判斷箱子位置與小人的關系
private boolean isBoxBlowMan(){
StringBuffer[] labelInCells = mGameActivity.getCurrentState().getLabelInCells();
if (labelInCells[mManRow+1].charAt(mManColumn) == 'B' || labelInCells[mManRow+1].charAt(mManColumn) == 'R'){
mBoxRow = mManRow + 1;
mBoxColumn = mManColumn;
return true;
}else return false;
}
private boolean isBoxUpMan(){
StringBuffer[] labelInCells = mGameActivity.getCurrentState().getLabelInCells();
if (labelInCells[mManRow-1].charAt(mManColumn) == 'B' || labelInCells[mManRow-1].charAt(mManColumn) == 'R'){
mBoxRow = mManRow - 1;
mBoxColumn = mManColumn;
return true;
}else return false;
}
private boolean isBoxLeftMan(){
StringBuffer[] labelInCells = mGameActivity.getCurrentState().getLabelInCells();
if (labelInCells[mManRow].charAt(mManColumn-1) == 'B' || labelInCells[mManRow].charAt(mManColumn-1) == 'R'){
mBoxRow = mManRow;
mBoxColumn = mManColumn-1;
return true;
}else return false;
}
private boolean isBoxRightMan(){
StringBuffer[] labelInCells = mGameActivity.getCurrentState().getLabelInCells();
if (labelInCells[mManRow].charAt(mManColumn+1) == 'B' || labelInCells[mManRow].charAt(mManColumn+1) == 'R'){
mBoxRow = mManRow;
mBoxColumn = mManColumn+1;
return true;
}else return false;
}
-
OnTouchEvent為觸摸監聽器,回調函數包含了邏輯判斷:如是否撞牆、修改數組和相關屬性記錄小人和箱子移動后的位置變化。每調用一次回調函數都要使當前界面無效,重新繪制界面。
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_DOWN)
return true;int touch_x = (int) event.getX(); //觸摸點的x坐標 int touch_y = (int) event.getY(); //觸摸點的y坐標 if (touch_up_to_man(touch_x, touch_y, mManRow, mManColumn)) { StringBuffer[] labelInCells = mGameActivity.getCurrentState().getLabelInCells(); if (isBoxUpMan()){ if (mBoxRow - 1 >= 0 && labelInCells[mBoxRow - 1].charAt(mBoxColumn) != 'W' && labelInCells[mBoxRow-1].charAt(mBoxColumn) != 'B') { labelInCells[mBoxRow].setCharAt(mBoxColumn,'M'); labelInCells[mManRow].setCharAt(mManColumn,' '); labelInCells[mBoxRow-1].setCharAt(mBoxColumn,'B'); mBoxRow--; mManRow--; mSoundPool.play(mSoundOneStep, mVolume, mVolume, 1, 0, 1f); } }else if (mManRow - 1 >= 0 && labelInCells[mManRow - 1].charAt(mManColumn) != 'W') { labelInCells[mManRow].setCharAt(mManColumn,' '); labelInCells[mManRow-1].setCharAt(mManColumn,'M'); mManRow--; mSoundPool.play(mSoundOneStep, mVolume, mVolume, 1, 0, 1f); } } if (touch_blow_to_man(touch_x, touch_y, mManRow, mManColumn)) { StringBuffer[] labelInCells = mGameActivity.getCurrentState().getLabelInCells(); if (isBoxBlowMan()) { if (mBoxRow + 1 < CELL_NUM_PER_LINE && labelInCells[mBoxRow + 1].charAt(mBoxColumn) != 'W' && labelInCells[mBoxRow+1].charAt(mBoxColumn) != 'B') { labelInCells[mBoxRow].setCharAt(mBoxColumn,'M'); labelInCells[mManRow].setCharAt(mManColumn,' '); labelInCells[mBoxRow+1].setCharAt(mBoxColumn,'B'); mBoxRow++; mManRow++; mSoundPool.play(mSoundOneStep, mVolume, mVolume, 1, 0, 1f); } }else if (mManRow + 1 < CELL_NUM_PER_LINE && labelInCells[mManRow + 1].charAt(mManColumn) != 'W'){ labelInCells[mManRow].setCharAt(mManColumn,' '); labelInCells[mManRow+1].setCharAt(mManColumn,'M'); mManRow++; mSoundPool.play(mSoundOneStep, mVolume, mVolume, 1, 0, 1f); } } if (touch_right_to_man(touch_x, touch_y, mManRow, mManColumn)){ StringBuffer[] labelInCells = mGameActivity.getCurrentState().getLabelInCells(); if (isBoxRightMan()){ if (mBoxColumn + 1 < CELL_NUM_PER_LINE && labelInCells[mBoxRow].charAt(mBoxColumn + 1) != 'W' && labelInCells[mBoxRow].charAt(mBoxColumn + 1) != 'B') { labelInCells[mBoxRow].setCharAt(mBoxColumn,'M'); labelInCells[mManRow].setCharAt(mManColumn,' '); labelInCells[mBoxRow].setCharAt(mBoxColumn+1,'B'); mBoxColumn++; mManColumn++; mSoundPool.play(mSoundOneStep, mVolume, mVolume, 1, 0, 1f); } }else if (mManColumn + 1 < CELL_NUM_PER_LINE && labelInCells[mManRow].charAt(mManColumn + 1) != 'W'){ labelInCells[mManRow].setCharAt(mManColumn,' '); labelInCells[mManRow].setCharAt(mManColumn+1,'M'); mManColumn++; mSoundPool.play(mSoundOneStep, mVolume, mVolume, 1, 0, 1f); } } if (touch_left_to_man(touch_x, touch_y, mManRow, mManColumn)){ StringBuffer[] labelInCells = mGameActivity.getCurrentState().getLabelInCells(); if (isBoxLeftMan()){ if (mBoxColumn -1 >= 0 && labelInCells[mBoxRow].charAt(mBoxColumn - 1) != 'W' && labelInCells[mBoxRow].charAt(mBoxColumn - 1) != 'B'){ labelInCells[mBoxRow].setCharAt(mBoxColumn,'M'); labelInCells[mManRow].setCharAt(mManColumn,' '); labelInCells[mBoxRow].setCharAt(mBoxColumn-1,'B'); mBoxColumn--; mManColumn--; mSoundPool.play(mSoundOneStep, mVolume, mVolume, 1, 0, 1f); } }else if (mManColumn - 1 >= 0 && labelInCells[mManRow].charAt(mManColumn - 1) != 'W') { labelInCells[mManRow].setCharAt(mManColumn,' '); labelInCells[mManRow].setCharAt(mManColumn-1,'M'); mManColumn--; mSoundPool.play(mSoundOneStep, mVolume, mVolume, 1, 0, 1f); } } postInvalidate();//使界面失效 引發onDraw方法執行 return true;
}
以下兩個方法用來獲取旗幟的位置,和進入另一個關卡。
public void get_flag_weizhi(){
mFlagCells.clear();
StringBuffer[] labelInCells = mGameActivity.getCurrentState().getLabelInCells();
for (int r = 0; r < GameView.CELL_NUM_PER_LINE; r++)
for (int c = 0; c< GameView.CELL_NUM_PER_LINE;c++){
if (labelInCells[r].charAt(c) == 'F'){
TCell tCell = new TCell();
tCell.row=r;
tCell.column=c;
mFlagCells.add(tCell);
}
}
}
public void goto_level(int level){
GameActivity activity = new GameActivity();
activity.setmCurrentState(level);
get_flag_weizhi();
postInvalidate();
}
}
GameLevels中的關卡信息表示
public static final int DEFAULT_ROW_NUM = 12;
public static final int DEFAULT_COLUMN_NUM = 12;
//游戲區單元格放了什么
public static final char NOTHING = ' '; //該單元格啥也沒有
public static final char BOX = 'B'; //該單元格放的是箱子
public static final char FLAG = 'F'; //紅旗,表示箱子的目的地
public static final char MAN = 'M'; //搬運工
public static final char WALL = 'W'; //牆
public static final char MAN_FLAG = 'R'; //搬運工 + 紅旗
public static final char BOX_FLAG = 'X'; //箱子 + 紅旗
public static final String [] LEVEL_1 = {
"WWWWWWWWWWWW",
"W FW",
"W W",
"W W",
"W WWWWWWWW W",
"W W",
"W B W",
"W M W",
"W W",
"W W",
"W W",
"WWWWWWWWWWWW"
};
自己實現的功能分析
整個項目功能都是我實現的,講一下遇到的問題。
- 自定義的View類與layout中xml文件的結合,最后成功解決了。
- 多個箱子的實現:因為箱子的個數是不確定的,不可能給每個箱子都定義兩個屬性來表示位置,但發現每次小人只能推一個箱子,所以在判斷小人旁有箱子的時候,將位置屬性附給該箱子。
- 判斷是否勝利,提前用TCell數組獲取旗幟的位置信息,提前將旗幟繪制出來,判斷該位置上是否有箱子。
- StringBuffer的使用。使用StringBuffer可以對字符串的單個字符進行操作,在切換關卡的時候,使用“=”將StringBuffer指向新關卡的字符串的地址,再重新繪制界面。
- 位置判斷遇到了許多少考慮的情況,在調試中不斷修改。
總結
該項目整體比較簡單,功能也比較小,寫下來也幫助我熟悉了Android開發,紙上談兵終覺淺,本項目讓我對書上一些布局、監聽器等章節的內容有了深入的理解,也熟悉了Java中的一些細節知識,例如StringBuffer,如果再有一些時間,可以將這個小游戲做的更好更美觀。