是男人就下100層(簡仿)
阿新 • • 發佈:2019-01-28
程式碼中的註釋,都是我後來加上的,我自己寫的時候,是大多沒有註釋,但是為了大家看起來方便,我都會寫上詳細的註釋。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(); } }