1. 程式人生 > >是男人就下100層(簡仿)

是男人就下100層(簡仿)

package com.mjc.mendown.view;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import com.mjc.mendown.R;
import com.mjc.mendown.util.PositionUtil;

import java.util.ArrayList;

/**
 * Created by mjc on 2016/3/3.
 */
public class GameLayout extends View {

    //當前檢視(GameLayout)的長和寬
    private int mLayoutWidth;
    private int mLayoutHeight;
    //輔助繪製障礙物的物件
    private Barrier mBarrier;
    //輔助繪製人物的物件
    private Person mPerson;
    //面板繪製的物件
    private Score mScore;

    private Paint mPaint;
    //小人的圓形半徑
    private int radius = 50;
    //不斷繪製的執行緒
    private Thread mThread;

    private MyHandler myHandler;
    private int mBarrierMoveSpeed = 8;
    //人物是否自動下落狀態
    private boolean isAutoFall;
    //遊戲是否正在執行
    private boolean isRunning;
    //人物左右移動的速度
    private int mPersonMoveSpeed = 20;
    //需要繪製的小人
    private Bitmap bitmap;

    //畫面中障礙物的位置資訊
    private ArrayList<Integer> mBarrierXs;
    private ArrayList<Integer> mBarrierYs;
    //障礙物起始和產生障礙的間隔
    private int mBarrierStartY = 500;
    private int mBarrierInterval = 500;
    //障礙物的高度
    private int mBarrierHeight = 60;
    //人物所站立的障礙在畫面中的index
    private int mTouchIndex = -1;

    //當小人自動下落瞬間,開始計時,單位毫秒
    private float mFallTime = 0;

    //重力加速度
    public static final float G = 9.8f;

    //總得分
    private int mTotalScore;
    //份數版塊的文字大小
    private int mTextSize = 16;

    //失敗後,彈出的選單,按鈕的位置
    private RectF mRestartRectf;
    private RectF mQuiteRectf;
    //按鈕的寬度和高度,這裡我省事沒有轉化為DP,都是直接用px,所以可能會
    //產生適配上的問題。
    private int mButtonWidth = 300;
    private int mButtonHeight = 120;
    private int Padding = 20;

    public GameLayout(Context context) {
        super(context);
        init();
    }

