【安卓app开发二】小游戏开发之--飞机避障(多线程动态刷新、自定义View制作与监听)

目录

前言

概览

实现过程

 1、基类(BodyObject)与飞机(Plane)、子弹(Bullet)类

 2、工具类(ScreenUtil)

 3、自定义View类(PlaneView)

后续方向


前言

        续上文【安卓app开发一】后,决定app实现小游戏开发,本期项目是实现飞机避障,即保证移动飞机过程中不会被子弹碰撞到,主要涉及到的问题知识点有

        1、建立英雄的基类方便后续代码的扩展

        2、多线程实现动态刷新,界面不断重绘实现子弹的运动

        3、自定义View的制作方法与监听器的使用流程

        4、飞机与子弹之间的碰撞检测

本期安卓app源码与安装包

链接:

https://pan.baidu.com/s/1h15-23kKTjHrBo7HRR-CuQ?pwd=05oh
提取码:

05oh


概览

        本期主要新增了一个飞机避障游戏界面,同时上期意见反馈界面仍然保留,可以继续提交使用者的想法(想玩的游戏或者当前游戏的意见反馈等等),更新后的界面如下图所示

         前4个后面与上期相同,飞机避障运行小游戏主要如下所示

         下边讲述一下实现过程!


实现过程

        实现流程如下图所示,具体实现方法见下方讲解代码

         主要添加了下方文件

 1、基类(BodyObject)与飞机(Plane)、子弹(Bullet)类

        基类主要是作为一个英雄的模板,英雄类直接继承于基类,并且在创建英雄类的时候其实是调用基类的构造方法,能提高代码的复用性在本期中,主要为英雄基类添加了移动步长(移动速度)、位置、尺寸以及图片。

  • 基类
package com.example.application.planegame.body;

import android.graphics.Bitmap;
import android.graphics.Paint;

//物体基类
public class BodyObject {
    private Paint paint;//物体的画笔
    private Bitmap bitmap;//物体的图片
    private double movestep;//物体的步长
    private double x,y;//物体的位置
    private int width,height;//物体的尺寸

    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 double getMovestep() {
        return movestep;
    }

    public void setMovestep(double movestep) {
        this.movestep = movestep;
    }

    public double getX() {
        return x;
    }

    public void setX(double x) {
        this.x = x;
    }

    public double getY() {
        return y;
    }

    public void setY(double y) {
        this.y = y;
    }

    public int getWidth() {
        return bitmap.getWidth();
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
       return bitmap.getHeight();
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public BodyObject(Paint paint, Bitmap bitmap,int x,int y) {
        this.paint = paint;
        this.bitmap = bitmap;
        this.x = x;
        this.y = y;
    }

    public BodyObject(Paint paint, double movestep,int x,int y) {
        this.paint = paint;
        this.movestep = movestep;
        this.x = x;
        this.y = y;
    }

    public BodyObject() {
    }


}

  •  飞机类
package com.example.application.planegame.body;

import android.graphics.Bitmap;
import android.graphics.Paint;

public class Plane extends BodyObject{

    //构造函数
    public Plane(Paint paint, Bitmap bitmap,int x,int y) {
        //调用父类的方法
        super(paint,bitmap,x,y);
    }


}
  •  子弹类
package com.example.application.planegame.body;

import android.content.Context;
import android.graphics.Paint;

import com.example.application.planegame.tool.ScreenUtil;

public class Bullet extends BodyObject{

    private double angle;//子弹的运动方向
    private double radius;//子弹的半径

    //构造函数
    public Bullet(Paint paint, double movestep,double radius,int x,int y) {
        super(paint, movestep,x,y);
        this.radius = radius;
        angle = Math.random()*Math.PI*2;
    }

    public double getAngle() {
        return angle;
    }

    public void setAngle(double angle) {
        this.angle = angle;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    //判断边界
    public void boundary(Bullet bullet, Context context){
        int[] screensize = ScreenUtil.getscreensize(context);
        if (bullet.getY() <= 0 || bullet.getY() >= screensize[0]){
            angle =- angle;
        }
        if (bullet.getX() <= 0 || bullet.getX() >= screensize[1]){
            angle = Math.PI - angle;
        }
    }
}
 2、工具类(ScreenUtil)

