kotlin快速实现一款小游戏,糖果雨来啦

3,628 阅读3分钟

前言

回想小时候,一到冬天就开始期盼着学校快点放寒假,期盼着快点过年。因为过年有放不完的鞭炮与吃不完的糖果,犹记得那时候我的口袋里总是充满着各式各样的糖果。今天就以糖果为主题,实现糖果雨来啦这个互动小游戏。

效果展示

开始引导页面糖果收集页面收集结束页面

实现细节

具体实现其实也很简单,主要分为3块内容:

  1. 开始引导页面:提供开始按钮来告诉用户如何开始,3秒倒计时动画,让用户做好准备。
  2. 糖果收集页面:自动生成糖果并从上往下掉落,用户点击糖果完成收集(糖果消失 & 糖果收集总数加一)。
  3. 收集结束页面:告诉用户一共收集了多少糖果,提供再玩一次按钮入口。

引导动画

如果单单是一个静态页面,提供文字来提醒用户如何开始游戏,会略显单调,所以我加了一些自定义View动画,模拟点击动作,来达到提醒用户作用。

利用三个动画组合在一起同时执行,从达到该效果,分别是:

  1. 手指移动去点击动画。
  2. 点击后的水波纹动画。
  3. 点击后糖果+1动画。

这里我们以 点击后糖果+1动画 举例。

我们先建一个res/anim/candy_add_anim.xml文件,如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <alpha
        android:duration="3000"
        android:fromAlpha="0.0"
        android:repeatCount="-1"
        android:repeatMode="restart"
        android:toAlpha="1.0" />

    <translate
        android:duration="3000"
        android:fromYDelta="0%"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:repeatCount="-1"
        android:repeatMode="restart"
        android:toYDelta="-10%p" />

    <scale
        android:duration="3000"
        android:fromXScale="0"
        android:fromYScale="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="-1"
        android:repeatMode="restart"
        android:toXScale="1"
        android:toYScale="1" />

</set>

然后在指定的View中执行该动画,如下:

binding.candyAddOneTv.apply {
    val animation = AnimationUtils.loadAnimation(context, R.anim.candy_add_anim)
    startAnimation(animation)
}

糖果的生成

从效果展示图中也可以看出,糖果的样式是各式各样的且其位置坐标是随机的。

我通过代码动态生成一个大小固定的TextView,然后通过设置layoutParams.setMargins来确定其坐标,通过setBackground(drawable)来设置糖果背景(为了使生成的糖果是各式各样的,所以我找了一些糖果的SVG图来作为背景),然后加入到View.root

具体代码如下:

//随机生成X坐标
val leftMargin = (0..(getScreenWidth() - 140)).random()
TextView(this).apply {
    layoutParams = FrameLayout.LayoutParams(140, 140).apply {
        setMargins(leftMargin, -140, 0, 0)
    }
    background = ContextCompat.getDrawable(this@MainActivity, generateRandomCandy())
    binding.root.addView(this)
}

并且通过协程delay(250),来达到一秒钟生成4颗糖果。

fun generatePointViewOnTime() {
    viewModelScope.launch {
        for (i in 1..60) {
            Log.e(TAG, "generatePointViewOnTime: i = $i")
            pointViewLiveData.value = i
            if (i % 4 == 0) {
                countDownTimeLiveData.postValue(i / 4)
            }
            delay(250)
        }
    }

}

糖果的掉落

介绍完了糖果的生成,接着就是糖果的掉落效果实现。

这里我们同样使用View动画即可完成,通过translationY(getScreenHeight().toFloat() + 200)来让糖果从最上方平移出屏幕最下方,同时为其设置加速插值器,达到掉落速度越来越快的效果。

整个平移时间设置为3s,具体代码如下:

private fun startMoving(view: View) {
    view.apply {
        animate().apply {
            interpolator = AccelerateInterpolator()
            duration = 3000
            translationY(getScreenHeight().toFloat() + 200)
            start()
        }
    }
}

糖果的收集

点击糖果,糖果消失,糖果收集总数+1。所以我们只需为其设置点击监听器,在用户点击时,为TextView设置visibility以及catchNumber++即可。

TextView(this).apply {
    ···略···

    setOnClickListener {
        this.visibility = View.GONE
        Log.e(TAG, "onCreate: tag = ${it.tag}, id = ${it.id}")
        catchNumber++
        binding.catchNumberTv.text = getString(R.string.catch_number, catchNumber)
        doVibratorEffect()
    }
}

点击反馈

为了更好的用户体验,为点击设置震动反馈效果。

private fun doVibratorEffect() {
    val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        val vibratorManager =
            getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
        vibratorManager.defaultVibrator
    } else {
        getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        vibrator.vibrate(VibrationEffect.createOneShot(30, VibrationEffect.DEFAULT_AMPLITUDE))
    } else {
        vibrator.vibrate(30)
    }
}

结束弹窗

当糖果收集结束后,弹出一个结束弹窗来告诉用户糖果收集情况,这里采用属性动画,让弹窗弹出的效果更加的生动。

private fun showAnimation(view: View) {
    view.scaleX = 0F
    view.scaleY = 0F

    //zoom in 放大;zoom out 缩小;normal 恢复正常
    val zoomInHolderX = PropertyValuesHolder.ofFloat("scaleX", 1.05F)
    val zoomInHolderY = PropertyValuesHolder.ofFloat("scaleY", 1.05F)
    val zoomOutHolderX = PropertyValuesHolder.ofFloat("scaleX", 0.8F)
    val zoomOutHolderY = PropertyValuesHolder.ofFloat("scaleY", 0.8F)
    val normalHolderX = PropertyValuesHolder.ofFloat("scaleX", 1F)
    val normalHolderY = PropertyValuesHolder.ofFloat("scaleY", 1F)
    val zoomIn = ObjectAnimator.ofPropertyValuesHolder(
        view,
        zoomInHolderX,
        zoomInHolderY
    )

    val zoomOut = ObjectAnimator.ofPropertyValuesHolder(
        view,
        zoomOutHolderX,
        zoomOutHolderY
    )
    zoomOut.duration = 400

    val normal = ObjectAnimator.ofPropertyValuesHolder(
        view,
        normalHolderX,
        normalHolderY
    )
    normal.duration = 500

    val animatorSet = AnimatorSet()
    animatorSet.playSequentially(zoomIn, zoomOut, normal)
    animatorSet.start()
}

总结

如果你对该小游戏有兴趣,想进一步了解一下代码,可以参考Github Candy-Catch,欢迎你给我点个小星星。

相信很多人都有这样的感受,随着年龄的增加,越来越觉得这年味越来越淡了,随之而来对过年的期盼度也是逐年下降。在这里,我愿大家童心未泯,归来仍是少年!

最后,给大家拜个早年,祝大家新春快乐

其实分享文章的最大目的正是等待着有人指出我的错误,如果你发现哪里有错误,请毫无保留的指出即可,虚心请教。

另外,如果你觉得文章不错,对你有所帮助,请给我点个赞,就当鼓励,谢谢~Peace~!