注册

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

前言

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

效果展示

开始引导页面糖果收集页面收集结束页面
8d81bcc344014fadbe8491724bef5500~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp
d59cffee7fe046d7b78ea00c0195bb11~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp
508cc9c80392401eb60be7ffe927e53b~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp

实现细节

具体实现其实也很简单,主要分为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~!

作者:Jere_Chen
来源:juejin.cn/post/7054194708410531876

0 个评论

要回复文章请先登录注册