近来,事情不多,闲暇之时,就想写个简单的游戏练练手。太复杂了,不使用游戏引擎来做,是非常困难的。这里,其实也没有打算说,开发一款游戏上线,就是练习如何自定义一个View。我想啊想,终于想到一个简单的游戏,那就是类似是男人就下100层的游戏。我也不需要找素材,我就直接绘制简答的物体就好了,重点在于自定义视图和绘制嘛。请原谅我还不会录制漂亮的gif图,希望有人能在留言中教我,下面是我的游戏效果:
        这里,我并没有使用重力传感器来做控制,为什么呢?首先,我这里呢,就是演示一下自定义view,其次,重力感应并不是难点,我的自定义视图里,已经有左右移动的接口,加上重力感应,是分分钟的事情。但是最重要的原因,还是我太懒了!O(∩_∩)O哈哈~
        开始说说这个游戏的编写过程。这个游戏,我们先从感官上分析,需要绘制的内容:1.小人;2.逐渐上升的障碍物;3.得分版;4.菜单。然后,我们需要想到,我需要一个视图,来绘制这些内容,但是,我还要不同的类,来负责绘制不同的内容,比如,小人的类负责绘制小人,得分版负责绘制得分版,自定义视图,主要控制游戏逻辑。这样,我们拆开,一步一步实现。
        我的实现过程是这样的,我先谢了游戏自定义视图的逻辑,然后,写了障碍物绘制的类,我先成功绘制了会上升的障碍物,然后开始写绘制小人的类, 然后,让小人可以自由下落;接着,我又写了碰撞检测,让小球跟随障碍物上升;最后再调试成功。这是我的实现思路。
下面,按照我的风格,让大家从感官上了解主要代码结构:
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;//人物所站立的障碍在画面中的indexprivate 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<>();//将文字大小转化成DPmTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mTextSize, getResources().getDisplayMetrics());//启动游戏isRunning = true;startGame();}@Overrideprotected 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);}@Overrideprotected 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() {@Overridepublic 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 {@Overridepublic 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为truecheckIsOutSide(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;}@Overridepublic 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();}
}

