代码组成
本项目主要分类三个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,如果再有一些时间,可以将这个小游戏做的更好更美观。