美文网首页Android进阶之路Android技术知识Android开发
Android 飞机小游戏(自定义ViewGroup+View)

Android 飞机小游戏(自定义ViewGroup+View)

作者: 来碗红油米粉 | 来源:发表于2020-03-03 18:09 被阅读0次

由于最近失业在家,闲下来了想着做个小游戏。以便于以后没信号的时候可以玩玩。先上效果图并附上 源码

成品效果图

思路

  1. 能移动的飞行物(View)
  2. 容纳飞行物的容器 (ViewGroup)
  3. 控制飞行物在容器中的位置、移动、以及碰撞检测

飞行物

首先捋一捋创建一个飞行物所需要的属性

  1. 类型(我的飞机、敌机、我的子弹、敌机子弹、BOSS飞机、BOSS子弹,为了方便我把爆炸效果也归类到飞行物中)
  2. 视图(View)
  3. 坐标
  4. 宽高
  5. 血量
  6. 威力(敌机撞到我,敌机子弹撞到我等等......)
  7. 飞行物的死亡标记
  8. 飞行物的飞行速度
  9. 为了方便计算,在内部算出ta的中心X和中心Y
  10. context

好的,屡清楚了可以开始写了

首先创建飞行物的基类Fly

abstract class Fly {
    //飞行物类型
    var flyType: FlyType
    //飞行物展示的view
    var view: View
    //飞行物的的偏移值 xy
    var x = 0F
        set(value) {
            view.x = value
            cx = (view.x + w / 2)
            field = value
        }
    var y = 0F
        set(value) {
            view.y = value
            cy = (view.y + h / 2)
            field = value
        }
    //飞行物的宽高
    var w = 0
    var h = 0
    //飞行物的血量
    var HP = 100
    //飞行物的碰撞威力
    var power = 100
    //这个飞行物是否已经死亡
    var isBoom = false
    //飞行物的移动速度 越小越快
    var speed = 1
    //上下文
    var context: Context
    //飞行物的中心点
    var cx = 0F
    var cy = 0F
}