代码中的注释,都是我后来加上的,我自己写的时候,是大多没有注释,但是为了大家看起来方便,我都会写上详细的注释。

       这里主要重写了onDraw()方法,内容都是通过canvas绘制的。然后注意的是,获取视图的宽和高是在onSizeChanged()方法中进行的,因为这个方法是在onMeasure()后回调的,我们能够通过代码中的方式获取视图的宽和高。然后就初始化一些跟视图宽高有关的变量。
       在看源码时,我们从init()方法开始看,init()方法中调用了startGame()方法:
    public void startGame() {mThread = new Thread() {@Overridepublic 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();}

这个方法,相当于源动力,不断循环,不断修改相关绘制参数,不断重绘界面;然后引起了onDraw()的方法的回调,然后继续看onDraw()中的代码,就是一些绘制的方法,绘制各种需要绘制的内容,这样,我们就完成了视图的绘制,类似一个游戏的控件就写完了。 其中一些负责绘制的类,都没有贴出来,但是他们的作用大同小异,基本都是通过参数,绘制相应的内容,这里就不多讲,看了源码很容易理解。

        后面,因为我不打算写重力传感器的代码,所以我放了两个按钮在xml布局中,为了能够更好的控制,写了一个按住连续点击的效果。
package com.mjc.mendown.util;import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;/*** Created by mjc on 2016/3/3.* 功能:使用这个类替代OnTouchListener,能够获得连续点击的效果*/
public abstract class OnContinueClickListener implements View.OnTouchListener {private boolean isContinue;private Thread mThread;//单例模式,只创建一个Handlerprivate volatile MyHandler mHandler;//不同事件,传入不同的what值,因为不同当前对象中,都只有一个实例private int what;public final static int interval = 20;private View view;public OnContinueClickListener() {//必须在主线程中调用if (mThread == null)mHandler = new MyHandler();}@Overridepublic boolean onTouch(View v, MotionEvent event) {this.view = v;switch (event.getAction()) {case MotionEvent.ACTION_DOWN:isContinue = true;mThread = new Thread() {@Overridepublic void run() {super.run();while (isContinue) {mHandler.sendEmptyMessage(what);Log.v("@msg-what", what + "");try {Thread.sleep(interval);} catch (InterruptedException e) {e.printStackTrace();}}}};mThread.start();break;case MotionEvent.ACTION_UP:isContinue = false;mThread = null;break;case MotionEvent.ACTION_CANCEL:isContinue = false;mThread = null;break;}return true;}private class MyHandler extends Handler {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);handleClickEvent(view);}}public abstract void handleClickEvent(View view);}

这个类写的是有瑕疵的,但是,使用起来非常方便,只需要在setOnTouchListener中,使用这个类替代元代的ontouchListener,如:

        left.setOnTouchListener(new OnContinueClickListener() {@Overridepublic void handleClickEvent(View view) {mGameLayout.moveLeft();}});right.setOnTouchListener(new OnContinueClickListener() {@Overridepublic void handleClickEvent(View view) {mGameLayout.moveRight();}});

我不太习惯在外面一个个讲,我喜欢在代码中,进行注释,我知道,仍然会有人不理解,不过,大家可以给我留言,我会尽量给大家解答的。个人思维,难免产生问题,请大家不吝指教。最后附上源码,CSDN不知道啥情况,上传的资源无法访问,500的错误,今天3.8妇女节,情有可原!

附:源码

是男人就下100层(简仿)相关推荐

  1. 是男人就下100层【第一层】——高仿微信界面(4)

    上一篇<是男人就下100层[第一层]--高仿微信界面(3)>中我们完成了登录,这一篇看完成登录后的一个短暂加载和引导界面. 加载界面: <RelativeLayout xmlns:a ...

  2. 是男人就下100层【第一层】——高仿微信界面(8)

    上一篇<是男人就下100层[第一层]--高仿微信界面(7)>中我们实现了下弹式菜单,这一篇我们来看看如何实现微信中的摇一摇功能. 首先我们来布局我们的摇一摇界面 布局文件如下: <? ...

  3. 是男人就下100层【第一层】——高仿微信界面(5)

    前面< 是男人就下100层[第一层]--高仿微信界面(4)>中我们已经完成了基本的引导界面和登录界面,这一篇中我们来看看登录后的主界面的布局和内容,来一步一步的完成该界面. 我们先来看看主 ...

  4. 是男人就下100层【第一层】——高仿微信界面(2)

    接着上一篇<是男人就下100层[第一层]--高仿微信界面(1)>,本打算实现上一篇文章中的第二个界面,这一篇先来实现一下登陆界面吧,接下来我们来开始登录界面的制作. 界面布局文件: < ...

  5. 是男人就下100层【第一层】——高仿微信界面(1)

    从今天开始将进行一个特别有趣且有意义的专栏<是男人就下100层>,计划对市面上比较火的应用进行高度仿照,并将开发过程贴出来,和大家交流和分享.由于时间关系可能进度会比较缓慢,但是任何事情如 ...

  6. 是男人就下100层【第一层】——高仿微信界面(3)

    上一篇<是男人就下100层[第一层]--高仿微信界面(2)>中实现了注册登录界面,这一篇来看看具体的登录界面实现,先来看看界面效果. 登录界面布局 <?xml version=&qu ...

  7. 是男人就下100层【第一层】——高仿微信界面(7)

    在上一篇<是男人就下100层[第一层]--高仿微信界面(6)>中我们已经对主界面的的各个菜单进行了简单实现,接下来我们完成两个比较有趣的功能,一个是上部的下弹式菜单,另一个是摇一摇功能. ...

  8. 是男人就下100层【第五层】——2048游戏从源代码到公布市场

    上一篇<是男人就下100层[第五层]--换肤版2048游戏>中阳光小强对2048游戏用自己的方式进行了实现,并分享了核心源码,这一篇阳光小强打算将该项目的全部源码公开并结合这个实例在这篇文 ...

  9. 是男人就下100层【第四层】——Crazy贪吃蛇(2)

    在上一篇<是男人就下100层[第四层]--Crazy贪吃蛇(1)>中我们让贪吃蛇移动了起来,接下来我们来实现让贪吃蛇能够绕着手机屏幕边线移动而且能够改变方向 一.加入状态并改动代码 首先我 ...

最新文章

  1. C++ Primer 5th笔记(chap 13 拷贝控制)合成的移动操作
  2. centos关闭php服务,linux(centos)防火墙的开启与关闭的方法
  3. AspectJ的实现机制
  4. 对datatable操作经验-排序和分页
  5. 使用border-collapse:collapse;属性新建一个细线表格
  6. 项目测试基础:白盒测试相关知识笔记
  7. [方法“Boolean Contains(System.Guid)”不支持转换为 SQL]的解决办法
  8. Python中关于文件路径的简单操作 [转]
  9. Swift中的闭包例子
  10. 工作320:uni-预加载问题
  11. 程序员的进阶课-架构师之路(10)-霍夫曼树
  12. oracle rac 清理log,Oacle rac架构监听日志清理
  13. opencv cv2.copyMakeBorder()函数详解
  14. stm32F103 模拟I2C mpu6050收到数据全为0,或者地址为209,104,0x68,0xD0的一些解决办法总结
  15. 文本匹配-bimpm
  16. 手机浏览器显示word文档
  17. 【邱锡鹏-神经网络与深度学习】第一章绪论 知识点汇总
  18. android手机下开发摄像头拍摄
  19. 【Bluetooth|蓝牙开发】二、蓝牙开发入门
  20. word里面Ctrl+V不能粘贴解决方法

热门文章

  1. RK3568上运行鸿蒙3.1Release
  2. Telnet远程登录
  3. 全球最干净航空公司排名:中日韩包揽前三,前10名中国有3家
  4. usb 转串口 /ttyUSB0 无法识别或者 没有数据返回 pl2303芯片
  5. 诗词情话生成器app,诗词情话生成软件推荐!​
  6. 医疗器械行业税收筹划研究
  7. 中国封闭式药物转移系统市场现状研究分析与发展前景预测报告
  8. 二本学渣考研失败,看懂这些帮你轻松解决就业问题!看完直接跪服
  9. 在云端的输入法:搜狗云引发下一代输入法革命
  10. python中pca算法_使用python的numpy库实现PCA算法