        由于要实现子弹触碰到屏幕边缘时实现反弹,利用该工具类实现获取手机屏幕尺寸大小的数据并存在在一个数组当中

package com.example.application.planegame.tool;

import android.content.Context;
import android.view.Display;
import android.view.WindowManager;

public class ScreenUtil {
    private static int screenwidth = 0;
    private static int screenheight = 0;

    //获取屏幕的尺寸大小
    public static int[] getscreensize(Context context){
        if (screenwidth == 0 || screenheight == 0){
            WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            Display display = manager.getDefaultDisplay();
            screenheight = display.getHeight();
            screenwidth = display.getWidth();
        }
        int[] screensize = {screenheight,screenwidth};
        return screensize;
    }
}
 3、自定义View类(PlaneView)

         该类主要是在屏幕上实现飞机与子弹的绘制,其实现流程主要是

        1、自定义PlaneView继承于View类并重写onDraw方法,同时在activity的PlaneGame文件中利用setConteneView(PlaneView),此时在PlaneGame相对应的布局文件中进行配置(即下方布局代码中的com.example.application.planegame.view.PlaneView),在此附上本期布局代码(activity_plane_game.xml)与PlaneGame代码

  • activity_plane_game.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:background="@mipmap/plane_background"
    android:layout_height="match_parent"
    tools:context=".activity.PlaneGame">

    <com.example.application.planegame.view.PlaneView
        android:id="@+id/PlaneView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></com.example.application.planegame.view.PlaneView>

    <TextView
        android:id="@+id/Text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="0dp"
        android:layout_marginTop="179dp"
        android:gravity="center"
        android:textColor="#CC3333"
        android:text="游戏结束,共坚持"
        android:textSize="30dp"></TextView>

</RelativeLayout>
  •  PlaneGame代码
package com.example.application.activity;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.example.application.R;
import com.example.application.planegame.view.PlaneView;

import cn.bmob.v3.Bmob;

public class PlaneGame extends AppCompatActivity {

    private TextView text;
    private PlaneView planeView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plane_game);
        Bmob.initialize(this,"ebb1d24c01600c55b1e8be7f2625d7a7");
        initView();
    }

    //初始化控件
    private void initView() {
        text = findViewById(R.id.Text);
        planeView = findViewById(R.id.PlaneView);
        text.setVisibility(View.INVISIBLE);

        //设置监听
        planeView.setBeatListener(new PlaneView.BeatListener() {
            @Override
            public void onBeat() {
                if (planeView.flag_strike){
                    text.setText("游戏结束,共玩了"+(planeView.etime - planeView.stime)/1000+"秒");
                    text.setVisibility(View.VISIBLE);
                }
            }
        });



    }
}

       ps:即在登陆界面点击登陆后进去PlaneGame所设计的游戏界面时,其代码执行逻辑为由于我们设定了setConteneView(PlaneView)与布局文件的配置,这时会内存中会构造一个PlaneView对象并自动调用PlaneView中重写的onDraw方法,我们在onDraw方法中利用canvas方法对飞机与子弹进行绘制;

        2、在PlaneView的构造方法中实现对英雄类的初始化工作,并且由于子弹类需要重绘实现动态更新,在初始化工作中要启动一个更新UI的线程来调用invalidate()方法实现重绘;

         3、接着就是实现onDraw的代码逻辑,利用canvas对飞机与子弹绘制,子弹利用一个List容器来存放,并重写invalidate()方法实现对子弹运动位置更新,方便在子线程中重绘时子弹的位置坐标得到更新,子弹坐标的计算公式为(angle为构造子弹对象时所赋值的随机数,movestep为步长),同时每重绘一次要判断子弹的位置坐标是否超出边缘,若超出要重新设定;飞机由于是使用者控制,故重写onTouchEvent方法即可

x += movestep*\cos (angle)

y += movestep*\sin (angle) 

         4、实现以上步骤后就实现动态界面,接下来进行监听机制的设定,只需要利用一个接口就可以完成,利用BeatListener接口中设置一个监听方法,并写一个设置监听器去设置其监听setBeatListener

         5、最后一步是实现碰撞检测,目前该实现步骤算法较为简单,飞机为一个矩形区域,子弹为一个圆形区域,碰撞即子弹的坐标与飞机区域的坐标有相等的,但此方法较为粗略,实现碰撞检测后要flag_strike标志位置为TRUE去通知子线程不必重绘了,并且利用监听器调用监听方法以实现在主线程中更新UI,出现“游戏结束,共玩了X秒”的字眼;

        以下便是自定义View类(PlaneView)的全部代码

