Android(安卓)自定义View实现炮弹射击小游戏
先看效果图:
这是一个比较简易的射击小游戏,后期可以将圆球,炮筒用其它图片来替换,应该可以变得好看一些。我实现这个效果,主要是为了学习和巩固自定义View的一些知识点。下面我来讲述一下本游戏的设计思路
从图上我们可以看到,我们需要一个炮筒,炮筒里可以发出许多的子弹,然后天上有很多的敌人,我们需要用子弹去碰撞到敌人,从而达到消灭敌人的效果。所有我们首先就需要有炮筒,子弹,敌人这三个类
大炮类:
public class Artillery { private Matrix matrix; //大炮的变换矩阵 private Paint paint; //大炮的画笔 private Bitmap bitmap; //大炮的图片 private int centerX,centerY; //大炮中心点 public Artillery(Matrix matrix, Paint paint, Bitmap bitmap) { this.matrix = matrix; this.paint = paint; this.bitmap = bitmap; } public Matrix getMatrix() { return matrix; } public void setMatrix(Matrix matrix) { this.matrix = matrix; } public Paint getPaint() { return paint; } public void setPaint(Paint paint) { this.paint = paint; } public Bitmap getBitmap() { return bitmap; } public void setBitmap(Bitmap bitmap) { this.bitmap = bitmap; } public int getCenterX() { return centerX; } public void setCenterX(int centerX) { this.centerX = centerX; } public int getCenterY() { return centerY; } public void setCenterY(int centerY) { this.centerY = centerY; } public void setCenter(int centerX, int centerY){ this.centerX=centerX; this.centerY=centerY; }}
因为子弹是从大炮的中心发出的,所以在大炮类中我们需要记录大炮的中心点(centerX,centerY)
子弹类:
public class Bullet { private Paint paint; //子弹的画笔 private int radius; //子弹的半径 private float moveStep; //每次移动的步长 public Bullet(Paint paint,int radius,int moveStep){ this.paint=paint; this.radius=radius; this.moveStep=moveStep; } public Paint getPaint() { return paint; } public void setPaint(Paint paint) { this.paint = paint; } public int getRadius() { return radius; } public void setRadius(int radius) { this.radius = radius; } public float getMoveStep() { return moveStep; } public void setMoveStep(float moveStep) { this.moveStep = moveStep; }}
敌人类:
public class Enemy { private Paint paint; //敌人的画笔 private float moveStep; //敌人每次移动的步长 private int radius; //敌人的半径(圆) public Enemy(Paint paint, int radius, float moveStep) { this.paint = paint; this.moveStep = moveStep; this.radius=radius; } public Paint getPaint() { return paint; } public void setPaint(Paint paint) { this.paint = paint; } public float getMoveStep() { return moveStep; } public void setMoveStep(float moveStep) { this.moveStep = moveStep; } public int getRadius() { return radius; } public void setRadius(int radius) { this.radius = radius; }}
由图上可以发现,大炮会随着我们的点击发生旋转,这里我们需要计算一下,假设我们点击的坐标为P0:(x0,y0),大炮的中心点为P1:(x1,y1),那么P0,P1连成的直线与水平轴会产生一个夹角θ。这个θ就是我们想要大炮旋转的角度
直线P0P1的斜率:tanθ=(y1-y0)/(x0-x1)
注意在android里面,是以屏幕左上方为坐标原点,且以屏幕下方为y轴正方向,越往屏幕下方y值越大。而我们这里是以大炮中心点为原点建立的坐标系来进行计算的。所以是y1-y0
根据斜率公式,就可以得到θ=arctan((y1-y0)/(x0-x1))
相应的在代码中的写法如下:
Math.toDegrees(Math.atan((arti.getCenterY()-event.getY())/(event.getX()-arti.getCenterX())));
其中arti是大炮对象,P0为(arti.getCenterX(),arti.getCenterY()),P1为(event.getX(),event.getY())
atan()是反三角计算方法,返回值为弧度
toDegrees()是将弧度转化为角度
我们大炮的旋转问题解决了,然后我们需要去实现点击某一位置时,生成一颗子弹,并让子弹打向那个位置的功能
1、点击监听的方法是onTouchEvent,我们重写这个方法以后就可以获取点击时的坐标
2、由于我可以不断的点击,屏幕里会出现非常多的子弹,每个子弹应该有他自己的位置和运动方向。所以,我们要创建一个新的类MyPoint,来记录每个点的位置和运动方向(之后的敌人位置也是使用这个类)
MyPoint类:
public class MyPoint { private int x; private int y; private double angle; //角度 public MyPoint(int x, int y, double angle) { this.x = x; this.y = y; this.angle = angle; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public double getAngle() { return angle; } public void setAngle(double angle) { this.angle = angle; } public void set(int x,int y){ this.x=x; this.y=y; } //点移动的方法 /* * moveStep:步长 * boundWidth:点所有在区域的宽度 * boundHeight:点所在区域的高度 * */ public void move(float moveStep,boolean isEnemy){ double moveY=0,moveX=0; if(getAngle()>=0){ //子弹在右上方 moveX=moveStep*Math.cos(getAngle()); moveY=moveStep*Math.sin(getAngle()); } else{ //子弹在左上方(角度为负数) moveX=-moveStep*Math.cos(-getAngle()); moveY=moveStep*Math.sin(-getAngle()); } if(!isEnemy)set((int)(getX()+moveX),(int)(getY()-moveY)); else set((int)(getX()+moveX),(int)(getY()+moveY)); } //是否离开该区域 public boolean isOutOfBounds(int boundWidth,int boundHeight){ if(getX()>boundWidth || getX()<0 )return true; else if(getY()>boundHeight || getY()<0)return true; else return false; } //是否离开该区域,忽略顶部 public boolean isOutOfBoundsWithOutTop(int boundWidth,int boundHeight){ if(getX()<0 || getX()>boundWidth)return true; else if(getY()>boundHeight)return true; else return false; } //是否发生碰撞 public boolean isCollider(MyPoint point,int bulletRadius,int enemyRadius){ //两个圆的圆心的距离小于两个圆的半径之和时,说明两个圆发生碰撞 return (getX()-point.getX())*(getX()-point.getX())+(getY()-point.getY())*(getY()-point.getY()) <= (bulletRadius+enemyRadius)*(bulletRadius+enemyRadius); }}
这里用角度angle来代替运动方向
所有子弹的初始位置都是大炮的中心点(arti.getCenterX(),arti.getCenterY())
我们用一个List集合(bulletPoints)来存放所有的点。
//将点击的位置放入点的集合中bulletPoints.add(new MyPoint(arti.getCenterX(),arti.getCenterY(),Math.toRadians(currentRotate)));
3、我们在onDraw()绘制方法里循环遍历bulletPoints集合,画出每一颗子弹,并修改下一次运动到的位置。
for(int i=0;i
//点移动的方法/** moveStep:步长* isEnemy:是否为敌人的位置* */public void move(float moveStep,boolean isEnemy){ double moveY=0,moveX=0; if(getAngle()>=0){ //子弹在右上方 moveX=moveStep*Math.cos(getAngle()); moveY=moveStep*Math.sin(getAngle()); } else{ //子弹在左上方(角度为负数) moveX=-moveStep*Math.cos(-getAngle()); moveY=moveStep*Math.sin(-getAngle()); } if(!isEnemy)set((int)(x+moveX),(int)(y-moveY)); else set((int)(x+moveX),(int)(y+moveY));}public void set(int x,int y){ this.x=x; this.y=y;}
我方的子弹已经生成完毕,最后,让我们生成敌人。
1、我们先写一个生成敌人的方法,并让它每隔一段时间被调用一次:
private Runnable spawnRunnable=new Runnable() { @Override public void run() { instantiateEnemy(); }};//生成敌人private void instantiateEnemy(){ //将敌人位置保存到集合中 enemyPoints.add(new MyPoint(positionRand.nextInt(Constants.SCREEN_WIDTH),-5,Math.toRadians(-90))); isSpawning=false;}@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); //...... //如果敌人未到达最大数量,并且不在生成敌人,则继续生成敌人 if(enemyPoints.size()
2、敌人移动的方法(与子弹的方法一样):
for(int i=0;i
3、敌人与子弹碰撞的方法:
@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); //...... //是否发生碰撞 for(int j=0;j
最后,呈上完整自定义View源码:
import android.content.Context;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Matrix;import android.graphics.Paint;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import com.hualinfo.bean.circlecollider.Artillery;import com.hualinfo.bean.circlecollider.Bullet;import com.hualinfo.bean.circlecollider.MyPoint;import com.hualinfo.bean.circlecollider.Enemy;import java.util.ArrayList;import java.util.List;import java.util.Random;import androidx.annotation.Nullable;public class MyCircleColliderView extends View { private Bullet bullet; //子弹对象 private Artillery arti; //大炮对象 private Enemy enemy; //敌人对象 private int maxEnemyNum=30; //敌人的最大数量 private boolean isSpawning=false; //正在生成敌人 private float currentRotate=-90; //当前大炮的旋转方向 private List bulletPoints=new ArrayList<>(); //每一个子弹的坐标点 private List enemyPoints=new ArrayList<>(); //每一个敌人的坐标点 private Random positionRand=new Random(); private BeatEnemyListener beatEnemyListener; public MyCircleColliderView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init(){ arti=new Artillery(new Matrix(),new Paint(),BitmapFactory.decodeResource(getResources(),R.mipmap.arti)); arti.setCenter(Constants.SCREEN_WIDTH/2,Constants.SCREEN_HEIGHT-arti.getBitmap().getHeight()/2); Paint bulletPaint=new Paint(); bulletPaint.setColor(Color.RED); bullet=new Bullet(bulletPaint,25,20); Paint enemyPaint=new Paint(); enemyPaint.setColor(Color.BLUE); enemy=new Enemy(enemyPaint,25,2); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); arti.getMatrix().reset(); arti.getMatrix().postTranslate(arti.getCenterX()-arti.getBitmap().getWidth()/2,arti.getCenterY()-arti.getBitmap().getHeight()/2); arti.getMatrix().postRotate(-currentRotate,arti.getCenterX(),arti.getCenterY()); canvas.drawBitmap(arti.getBitmap(),arti.getMatrix(),arti.getPaint()); for(int i=0;i
Activity类:
public class MyCircleColliderAct extends AppCompatActivity { private MyCircleColliderView colliderView; private TextView tv_score; private int score=0; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.act_circle_collider); init(); } private void init(){ tv_score=findViewById(R.id.tv_score); colliderView=findViewById(R.id.collider_view); colliderView.setBeatEnemyListener(new MyCircleColliderView.BeatEnemyListener() { @Override public void onBeatEnemy() { tv_score.setText("干掉了"+(++score)+"个敌人"); } }); }}
xml布局文件:
<?xml version="1.0" encoding="utf-8"?>
更多相关文章
- android小游戏 飞机躲子弹
- Android(安卓)四种启动方式个人理解简洁表达
- android 游戏 实战打飞机游戏 子弹生成与碰撞 以及爆炸效果(5)
- Android(安卓)射击类游戏 (一)
- Google本想借刀杀人,却用Android武装了敌人
- 基于WiEngine游戏引擎--战斗场景之技能
- android 游戏 实战打飞机游戏 BOSS的设计 以及胜利失败页面
- Q版疯狂大炮游戏android源码下载
- Android植物大战僵尸小游戏