前言
本文介绍如何使用 Kotlin 语言和 Android 属性动画技术,快速开发一款包含基础、进阶与困难三个难度的糖果捕捉小游戏。文章将重点解析动态视图生成、屏幕坐标随机分布以及基于相似三角形的平移距离计算原理。
基于 Kotlin 和 Android 属性动画技术,实现了一款包含基础、进阶、困难三个难度的糖果捕捉小游戏。核心逻辑涉及动态视图创建、屏幕坐标随机生成、相似三角形计算平移距离以及递归算法确保视图移出屏幕。文章详细解析了不同难度下的数学模型与代码实现细节,并提供了优化建议。

本文介绍如何使用 Kotlin 语言和 Android 属性动画技术,快速开发一款包含基础、进阶与困难三个难度的糖果捕捉小游戏。文章将重点解析动态视图生成、屏幕坐标随机分布以及基于相似三角形的平移距离计算原理。
开发本游戏需要以下基础环境:
在 build.gradle 中确保已添加必要的依赖,虽然本示例主要使用原生 View 系统,但建议引入 Material Design 组件以提升 UI 体验。
游戏包含三个难度版本,核心玩法均为点击屏幕收集掉落的糖果:
(图:基础版糖果垂直下落示意)
(图:进阶版糖果向四周发散示意)
(图:困难版糖果从四角生成示意)
引导动画及基础版的糖果生成逻辑较为简单,此处不再赘述。本节重点讲解进阶版与困难版的核心算法与代码实现。
进阶版的核心在于让糖果从中心点向四周随机位置移动并移出屏幕。具体步骤如下:
TextView(this).apply {
// 1. 设置为居中显示
layoutParams = ConstraintLayout.LayoutParams(tvWidth, tvHeight).apply {
bottomToBottom = ConstraintSet.PARENT_ID
topToTop = ConstraintSet.PARENT_ID
startToStart = ConstraintSet.PARENT_ID
endToEnd = ConstraintSet.PARENT_ID
}
// 2. 设置糖果背景图片
background = ContextCompat.getDrawable(this@AdvancedActivity, generateRandomCandy())
// 3. 添加到根布局
viewBinding.root.addView(this)
}
上述代码通过 ConstraintLayout.LayoutParams 实现了绝对居中的布局效果。tvWidth 和 tvHeight 为预设的糖果尺寸常量。
为了模拟自然散落的效果,我们需要在屏幕范围内生成随机坐标。这里封装了两个辅助函数:
fun generateRandomX(leftRange: Int = 0, rightRange: Int = getScreenWidth()): Int {
return (leftRange..rightRange).random()
}
fun generateRandomY(leftRange: Int = 0, rightRange: Int = getScreenHeight()): Int {
return (leftRange..rightRange).random()
}
我们将屏幕划分为四个象限,生成的随机坐标点会均匀分布在这些区域中,从而决定糖果最终飞出的方向。
确定随机坐标后,需计算将该点平移到屏幕外的距离。我们采用相似三角形原理进行估算。
以左上角区域为例,假设屏幕中心点为原点,随机点坐标为 (x, y)。为了使糖果飞出屏幕,我们需要计算 X 轴和 Y 轴的位移量。取两个相同大小的相似三角形进行推导:
(halfScreenWidth - x) * -2(halfScreenHeight - y) * -2其他三个区域的计算公式类似,只是符号不同。合并后的总计算方法如下:
private fun calculatePosition(x: Int, y: Int): Pair<Int, Int> {
// 利用相似三角形来确定平移距离
return if (x <= halfScreenWidth) {
if (y <= halfScreenHeight) {
// 左上区域
Pair((halfScreenWidth - x) * -2, (halfScreenHeight - y) * -2)
} else {
// 左下区域
Pair((halfScreenWidth - x) * -2, (y - halfScreenHeight) * 2)
}
} else {
if (y <= halfScreenHeight) {
// 右上区域
Pair((x - halfScreenWidth) * 2, (halfScreenHeight - y) * -2)
} else {
// 右下区域
Pair(((x - halfScreenWidth) * 2).toInt(), (y - halfScreenHeight) * 2)
}
}
}
在实际调试中发现,仅靠简单的相似三角形计算有时会导致平移距离不足,糖果无法完全移出屏幕视野。为解决此问题,引入递归算法动态放大平移距离。
/**
* 递归检查并确保平移距离足够使 View 移出屏幕
*/
private fun checkTransitionXy(transitionXyPair: Pair<Int, Int>): Pair<Int, Int> {
// 终止条件:X 轴或 Y 轴平移距离足以覆盖屏幕宽度/高度加上 View 自身尺寸
if (transitionXyPair.first.absoluteValue > (halfScreenWidth + tvWidth)
|| transitionXyPair.second.absoluteValue > (halfScreenHeight + tvHeight)
) {
return transitionXyPair
}
// 不满足条件则放大 1.1 倍继续递归
return checkTransitionXy(
Pair(
(transitionXyPair.first * 1.1).toInt(),
(transitionXyPair.second * 1.1).toInt()
)
)
}
该算法会不断倍增平移向量,直到满足移出屏幕的条件,确保视觉效果的完整性。
困难版增加了发球点的数量,糖果从屏幕四个角落生成并向对角线方向发散。这要求我们在初始化时明确指定起始方位。
enum class Direction {
LEFT_TOP,
RIGHT_TOP,
LEFT_BOTTOM,
RIGHT_BOTTOM
}
通过 Direction.values().random() 随机选择初始方位,并据此设置 LayoutParams。
TextView(this).apply {
// 1. 设置糖果背景
background = ContextCompat.getDrawable(this@DifficultActivity, generateRandomCandy())
// 2. 根据随机方向设置初始位置
tv.layoutParams = when (Direction.values().random()) {
Direction.LEFT_TOP -> {
ConstraintLayout.LayoutParams(tvWidth, tvHeight).apply {
startToStart = ConstraintSet.PARENT_ID
topToTop = ConstraintSet.PARENT_ID
}
}
Direction.LEFT_BOTTOM -> {
ConstraintLayout.LayoutParams(tvWidth, tvHeight).apply {
startToStart = ConstraintSet.PARENT_ID
bottomToBottom = ConstraintSet.PARENT_ID
}
}
Direction.RIGHT_TOP -> {
ConstraintLayout.LayoutParams(tvWidth, tvHeight).apply {
endToEnd = ConstraintSet.PARENT_ID
topToTop = ConstraintSet.PARENT_ID
}
}
Direction.RIGHT_BOTTOM -> {
ConstraintLayout.LayoutParams(tvWidth, tvHeight).apply {
endToEnd = ConstraintSet.PARENT_ID
bottomToBottom = ConstraintSet.PARENT_ID
}
}
}
// 3. 添加到视图中
viewBinding.root.addView(this)
}
由于是从角落向对角发散,坐标计算相对直接。以左上角为例,向右下角移动:
x * 2y * 2完整的计算逻辑如下:
/**
* 计算对角线方向的平移距离
* @param x 生成的随机坐标 X 值
* @param y 生成的随机坐标 Y 值
*/
private fun calculatePosition(x: Int, y: Int): Pair<Int, Int> {
return if (x > halfScreenWidth) {
if (y > halfScreenHeight) {
// 右下区域
Pair(x * 2, y * 2)
} else {
// 右上区域
Pair(x * 2, -2 * (screenHeight - y))
}
} else {
if (y > halfScreenHeight) {
// 左下区域
Pair(-2 * (screenWidth - x), 2 * y)
} else {
// 左上区域
Pair(-2 * (screenWidth - x), -2 * (screenHeight - y))
}
}
}
此算法同样可以结合递归放大策略,以确保在不同屏幕分辨率下的兼容性。
在实现此类动态生成大量 View 的游戏时,需注意以下几点以避免内存泄漏或卡顿:
通过 Kotlin 结合属性动画,我们可以高效地实现多种动态交互效果。本文详细拆解了糖果捕捉游戏中进阶版与困难版的数学模型与代码实现。后续可进一步扩展功能,如自定义糖果生成速度、掉落速度或增加音效反馈等。开发者可根据实际需求调整参数,打造个性化的游戏体验。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online