• 推箱子小游戏——代码分析


    代码组成

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

  • 相关阅读:
    5.3 java虚拟机的体系结构
    5.2 java虚拟机的生命周期
    3.11.5 doPrivileged()方法
    3.11.1 implies方法
    MT【178】平移不变性
    MT【177】三个乘积和
    MT【176】两两乘积
    MT【175】刚刚凑巧
    MT【174】凹凸无妨
    MT【173】齐次消元单变量
  • 原文地址:https://www.cnblogs.com/20189210mujian/p/10878319.html
Copyright © 2020-2023  润新知