这里飞行物基类创建好了之后开始派生子类

  1. 飞机(Plane.kt
  2. 子弹(Bullet.kt
  3. 爆炸(Boom.kt

创建飞行物的枚举类型FlyType

enum class FlyType {
    PLANE_GCD,  //我军飞机
    PLANE_GMD,  //敌军飞机
    PLANE_BOSS, //boss飞机
    BULLET_GCD, //我军子弹
    BULLET_GMD, //敌军子弹
    BULLET_BOSS,//boss子弹
    BOOM,
    OTHER //预留
}

创建飞行物的视图(View)
作图是不可能作图的,这辈子都不可能作图,只能靠onDraw维持生活这样子

精神领袖
  1. 飞机PlaneView (我机和敌机都用这个view,如果是敌机就给他旋转180°,canvas.rotate
    竖着draw一个rect为飞机主体,横着draw两个rect为飞机的翅膀 ,再在尾部绘制火焰效果(火焰效果看这里,飞机就完成啦(点我看图),代码我就不贴了。

  2. 子弹BulletView (我机和敌机一样都用这个View)
    竖着draw一个rect为子弹主体,横着draw一个rect为子弹尾翼,再在尾部绘制火焰效果

  3. 爆炸效果BoomView
    实现的思路就是在view的中心绘制12个rect,之后用动画将这个12个rect沿着同一个方向移动,
    在onDraw的过程中,每绘制一个之后,canvas.rotate(30),这样就有扩散效果了

容器

MapView

class MapView : ViewGroup {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)

    private var w = 0
    private var h = 0
    private var loadFinish = false
    var onMeasureFinishListener: OnViewLoadFinishListener? = null

    init {
        setWillNotDraw(true)
    }

    /**
     * 指定飞行物的位置
     */
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (child != null) {
                val childW = child.measuredWidth
                val childH = child.measuredHeight
                child.layout(0, 0, childW, childH)
            }

        }
    }

    /**
     * 测量
     */
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        w = measuredWidth
        h = measuredHeight
        if (!loadFinish) {
            onMeasureFinishListener?.onFinish(w, h)
            loadFinish = true
        }
    }

    /**
     * 添加飞行物
     */
    fun addFly(fly: Fly) {
        addView(fly.view)

    }

    /**
     * 删除飞行物
     */
    fun removeFly(fly: Fly) {
        removeView(fly.view)
    }

    interface OnViewLoadFinishListener {
        fun onFinish(w: Int, h: Int)
    }

}

控制

首先提供一个简单的工厂类来获取飞机、子弹、爆炸

object FlyFactory{
  /**
     * 获取飞机的Fly
     */
    fun getPlane(context: Context, flyType: FlyType): Plane?{......}
 /**
     * 获取子弹的Fly
     */
    fun getBullet(context: Context, flyType: FlyType): Bullet?{......}
  /**
     * 获取爆炸效果的Fly
     */
    fun getBoom(context: Context, flyType: FlyType): Boom? {......}
  /**
     * 获取爆炸View
     */
    private fun getBoomView(context: Context, flyType: FlyType): View?{......}
  /**
     * 获取飞机或子弹的View
     */
    private fun getView(context: Context, flyType: FlyType): View?{......}
}

主要的逻辑控制类FlyController

飞行物管理的集合(为了避免ConcurrentModificationException异常,这里用CopyOnWriteArrayList)

/**
     * 我军飞机
     */
    private var gcdPlanes: CopyOnWriteArrayList<Fly> = CopyOnWriteArrayList()
    /**
     * 敌军飞机集合
     */
    private var gmdPlanes: CopyOnWriteArrayList<Fly> = CopyOnWriteArrayList()
    /**
     * 我军子弹集合
     */
    private var gcdBullets: CopyOnWriteArrayList<Fly> = CopyOnWriteArrayList()
    /**
     * 敌军子弹集合
     */
    private var gmdBullets: CopyOnWriteArrayList<Fly> = CopyOnWriteArrayList()

创建飞机

        val plane = FlyFactory.getPlane(activity, flyType)!!
        if (flyType == FlyType.PLANE_GCD) {
            //我的飞机的位置y在屏幕的最下方,x在屏幕中心位置
            plane.x = w / 2F - plane.w / 2
            plane.y = h - plane.h.toFloat()
        } else if (flyType == FlyType.PLANE_GMD) {
            //敌机y在屏幕的最上方,x则是容器的w以内的随机位置
            plane.x = (w - plane.w) * random.nextFloat()
            plane.y = -plane.h * 2F
        } 

将飞机加入集合与容器MapView

/**
     * 添加Fly到集合和mapView
     */
    private fun addFly(fly: Fly) {
        when (fly.flyType) {
            FlyType.PLANE_GCD -> {
                gcdPlanes.add(fly)
            }
            FlyType.PLANE_GMD -> {
                gmdPlanes.add(fly)
            }
            ......省略代码
        }
        mapView.addFly(fly)
      
    }

对应的有添加就有删除

    /**
     * 从集合和mapView中删除fly
     */
    private fun removeFly(fly: Fly) {
        when (fly.flyType) {
            FlyType.PLANE_GMD -> {
                createPlane(FlyType.PLANE_GMD)
                gmdPlanes.remove(fly)
            }
            FlyType.PLANE_GCD -> {
                gcdPlanes.remove(fly)
            }
            ......省略代码
        }
        mapView.removeFly(fly)
       //删除的时候标记为死亡状态
        fly.boom()
    }

创建子弹(子弹的初始位置为发射子弹飞机的当前位置) 所以需要传入plane

   /**
     * 创建子弹
     */
    private fun createBullet(plane: Plane) {
        val bullet = FlyFactory.getBullet(activity, plane.flyType)!!

        bullet.x = (plane.cx - bullet.w / 2)

        bullet.y = (plane.cy - bullet.h / 2)
 
    }

启动线程为飞机创建子弹

    /**
     * 我的飞机自动发射子弹线程
     */
    private fun startGcdPlaneShotThread() {
        Thread {
            while (true) {
                activity.runOnUiThread {
                    if (gcdPlanes.size != 0) {
                        shot(gcdPlanes[0] as Plane)
                    }
                }
                SystemClock.sleep(200)
            }
        }.start()
    }

    /**
     * 敌机自动发射子弹线程
     */
    private fun startGmdPlaneShotThread() {
        Thread {
            while (true) {
                //遍历敌机线程,并随机确定是否为飞机创建子弹
                gmdPlanes.forEach {
                    activity.runOnUiThread {
                        if (random.nextInt(3) % 3 == 0 )
                            shot(it as Plane)
                    }
                }
                SystemClock.sleep(2000)

            }
        }.start()
    }

飞机和子弹创建好之后需要立马移动它们 (飞机和子弹的飞行动画)

/**
     * y轴移动Fly
     */
    private fun moveFlyY(fly: Fly) {
        //如果fly已经死亡
        if (fly.isBoom) {
            return
        }
        //移动的起始位置是自己的y
        var start = fly.cy
       //移动的目标位置,如果是敌机和敌机子弹,则是屏幕的最下方再加自身两个高度 ,我的子弹则相反
        var end = when (fly.flyType) {
            FlyType.BULLET_GCD -> {
                -fly.h * 2
            }
            FlyType.PLANE_GCD -> {
                return
            }
            FlyType.PLANE_GMD -> {
                h + fly.h * 2
            }
            FlyType.BULLET_GMD -> {
                h + fly.h * 2
            }
        ......省略代码
        }

        ValueAnimator.ofFloat(start, end.toFloat())
            .apply {
                addUpdateListener {
                    //在动画过程中,如果经过碰撞检测判定飞行器已经死亡,则马上取消动画
                    if (fly.isBoom) {
                        cancel()
                        return@addUpdateListener
                    }

                    //更改飞行物的y值以移动飞行物
                    fly.y = (it.animatedValue as Float)

                   ......省略代码
                }
                //动画的时间取值是起始距离到结束距离的值座位毫秒数,再乘以飞行器的速度
                //speed越高则飞行越慢,再之前给敌机的speed赋值的时候可以使用随机数,让敌机飞行有块有慢
                duration = (abs(start - end)).toLong() * fly.speed
                interpolator = LinearInterpolator()
                start()
            }
    }

经过上面过程之后,飞机和子弹就动起来了。接下来在动画中,需要做飞出屏幕的检测和碰撞检测

首先是飞出屏幕检测

/**
     * Fly位置检测 如果已经超出屏幕则删除
     */
    private fun checkFlyPosition(fly: Fly): Boolean {
       //如果view已经不再屏幕内了 删除它
        if (fly.x + fly.w <= -fly.w ||

            fly.x >= w + fly.w ||

            fly.y + fly.h <= -fly.h ||

            fly.y >= h + fly.h
        ) {
            removeFly(fly)
            return false
        }
        return true
    }

碰撞检测

    /**
     * 通过flyType来决定选择哪个集合中的元素与它进行碰撞检测
     */
    private fun selectHitAndBeHit(fly: Fly) {
        when (fly.flyType) {
            FlyType.BULLET_GCD -> {
                //如果是我的子弹,则与敌机集合检测
                hitAndBeHit(fly, gmdPlanes)
            }
            FlyType.BULLET_GMD -> {
                //如果是敌机的子弹则与我机检测
                hitAndBeHit(fly, gcdPlanes)
            }
            FlyType.PLANE_GMD -> {
                //...
                hitAndBeHit(fly, gcdPlanes)
            }
            FlyType.PLANE_BOSS -> {
                //...
                hitAndBeHit(fly, gcdPlanes)
            }
            FlyType.BULLET_BOSS -> {
                //...
                hitAndBeHit(fly, gcdPlanes)
            }
        }
    }

    /**
     * 碰撞和被碰撞的集合
     */
    private fun hitAndBeHit(hitFly: Fly, beHitFlys: CopyOnWriteArrayList<Fly>) {
        for (beHitFly in beHitFlys) {
            //碰撞之后跳出循环
            if (isCollision(
                    hitFly.x,
                    hitFly.y,
                    hitFly.w.toFloat(),
                    hitFly.h.toFloat(),
                    beHitFly.x,
                    beHitFly.y,
                    beHitFly.w.toFloat(),
                    beHitFly.h.toFloat()
                )
            ) {
                sellingHP(hitFly, beHitFly)
                break
            }
        }
    }

    /**
     * 碰撞检测
     */
    private fun isCollision(
        x1: Float,
        y1: Float,
        w1: Float,
        h1: Float,
        x2: Float,
        y2: Float,
        w2: Float,
        h2: Float
    ): Boolean {
        if (x1 > x2 + w2 || x1 + w1 < x2 || y1 > y2 + h2 || y1 + h1 < y2) {
            return false
        }
        return true
    }


    /**
     * 碰撞扣除HP
     */
    private fun sellingHP(fly1: Fly, fly2: Fly) {
        fly1.HP -= fly2.power
        fly2.HP -= fly1.power
        isDid(fly1)
        isDid(fly2)
    }

    /**
     * 飞行物是否死亡
     */
    private fun isDid(fly: Fly) {
        if (fly.HP <= 0) {
          //创建爆炸效果
            createBoom(fly)
            removeFly(fly)
        }
    }

当碰撞发生时 如果有飞行器死亡,则会触发一个爆炸效果,位置就在飞行器死亡的位置

 /**
     * 创建爆炸效果
     */
    private fun createBoom(fly: Fly) {
        val boom = FlyFactory.getBoom(activity, fly.flyType)!!
        boom.x = (fly.cx - boom.w / 2)
        boom.y = (fly.cy - boom.h / 2)
        mapView.addFly(boom)

        val flyBoomView = boom.view as FlyBoomView
        //爆炸动画结束的时候将其从mapView中删除
        flyBoomView.animatorListener = object : FlyBoomView.AnimatorListener {
            override fun onAnimationEnd() {
                removeFly(boom)
            }
        }
    }

完!

相关文章

网友评论

    本文标题:Android 飞机小游戏(自定义ViewGroup+View)

    本文链接:https://www.haomeiwen.com/subject/iohhlhtx.html