package com.example.application.planegame.view;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

import com.example.application.R;
import com.example.application.activity.PlaneGame;
import com.example.application.planegame.body.Bullet;
import com.example.application.planegame.body.Plane;

import java.util.ArrayList;
import java.util.List;

public class PlaneView extends View{

    private Plane plane;//飞机对象
    private Bullet bullet;//子弹对象
    private List<Bullet> bulletList;//子弹容器
    private Paint paint;//画笔
    private BeatListener beatListener;//监听器
    public boolean flag_strike = false;//是否碰撞结束线程标志位
    public long stime;//游戏开始时间
    public long etime;//游戏结束时间
    private PlaneView planeView;
    private Handler handler;


    //构造方法
    public PlaneView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    //创建对象
    private void init(){
        paint = new Paint();
        paint.setAntiAlias(true);//抗锯齿
        paint.setAlpha(255);
        //创建飞机、子弹对象
        plane = new Plane(paint, BitmapFactory.decodeResource(getResources(),R.mipmap.plane1), 500, 1000);
        paint.setColor(Color.YELLOW);
        bulletList = new ArrayList<>();
        //创建20个子弹
        for (int i = 1;i <= 20;i++) {
            bullet = new Bullet(paint, 5, 23, 450, 330);
            bulletList.add(bullet);
        }
        //使用匿名类启动线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                    while (!flag_strike){
                        try {
                            invalidate();
                            Thread.sleep(20);
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }

            }
        }).start();
        //计算时间
        stime = System.currentTimeMillis();
    }

    //画对象
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘画飞机与子弹
        canvas.drawBitmap(plane.getBitmap(), (float) plane.getX(), (float) plane.getY(),paint);
        for (Bullet bullet1 : bulletList) {
            //判断是否出界,出界后返回
            bullet1.boundary(bullet1,getContext());
            //绘制子弹
            canvas.drawCircle((float) bullet1.getX(), (float) bullet1.getY(), (float) bullet1.getRadius(),paint);
            //判断是否碰撞
            if (strike(bullet1)) {
                etime = System.currentTimeMillis();
                //线程结束标志
                flag_strike = true;
                //调用监听器方法,是的在PlaneGame中能监听得到
                beatListener.onBeat();
                break;
            }
        }
    }

    //动态刷新实现子弹实时运动
    @Override
    public void invalidate() {
        //改变子弹的x,y坐标
        for (Bullet bullet1 : bulletList) {
            //坐标更新计算公式
            bullet1.setX((bullet1.getX() + bullet1.getMovestep() * Math.cos(bullet1.getAngle())));
            bullet1.setY((bullet1.getY() + bullet1.getMovestep() * Math.sin(bullet1.getAngle())));
        }
        super.invalidate();
    }

    //触摸移动飞机
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        if (flag_strike == false){
            switch (event.getAction()){
                //移动飞机
                case MotionEvent.ACTION_MOVE:
                    //移动的距离
                    final double xDistance = event.getX() - plane.getX();
                    final double yDistance = event.getY() - plane.getY();
                    //只有移动距离大于10之后,才能认定为移动事件
                    if (Math.abs(xDistance) > 10 || Math.abs(yDistance) > 10){
                        plane.setX(event.getX());
                        plane.setY(event.getY());
                    }
            }
            return true;
        }
        return false;
    }

    //主页面碰撞监听
    public interface BeatListener{
        void onBeat();
    }

    public void setBeatListener(BeatListener beatListener){
        this.beatListener = beatListener;
    }

    //碰撞检测
    public boolean strike(Bullet bullet){
        if (bullet.getX() >= plane.getX() && bullet.getX() <= plane.getX() + plane.getWidth()
        && bullet.getY() >= plane.getY() && bullet.getY() <= plane.getY() +plane.getHeight()){
            return true;
        }
        return false;
    }
}

后续方向

        增加英雄类型与应用商店以及金币,还有血量控制之类,同时也将进行别的小游戏的开发......

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值