美文网首页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