先看效果图:

这是一个比较简易的射击小游戏,后期可以将圆球,炮筒用其它图片来替换,应该可以变得好看一些。我实现这个效果,主要是为了学习和巩固自定义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"?>        

 

更多相关文章

  1. android小游戏 飞机躲子弹
  2. Android(安卓)四种启动方式个人理解简洁表达
  3. android 游戏 实战打飞机游戏 子弹生成与碰撞 以及爆炸效果(5)
  4. Android(安卓)射击类游戏 (一)
  5. Google本想借刀杀人,却用Android武装了敌人
  6. 基于WiEngine游戏引擎--战斗场景之技能
  7. android 游戏 实战打飞机游戏 BOSS的设计 以及胜利失败页面
  8. Q版疯狂大炮游戏android源码下载
  9. Android植物大战僵尸小游戏

随机推荐

  1. Android 5.0低版本出现Error inflating c
  2. Android版本更新(Service下载 Notificatio
  3. Android多服务器同时打包多个apk
  4. Android 改变屏幕亮度时需添加权限
  5. Android API 中文 (15) —— GridView
  6. 自定义Cordova插件、Ionic插件开发
  7. android编译sdk
  8. android的autocompletetextview适配器 刷
  9. 手动下载SDK tools,Build Tools,Platforms
  10. Android 中 onSaveInstanceState()使用方