    public GameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        //初始化畫筆
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.GRAY);
        mPaint.setStrokeWidth(10);
        //讀取本地的img圖片
        bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img);
        //預設開始自動下落
        isAutoFall = true;
        myHandler = new MyHandler();
        //用來記錄畫面中,每一個障礙物的x座標
        mBarrierXs = new ArrayList<>();
        //和上面的x對應的每個障礙物的y座標
        mBarrierYs = new ArrayList<>();
        //將文字大小轉化成DP
        mTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mTextSize, getResources().getDisplayMetrics());
        //啟動遊戲
        isRunning = true;
        startGame();
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //當前方法,是在onMeasure呼叫之後,進行回撥,所以直接getMeasureWidth等
        //獲取當前檢視的寬和高
        mLayoutWidth = getMeasuredWidth();
        mLayoutHeight = getMeasuredHeight();
        //根據檢視寬高,初始化障礙物的資訊
        mBarrier = new Barrier(mLayoutWidth, mPaint);
        mBarrier.setHeight(mBarrierHeight);
        //建立人物繪製類物件
        mPerson = new Person(mPaint, radius, bitmap);
        mPerson.mPersonY = 300;
        mPerson.mPersonX = mLayoutWidth / 2;
        //初始化分數繪製物件
        mScore = new Score(mPaint);
        mScore.x = mLayoutWidth / 2 - mScore.panelWidth / 2;

        //選單上重啟按鈕的左邊座標,mRestartRectf是重啟按鈕繪製區域
        int rX = mLayoutWidth / 2 - 20 - mButtonWidth;
        int rY = mLayoutHeight * 3 / 5;
        mRestartRectf = new RectF(rX, rY, rX + mButtonWidth, rY + mButtonHeight);
        //下面是選單上退出按鈕的區域
        int qX = mLayoutWidth / 2 + 20;
        int qY = mLayoutHeight * 3 / 5;
        mQuiteRectf = new RectF(qX, qY, qX + mButtonWidth, qY + mButtonHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪製分數面板
        generateScore(canvas);
        //繪製障礙物
        generateBarrier(canvas);
        //如果小人正在下落,才檢測是否碰撞
        if (isAutoFall)
            checkTouch();
        //根據是否下落,繪製小人的位置
        generatePerson(canvas);
        //如果沒有結束,說明就是在執行
        //檢查小人是否超出邊界,判斷遊戲是否結束
        isRunning = !checkIsGameOver();
        //如果遊戲結束
        if (!isRunning) {
            //繪製面板
            drawPanel(canvas);
            //繪製遊戲結束數字
            notifyGameOver(canvas);
            //繪製兩個按鈕
            drawButton(canvas, mRestartRectf, "重來", Color.parseColor("#ae999999"), Color.WHITE);
            drawButton(canvas, mQuiteRectf, "退出", Color.parseColor("#ae999999"), Color.WHITE);
        }
    }

    /**
     * 繪製結束彈出框的背景區域
     * @param canvas
     */
    private void drawPanel(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setColor(Color.parseColor("#8e333333"));
        canvas.drawRoundRect(new RectF(mRestartRectf.left - Padding * 2, mLayoutHeight * 2 / 5 - Padding, mQuiteRectf.right + Padding * 2, mQuiteRectf.bottom + Padding), Padding, Padding, mPaint);
    }

    /**
     * 繪製Game over文字
     * @param canvas
     */
    private void notifyGameOver(Canvas canvas) {
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setTextSize(mTextSize * 1.5f);
        mPaint.setColor(Color.parseColor("#cc0000"));
        mPaint.setFakeBoldText(false);
        canvas.drawText("Game over", mLayoutWidth / 2, mLayoutHeight / 2, mPaint);
    }

    //繪製選單按鈕,下面的操作使得文字能夠居中顯示
    private void drawButton(Canvas canvas, RectF rectF, String text, int strokeColor, int textColor) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(strokeColor);
        canvas.drawRoundRect(rectF, 10, 10, mPaint);
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(textColor);
        mPaint.setTextAlign(Paint.Align.CENTER);
        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
        float textHeight = fontMetrics.bottom - fontMetrics.top;
        int y = (int) (rectF.top + textHeight / 2 + (rectF.bottom - rectF.top) / 2 - fontMetrics.bottom);
        canvas.drawText(text, rectF.left + mButtonWidth / 2, y, mPaint);

    }

    /**
     * 繪製分數面板
     * @param canvas
     */
    private void generateScore(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.parseColor("#666666"));
        mScore.drawPanel(canvas);
        mPaint.setColor(Color.WHITE);
        mPaint.setFakeBoldText(true);
        mPaint.setTextSize(mTextSize);
        mScore.drawScore(canvas, mTotalScore + "");
    }

    /**據初始位置,生成障礙物,難點
     * 1.繪製時,每一個障礙物間的距離是一致的
     * 2.繪製時,都是從第一個障礙物開始繪製
     * 3.迴圈繪製,並把障礙物的x,y位置,分別儲存在陣列中
     * 4.障礙物逐漸上升,當障礙物超出邊界時,我們刪除陣列中儲存的
     *      第一個位置的x,但是保持原有下面已經出現過得障礙物x的位置
     *      並在最後新增新的障礙物的位置;y位置,每次都重新生成,重新
     *      儲存在陣列中
     * */
    private void generateBarrier(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.DKGRAY);
        //每次都清楚Y座標資訊,因為後面會重新生成
        mBarrierYs.clear();
        //死迴圈,有條件退出
        for (int i = 0; ; ) {
            //i小於陣列中的長度,那麼取出原有的x位置資訊,繪製舊障礙物;
            // 否則就隨機生成新的座標資訊新增到陣列中
            if (i < mBarrierXs.size()) {
                mBarrier.mPositionX = mBarrierXs.get(i);
            } else {
                mBarrier.mPositionX = PositionUtil.getRangeX(mLayoutWidth);
                mBarrierXs.add(mBarrier.mPositionX);
            }
            //障礙物的y座標
            mBarrier.mPositionY = mBarrierStartY + mBarrierInterval * i;
            mBarrierYs.add(mBarrier.mPositionY);
            //繪製到檢視外,則不再進行繪製,退出迴圈
            if (mBarrier.mPositionY > mLayoutHeight) {
                break;
            }
            mBarrier.drawBarrier(canvas);
            i++;
        }
    }


    private void generatePerson(Canvas canvas) {
        //如果小人在自動下落
        if (isAutoFall) {
            //自動下落繪製
//            mPerson.autoFallY();
            mFallTime += 20;
            //根據重力加速度計算小人下落的位置
            mPerson.mPersonY += mFallTime / 1000 * G;
            mPerson.draw(canvas);
        } else {
            // 獲取被擋住的障礙位置
            Log.v("@time", mFallTime / 1000 + "");
            //小人被擋住,下落的時間重置
            mFallTime = 0;
            //mTouchIndex表示的是小人在檢視中被阻擋的的障礙物的位置
            //如果是小於0,表示沒有阻擋,
            if (mTouchIndex >= 0) {
                //設定小人被阻擋的位置,被進行繪製
                mPerson.mPersonY = mBarrierYs.get(mTouchIndex) - 2 * radius;
                mPerson.draw(canvas);
            }
        }
    }

    /**
     *碰撞檢測
     */
    private void checkTouch() {
        for (int i = 0; i < mBarrierYs.size(); i++) {
            //碰撞檢測
            if (isTouchBarrier(mBarrierXs.get(i), mBarrierYs.get(i))) {
                mTouchIndex = i;
                isAutoFall = false;
            }
        }
    }

    private boolean checkIsGameOver() {
        return mPerson.mPersonY < 0 || mPerson.mPersonY > mLayoutHeight - 2 * radius;
    }

    /**
     * 碰撞檢測
     * @param x 障礙物x座標
     * @param y 障礙物y座標
     * @return
     */
    private boolean isTouchBarrier(int x, int y) {
        boolean res = false;
        int pY = mPerson.mPersonY + 2 * radius;
        //在瞬間重新整理的時候,只要小人的位置和障礙的位置,差值在小人和障礙物的瞬間重新整理的最大值就屬於碰撞
        //比如:小人下落速度為a,障礙物上升速度為b,畫面重新整理時間為t,瞬間重新整理,會有個最大差值,這個值就是
        //臨界值
        if (Math.abs(pY - y) <= Math.abs(mBarrierMoveSpeed + Person.SPEED + mFallTime / 1000 * G)) {
            if (mPerson.mPersonX + 2 * radius >= x && mPerson.mPersonX <= x + mBarrier.getWidth()) {
                res = true;
            }
        }
        return res;
    }


    public void startGame() {
        mThread = new Thread() {
            @Override
            public void run() {
                super.run();
                while (isRunning) {
                    //開始讓障礙往上面滾動,障礙物的繪製,是跟mBarrierStartY相關的
                    mBarrierStartY -= mBarrierMoveSpeed;
                    //當第一個障礙物開始消失
                    if (mBarrierStartY <= -mBarrierInterval - mBarrierHeight) {
                        mBarrierStartY = -mBarrierHeight;
                        //刪除剛消失的障礙物座標資訊
                        if (mBarrierXs.size() > 0)
                            mBarrierXs.remove(0);
                        //得分++
                        mTotalScore++;
                        //小球碰撞位置--
                        mTouchIndex--;
                    }
                    //這裡應該是可以直接用postInvalidate()
                    myHandler.sendEmptyMessage(0x1);
                    try {
                        //每20毫秒重新整理一次介面
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        mThread.start();
    }

    private class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0x1) {
                invalidate();
            }

        }
    }

    //控制小人向左移動
    public void moveLeft() {
        int x = mPerson.mPersonX;
        int dir = x - mPersonMoveSpeed;
        if (dir < 0)
            dir = 0;
        mPerson.mPersonX = dir;
        //移動過程中,啟動邊界檢測,設定isAutoFall為true
        checkIsOutSide(dir);
        invalidate();
    }

    /**
     * 類似moveLeft
     */
    public void moveRight() {
        int x = mPerson.mPersonX;
        int dir = x + mPersonMoveSpeed;
        if (dir > mLayoutWidth - radius * 2)
            dir = mLayoutWidth - radius * 2;
        mPerson.mPersonX = dir;
        checkIsOutSide(dir);
        invalidate();
    }

    private void checkIsOutSide(int x) {
        isAutoFall = true;
    }

    public void stop() {
        isRunning = false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //遊戲正在執行,沒有生成選單
                if (isRunning)
                    break;
                //獲取觸控位置資訊
                float x = event.getX();
                float y = event.getY();
                //如果觸控到重啟遊戲的按鈕,觸發
                if (mRestartRectf.contains(x, y)) {
                    restartGame();
                } else if (mQuiteRectf.contains(x, y)) {//觸控到退出按鈕
                    Toast.makeText(getContext(), "退出到主選單", Toast.LENGTH_SHORT).show();
                }
                break;

        }
        return super.onTouchEvent(event);
    }

    /**
     * 重置遊戲資訊
     */
    private void restartGame() {
        mBarrierXs.clear();
        mBarrierYs.clear();
        mBarrierStartY = 500;
        mPerson.mPersonY = 300;
        mPerson.mPersonX = mLayoutWidth / 2;
        mTotalScore = 0;
        isAutoFall = true;
        mFallTime = 0;
        isRunning = true;
        startGame();
    }
}
       程式碼中的註釋,都是我後來加上的,我自己寫的時候,是大多沒有註釋,但是為了大家看起來方便,我都會寫上詳細的註釋。