Android游戏教程:空间大战 - 动画特效

发布时间:2023-11-30 12:48:16 浏览量:252次

暂停
00:00 / 00:23
00:00
进入全屏
50
    点击按住可拖动视频

    游戏效果演示

    游戏动画其实就是将图片资源逐帧逐帧地播放,只是在这部游戏中我们并不打算用图片文件而是用程序代码手绘画面,因为是手绘所以画面简单了些。好了!照例先定义一个父类,然后再定义其它的动画类并继承父类。

    import android.graphics.Canvas
    
    abstract class BaseEffect {
        // 动画播放的中心坐标
        protected var x: Float = 0f
        protected var y: Float = 0f
        var free: Boolean = true
    
        abstract fun draw(canvas: Canvas)
    }

    定义完父类后就可以定义实际的子类了,我们先定义子弹的弹痕类BulletEffect.kt

    import android.graphics.*
    import com.bamboo.boxspacegame.AppGobal
    
    /**
     * 子弹击中物体时的特效
     */
    class BulletEffect : BaseEffect() {
        private var currentFrame = 0
    
        companion object {
            private const val FRAME_COUNT = 15 // 动画的总帧数
    
            /**
             * 初始化弹痕的Bitmap并缓存
             */
            fun init() {
                val paint = Paint()
                paint.color = Color.WHITE
                paint.style = Paint.Style.FILL
                repeat(FRAME_COUNT) {
                    val bmp = Bitmap.createBitmap(
                        AppGobal.unitSize.toInt(),
                        AppGobal.unitSize.toInt(),
                        Bitmap.Config.ARGB_8888
                    )
                    Canvas(bmp).apply {
                        val unit = AppGobal.unitSize / 2f
                        paint.alpha = 255 - (255 / FRAME_COUNT * it)
                        paint.shader = RadialGradient(
                            unit, unit, unit + 0.1f,
                            intArrayOf(Color.WHITE, Color.TRANSPARENT), null,
                            Shader.TileMode.CLAMP
                        )
                        this.drawCircle(unit, unit, unit, paint)
                    }
                    AppGobal.bmpCache.put("bulletEffect_$it", bmp)
                }
            }
        }
    
        /**
         * 根据当前帧的编号绘制动画
         */
        override fun draw(canvas: Canvas) {
            val bmp = AppGobal.bmpCache["bulletEffect_$currentFrame"]
            val ex = x - AppGobal.unitSize / 2
            val ey = y - AppGobal.unitSize / 2
            canvas.drawBitmap(bmp, ex, ey, null)
            currentFrame++
            if (currentFrame >= FRAME_COUNT) {
                currentFrame = 0
                free = true
            }
        }
    
        /**
         * 播放动画并设置动画播放的坐标
         */
        fun play(x: Float, y: Float) {
            free = false
            this.x = x
            this.y = y
            this.currentFrame = 0
        }
    
    }

    当子弹击中目标或击在屏幕边界的时候就开始播放弹痕动画。接着定义开始爆炸动画类BombEffect.kt

    import android.graphics.*
    import com.bamboo.boxspacegame.AppGobal
    
    /**
     * 爆炸动画特效
     */
    class BombEffect : BaseEffect() {
        private val paint = Paint()
        private var currentFrame = 1
        private var onFinished: (() -> Unit)? = null // 动画播放完毕后的回调函数
    
        companion object {
            const val FRAME_COUNT = 20 // 动画的总帧数
        }
    
        /**
         * 播放动画
         * @param onFinished 动画播放完毕后的回调函数,默认可以不传
         */
        fun play(x: Float, y: Float, onFinished: (() -> Unit)? = null) {
            this.free = false
            this.x = x
            this.y = y
            this.currentFrame = 0
            this.onFinished = onFinished
        }
    
        /**
         * 直接在屏幕上绘制图像,并不在游戏初始化时缓存
         */
        override fun draw(canvas: Canvas) {
            paint.color = Color.WHITE
            paint.strokeWidth = 2f
            val inc = AppGobal.unitSize / FRAME_COUNT
            currentFrame++
            if (currentFrame >= FRAME_COUNT) {
                currentFrame = 0
                free = true
                onFinished?.let { it() }
            } else {
                paint.style = if (currentFrame >= FRAME_COUNT / 2) Paint.Style.STROKE
                else Paint.Style.FILL
                canvas.drawCircle(x, y, inc * currentFrame, paint)
            }
        }
    }

    接着实现游戏的瞬移动画类FlashEffect.kt

    import android.graphics.*
    import com.bamboo.boxspacegame.AppGobal
    
    /**
     * 瞬移的动画特效
     * 动画可以正序或倒序播放
     */
    class FlashEffect : BaseEffect() {
        private var isInvert: Boolean = false // 判断动画是正序播放还是倒序播放
        private var currentFrame = 0
        private var onFinished: (() -> Unit)? = null // 动画播放完毕后的回调函数
    
        companion object {
            private const val FRAME_COUNT = 30
    
            fun init() {
                val unit = AppGobal.unitSize / 2
                val paint = Paint()
                paint.color = Color.WHITE
                paint.style = Paint.Style.FILL_AND_STROKE
                paint.strokeWidth = 1f
                repeat(FRAME_COUNT) {
                    // 绘制瞬移的各帧图像
                    val bmp = Bitmap.createBitmap(
                        (AppGobal.unitSize * 2).toInt(),
                        (AppGobal.unitSize * 2).toInt(),
                        Bitmap.Config.ARGB_8888
                    )
                    Canvas(bmp).apply {
                        paint.shader = RadialGradient(
                            unit, unit, unit,
                            intArrayOf(
                                Color.parseColor("#33FFFFFF"),
                                Color.parseColor("#66FFFFFF"),
                                Color.WHITE,
                            ), null,
                            Shader.TileMode.CLAMP
                        )
                        val step = (unit / FRAME_COUNT) * it
                        this.drawCircle(unit, unit, unit - step, paint)
                        paint.shader = RadialGradient(
                            unit, unit, unit,
                            intArrayOf(Color.WHITE, Color.parseColor("#33FFFFFF")), null,
                            Shader.TileMode.CLAMP
                        )
                        this.drawOval(
                            unit - (unit + step), unit - 2,
                            unit + (unit + step) - 1, unit + 2,
                            paint
                        )
                        this.drawOval(unit - 1, unit - 6, unit + 1, unit + 6, paint)
                    }
                    AppGobal.bmpCache.put(AppGobal.BMP_FLASH + "_$it", bmp)
                }
            }
        }
    
        override fun draw(canvas: Canvas) {
            val bmp = AppGobal.bmpCache[AppGobal.BMP_FLASH + "_$currentFrame"]
            canvas.drawBitmap(bmp, x, y, null)
            if (isInvert) {
                currentFrame--
                if (currentFrame < 0) {
                    currentFrame = 15
                    free = true
                    onFinished?.let { it() }
                }
            } else {
                currentFrame++
                if (currentFrame >= FRAME_COUNT) {
                    currentFrame = 0
                    free = true
                    onFinished?.let { it() }
                }
            }
        }
    
        /**
         * 播放瞬移动画
         * @param isInvert 是否倒置播放动画
         * @param onFinished 动画播放完毕后响应
         */
        fun play(x: Float, y: Float, isInvert: Boolean = false, onFinished: (() -> Unit)? = null) {
            this.x = x
            this.y = y
            this.free = false
            this.currentFrame = if (isInvert) FRAME_COUNT - 1 else 0
            this.isInvert = isInvert
            this.onFinished = onFinished
        }
    }

    至此,游戏所需要的三个动画类就全部完成了。现在我们要实现了个管理类,对这种特效类进行统一管理。EffectManager.kt

    import android.graphics.Canvas
    
    /**
     * 特效管理类
     */
    object EffectManager {
        private val listEffect = mutableListOf<BaseEffect>()
    
        @Synchronized
        private inline fun <reified T : BaseEffect> obtain(t: T): BaseEffect {
            val effect = listEffect.find {
                it.free && (it is T)
            }
            return effect ?: t.apply { listEffect += this }
        }
    
        fun obtainBomb(): BombEffect {
            return obtain(BombEffect()) as BombEffect
        }
    
        fun obtainFlash(): FlashEffect {
            return obtain(FlashEffect()) as FlashEffect
        }
    
        fun obtainBullet(): BulletEffect {
            return obtain(BulletEffect()) as BulletEffect
        }
    
        fun draw(canvas: Canvas) {
            try {
                listEffect.filter { !it.free }.forEach { it.draw(canvas) }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        
        @Synchronized
        fun release() {
            listEffect.clear()
        }
    }

    下一篇我们就要实现游戏的关卡类了。

    传送门:Android游戏教程:空间大战

    如果对我的文章感兴趣或有疑问的话可以加我的公众号或Q群聊:口袋里的安卓

    热门课程推荐

    热门资讯

    请绑定手机号

    x

    同学您好!

    您已成功报名0元试学活动,老师会在第一时间与您取得联系,请保持电话畅通!
    确定