镇楼图
先看为敬
介绍
IG拿了冠军,大家心中的喜悦就不用多说了,然鹅还木有结束,一组IG战队的王老板吃热狗的照片,也火爆了全网,上了热搜,网上看到好多拿王老板开涮的,其中就看到了一个用户发布的Java版王老板飞机大作,于是乎,闲来没事,我也写了一个android版的,不多说,接下来看代码。
首先,先创建一个所有物体的基类:Floating
public class Floating {
/**
* 图片 也就是物体的样子
*/
private Bitmap mBitmap;
/**
* w 宽 h高
*/
private int w, h;
/**
* 所占据的矩形区域
*/
private Rect mRect;
/**
* 中心点位置
*/
private Point mPoint;
/**
* 物体的唯一ID
*/
private long id;
/**
* tag 备用
*/
private Object tag;
public Floating(Context context, @DrawableRes int img, int w, int h) {
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), img);
this.mBitmap = XTools.BitmapUtil().zoomBmp(bitmap, w, h);
this.w = w;
this.h = h;
mRect = new Rect();
mPoint = new Point();
id = System.currentTimeMillis();
}
public Bitmap getBitmap() {
return mBitmap;
}
public int getW() {
return w;
}
public int getH() {
return h;
}
/**
* 相对于之前的中心点做偏移
*
* @param dx dx
* @param dy dy
*/
public void addCenterPosition(int dx, int dy) {
setCenterPosition(mPoint.x + dx, mPoint.y + dy);
}
/**
* 设置中心点
*
* @param x x
* @param y y
*/
public void setCenterPosition(int x, int y) {
mPoint.x = x;
mPoint.y = y;
int offsetW = w / 2;
int offsetH = h / 2;
//设置完中心点后,重新设置所在区域的矩形
mRect.set(x - offsetW, y - offsetH, x + offsetW, y + offsetH);
}
public void setBitmap(Bitmap bitmap) {
this.mBitmap = bitmap;
}
public Object getTag() {
return tag;
}
public void setTag(Object tag) {
this.tag = tag;
}
public Point getCenterPosition() {
return mPoint;
}
public Rect getRect() {
return mRect;
}
/**
* h画到屏幕上
*
* @param canvas canvas
*/
public void draw(Canvas canvas) {
canvas.drawBitmap(getBitmap(), null, getRect(), null);
}
@Override
public int hashCode() {
return String.valueOf(id).hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof Floating && obj.hashCode() == this.hashCode();
}
}
接下来创建一个自己的飞机:MyPlane
public class MyPlane extends Floating {
private int w;
private int h;
public MyPlane(Context context, int w, int h) {
super(context, R.drawable.bullet, w, h);
this.w = w;
this.h = h;
}
@Override
public int getW() {
return w;
}
@Override
public int getH() {
return h;
}
}
接下来是敌机类:FoePlane
public class FoePlane extends Floating {
/**
* 是否被子弹销毁
*/
private boolean isDestroy;
/**
* 销毁时时间
*/
private long destroyTime;
/**
* 销毁后在屏幕中停留的时间
*/
private static final int DESTROY_TIME = 1000;
private Context mContext;
public FoePlane(Context context, int size) {
super(context, R.drawable.fb_n, size, size);
mContext = context;
}
public boolean isDestroy() {
return isDestroy;
}
/**
* @param destroy 是否销毁
*/
public void setDestroy(boolean destroy) {
//如果之前没有被销毁,并且现在设置成销毁
if (destroy && !isDestroy) {
destroyTime = getTime();
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.fb);
//设置销毁后的图片,即王老板吃到了热狗
setBitmap(XTools.BitmapUtil().zoomBmp(bitmap, getW()));
}
isDestroy = destroy;
}
/**
* 判断是否销毁并可以移除屏幕
*
* @return 是否销毁并可以移除屏幕
*/
public boolean canDestroyAndRemove() {
return isDestroy() && getTime() - destroyTime >= DESTROY_TIME;
}
private long getTime() {
return System.currentTimeMillis();
}
}
最后是子弹:Bullet
public class Bullet extends Floating {
public Bullet(Context context, int w, int h) {
super(context, R.drawable.bullet, w, h);
}
}
搞定,最关键的一步,用SurfaceView绘制:
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
/**
* 线程池
*/
private ScheduledExecutorService mThread;
/**
* 我方飞机(大热狗)
*/
private MyPlane myPlane;
/**
* GameView的宽高
*/
private int w, h;
private Canvas mCanvas;
/**
* 出现在屏幕上的敌机(没有热狗的王老板)最大数量
*/
private static final int MAX_FOE_COUNT = 10;
/**
* 屏幕刷新间隔 毫秒
*/
private static final int REFRESH_TIME = 10;
/**
* 敌机(没有热狗的王老板)刷新间隔 毫秒
*/
private static final int FOE_REFRESH_TIME = 100;
/**
* 子弹(热狗)大小:宽度 这里默认长度是宽度的3倍
*/
private static final int BULLET_SIZE = 35;
/**
* 子弹(热狗)速度,每刷新一次屏幕,子弹移动的像素数量
*/
private static final int BULLET_SPEED = 10;
/**
* 发射子弹(热狗)的时间间隔
*/
private static final int BULLET_ADD_SPEED = 500;
/**
* 敌机(没有热狗的王老板)速度,每刷新一次屏幕,敌机移动的像素数量
*/
private static final int FOE_PLANE_SPEED = 3;
/**
* 敌机(没有热狗的王老板)集合,只有在集合里的敌机才会显示出来
*/
private List<FoePlane> mFoeList;
/**
* 子弹(热狗)集合,只有在集合里的子弹才会显示出来
*/
private List<Bullet> mBulletList;
/**
* 敌机(没有热狗的王老板)大小
*/
private int mFoeSize;
/**
* 是否游戏结束
*/
private boolean gameOver;
/**
* 随机帮助类
*/
private Random r;
public GameView(@NonNull Context context) {
this(context, null);
}
public GameView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public GameView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
r = new Random();
// 设置可获得焦点
setFocusable(true);
setFocusableInTouchMode(true);
// 设置常亮
this.setKeepScreenOn(true);
// 设置画布 背景透明
setZOrderOnTop(true);
mHolder = getHolder();
mHolder.setFormat(PixelFormat.TRANSLUCENT);
mHolder.addCallback(this);
mFoeList = new ArrayList<>();
mBulletList = new ArrayList<>();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.w = w;
this.h = h;
int ph = w / 5;
int pw = ph / 3;
//根据窗口大小设置出自己的飞机大小
myPlane = new MyPlane(getContext(), pw, ph);
//根据窗口大小设置出敌机大小
mFoeSize = w / 6;
}
private int x, y;
/**
* 是否点击在自己的飞机上
*/
private boolean touchOnPlant;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
y = (int) event.getRawY();
//如果点击的点在我们的飞机内
if (myPlane.getRect().contains(x, y)) {
//可以移动飞机
touchOnPlant = true;
}
break;
case MotionEvent.ACTION_MOVE:
//如果可以移动飞机
if (touchOnPlant) {
int dx = (int) (event.getRawX() - x);
int dy = (int) (event.getRawY() - y);
//设置飞机的偏移量
myPlane.addCenterPosition(dx, dy);
x = (int) event.getRawX();
y = (int) event.getRawY();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//不可以移动飞机
touchOnPlant = false;
break;
default:
break;
}
return true;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mThread == null) {
//手动创建线程池,进行绘制
mThread = new ScheduledThreadPoolExecutor(1, (ThreadFactory) Thread::new);
mThread.scheduleWithFixedDelay(GameView.this::draw, 0, REFRESH_TIME, TimeUnit.MILLISECONDS);
mThread.scheduleWithFixedDelay(GameView.this::addFoe, 0, FOE_REFRESH_TIME, TimeUnit.MILLISECONDS);
mThread.scheduleWithFixedDelay(GameView.this::addBullet, 0, BULLET_ADD_SPEED, TimeUnit.MILLISECONDS);
//初始化游戏
again();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//页面销毁,停止并回收线程池
mThread.shutdown();
mThread = null;
}
/**
* 添加敌机
*/
private void addFoe() {
//如果集合数量小于设置的最大敌机量
if (mFoeList.size() < MAX_FOE_COUNT) {
FoePlane foePlane = new FoePlane(getContext(), mFoeSize);
//随机敌机出现的x点坐标
int pos = r.nextInt(w);
int min = mFoeSize;
int max = w - mFoeSize;
//为了防止敌机只在屏幕中出现一半,所以做了下面两个判断
if (pos < min) {
pos = min;
}
if (pos > max) {
pos = max;
}
//设置敌机的出现坐标
foePlane.setCenterPosition(pos, 0);
mFoeList.add(foePlane);
}
}
/**
* 添加子弹
*/
private void addBullet() {
Bullet bullet = new Bullet(getContext(), BULLET_SIZE, BULLET_SIZE * 3);
Rect rect = myPlane.getRect();
//设置子弹出现的坐标 从飞机的中间上方出现
bullet.setCenterPosition(rect.centerX(), rect.top);
mBulletList.add(bullet);
}
/**
* 最重要的画
*/
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
if (mCanvas != null) {
if (!gameOver) {
//画背景
drawBg();
//画我的飞机
drawMyPlane();
//画敌机
drawFoePlane();
//画子弹
drawBullet();
//判断是否子弹击中敌机
checkHit();
//判断是否游戏结束
checkGameOver();
} else {
drawGameOver();
}
}
} catch (Exception ignored) {
} finally {
if (mCanvas != null) {
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
/**
* 画背景
*/
private void drawBg() {
mCanvas.drawColor(Color.WHITE);
}
/**
* 画我的飞机
*/
private void drawMyPlane() {
myPlane.draw(mCanvas);
}
/**
* 画敌机
*/
private void drawFoePlane() {
if (mFoeList != null) {
Iterator<FoePlane> iterator = mFoeList.iterator();
//遍历数组,这里之所以不用for循环,是因为遍历数组的时候不能remove元素,否则会抛异常
while (iterator.hasNext()) {
FoePlane foePlane = iterator.next();
//画飞机
foePlane.draw(mCanvas);
//如果飞机没有被子弹打中销毁
if (!foePlane.isDestroy()) {
//让飞机向下飞一段距离
foePlane.addCenterPosition(0, FOE_PLANE_SPEED);
}
//如果飞机的上边大于窗口下方,则飞机飞出了屏幕,那么移除就好了
if (foePlane.getRect().top > h) {
iterator.remove();
}
}
}
}
/**
* 画子弹
*/
private void drawBullet() {
if (mBulletList != null) {
Iterator<Bullet> iterator = mBulletList.iterator();
//遍历数组,这里之所以不用for循环,是因为遍历数组的时候不能remove元素,否则会抛异常
while (iterator.hasNext()) {
Bullet bullet = iterator.next();
//画子弹
bullet.draw(mCanvas);
//让子弹向下飞一段距离
bullet.addCenterPosition(0, -BULLET_SPEED);
//如果子弹的下边大于窗口上方,则子弹飞出了屏幕,那么移除就好了
if (bullet.getRect().bottom < 0) {
iterator.remove();
}
}
}
}
/**
* 判断是否子弹击中敌机
*/
private void checkHit() {
Iterator<FoePlane> foeIterator = mFoeList.iterator();
//遍历数组,这里之所以不用for循环,是因为遍历数组的时候不能remove元素,否则会抛异常
//遍历敌机
while (foeIterator.hasNext()) {
FoePlane foePlane = foeIterator.next();
//获取敌机所在位置的矩形
Rect foePlaneRect = foePlane.getRect();
//如果敌机没有被子弹击中销毁,则进行下方判断子弹是否击中敌机的逻辑
if (!foePlane.isDestroy()) {
Iterator<Bullet> bulletIterator = mBulletList.iterator();
//遍历子弹
while (bulletIterator.hasNext()) {
Bullet bullet = bulletIterator.next();
//获取子弹所在位置的矩形
Rect bulletRect = bullet.getRect();
//判断子弹和敌机所在的矩形是否发生碰撞
if (RectUtil.overlap(foePlaneRect, bulletRect)) {
//如果子弹击中了敌机,设置敌机为销毁状态
foePlane.setDestroy(true);
//在屏幕中移除击中敌机的子弹
bulletIterator.remove();
}
}
}
//判断敌机是否销毁并可以移除屏幕
if (foePlane.canDestroyAndRemove()) {
foeIterator.remove();
}
}
}
/**
* 游戏结束后的画面
*/
private void drawGameOver() {
mCanvas.drawColor(Color.BLACK);
}
/**
* 判断是否游戏结束
*/
private void checkGameOver() {
//遍历敌机
for (Floating p : mFoeList) {
//如果我方飞机和敌机所在矩形发生碰撞
if (RectUtil.overlap(myPlane.getRect(), p.getRect())) {
//设置游戏结束
gameOver = true;
if (mOnGameListener != null) {
//在主线程回调
XTools.UiUtil().runOnUIThread(() -> mOnGameListener.onGameOver());
}
break;
}
}
}
/**
* 再来一次
*/
public void again() {
mFoeList.clear();
//设置初始位置
myPlane.setCenterPosition(w / 2, h - myPlane.getH());
gameOver = false;
if (mOnGameListener != null) {
//在主线程回调
XTools.UiUtil().runOnUIThread(() -> mOnGameListener.onStart());
}
}
private OnGameListener mOnGameListener;
public interface OnGameListener {
/**
* 游戏开始
*/
void onStart();
/**
* 游戏失败
*/
void onGameOver();
}
public void setOnGameListener(OnGameListener onGameListener) {
mOnGameListener = onGameListener;
}
}
其中多次用到判断矩形碰撞的功能:
public class RectUtil {
/**
* 判断两个矩形是否碰撞
*
* @param r1 矩形1
* @param r2 矩形2
* @return 是否碰撞
*/
public static boolean overlap(Rect r1, Rect r2) {
//左
if (r1.right < r2.left) {
return false;
}
//右
if (r1.left > r2.right) {
return false;
}
//上
if (r1.bottom < r2.top) {
return false;
}
//下
if (r1.top > r2.bottom) {
return false;
}
return true;
}
}
完成~~~~~~~~~~
完整源码请看:
https://github.com/610968110/Plane