GridLayout玩出花系列-使用Android原生GridLayout控件打造2048小游戏
GridLayout玩出花系列-使用Android原生GridLayout控件打造俄罗斯方块小游戏
方块消除游戏是一种简单有趣、老少皆宜的休闲益智类游戏。玩家通过将相同颜色的方块进行消除,从而获得分数。
👇🏻下面我们使用Android原生控件来实现这个小游戏(PS:不包含自定义View的方式)
实现思路
游戏场景
使用不同颜色的方块绘制一个8x8的游戏板,可以用GridLayout来进行绘制,同时包含游戏分数和重新开始按钮
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/scoreTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="分数: 0"
android:textSize="18sp" />
<GridLayout
android:id="@+id/gameBoard"
android:layout_width="match_parent"
android:layout_height="400dp"
android:columnCount="8"
android:padding="1dp"
android:rowCount="8" />
<Button
android:id="@+id/restartButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:text="重新开始" />
</LinearLayout>
定义5种不同的颜色,然后随机生成不同颜色的方块,最后添加到GridLayout中
kotlin
private val blocks = Array(8) { IntArray(8) }
private val blockButtons = Array(8) { arrayOfNulls<Button>(8) }
private val colors = arrayOf(
R.color.empty,
R.color.red,
R.color.blue,
R.color.green,
R.color.yellow,
R.color.cyan
)
private fun initializeGameBoard() {
for (i in 0 until 8) {
for (j in 0 until 8) {
val button = Button(this).apply {
layoutParams = GridLayout.LayoutParams().apply {
width = 0
height = 0
columnSpec = GridLayout.spec(j, 1f)
rowSpec = GridLayout.spec(i, 1f)
}
setOnClickListener {
onBlockClicked(i, j)
}
background = null
isAllCaps = false
}
blockButtons[i][j] = button
gameBoard.addView(button)
}
}
for (i in 0 until 8) {
for (j in 0 until 8) {
blocks[i][j] = Random.nextInt(1, colors.size)
updateBlockColor(i, j)
}
}
}
private fun updateBlockColor(row: Int, col: Int) {
blockButtons[row][col]?.setBackgroundColor(
ContextCompat.getColor(this, colors[blocks[row][col]])
)
}
此时的方块看起来是连接在一起的,我们可以添加个背景样式,然后给每个方块添加间距实现一个网格效果
xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#CCCCCC" />
<stroke
android:width="1dp"
android:color="#888888" />
</shape>
给GridLayout设置背景样式
xml
<GridLayout
android:id="@+id/gameBoard"
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="@drawable/grid_background"
android:columnCount="8"
android:padding="1dp"
android:rowCount="8" />
给每个方块添加间距setMargins(1, 1, 1, 1)
kotlin
private fun initializeGameBoard() {
for (i in 0 until 8) {
for (j in 0 until 8) {
val button = Button(this).apply {
layoutParams = GridLayout.LayoutParams().apply {
width = 0
height = 0
columnSpec = GridLayout.spec(j, 1f)
rowSpec = GridLayout.spec(i, 1f)
setMargins(1, 1, 1, 1)
}
setOnClickListener {
onBlockClicked(i, j)
}
background = null
isAllCaps = false
}
blockButtons[i][j] = button
gameBoard.addView(button)
}
}
for (i in 0 until 8) {
for (j in 0 until 8) {
blocks[i][j] = Random.nextInt(1, colors.size)
updateBlockColor(i, j)
}
}
}
此时我们的游戏场景已经完成
消除规则
点击相邻方块颜色相同的块进行消除,再方块点击时找出相邻的方块,如果相邻方块颜色相同并且数量大于2消除方块
kotlin
private fun onBlockClicked(row: Int, col: Int) {
if (isGameOver) return
val color = blocks[row][col]
if (color != 0) {
val connectedBlocks = findConnectedBlocks(row, col, color)
if (connectedBlocks.size >= 2) {
removeBlocks(connectedBlocks)
}
}
}
private fun findConnectedBlocks(row: Int, col: Int, color: Int): List<Pair<Int, Int>> {
val visited = Array(8) { BooleanArray(8) }
val connectedBlocks = mutableListOf<Pair<Int, Int>>()
fun dfs(r: Int, c: Int) {
if (r < 0 || r >= 8 || c < 0 || c >= 8 || visited[r][c] || blocks[r][c] != color) return
visited[r][c] = true
connectedBlocks.add(Pair(r, c))
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
}
dfs(row, col)
return connectedBlocks
}
private fun removeBlocks(connectedBlocks: List<Pair<Int, Int>>) {
for ((row, col) in connectedBlocks) {
blocks[row][col] = 0
updateBlockColor(row, col)
}
}
当玩家点击一个方块时,触发onBlockClicked
方法。调用findConnectedBlocks
方法使用深度优先搜索(DFS)来查找连接的相同颜色方块,当连接的相同颜色方块数量大于等于2时调用removeBlocks
方法进行消除。
查找相邻方块实现原理:
- 初始化:
- 创建一个 8x8 的布尔数组 visited,用于标记已访问的方块。
- 创建一个可变列表 connectedBlocks,用于存储找到的相连方块。
- 深度优先搜索(DFS):
- 定义一个内部函数 dfs,使用当前方块的行和列作为参数。
- DFS 从给定的起始方块开始,然后递归地探索相邻的方块。
- 边界和有效性检查:
- 检查当前位置是否在游戏范围内(r < 0 || r >= 8 || c < 0 || c >= 8)。
- 检查当前方块是否已被访问(visited[r][c])。
- 检查当前方块的颜色是否与目标颜色相同(blocks[r][c] != color)。
- 如果以上任一条件不满足,则返回,不继续探索。
- 标记和记录:
- 通过了所有检查,将当前方块标记为已访问(visited[r][c] = true)。
- 将当前方块添加到相连方块列表中(connectedBlocks.add(Pair(r, c)))。
- 递归探索:
- 对当前方块的上、下、左、右四个相邻方块递归调用 DFS。
- 保证所有相连的同色方块都会被探索到。
- 返回结果:
- 搜索完成后,返回找到的所有相连方块的列表。
填补空缺
消除方块后,上方的方块会下落填补空缺
kotlin
private fun dropBlocks() {
for (col in 0 until 8) {
var emptyRow = 7
for (row in 7 downTo 0) {
if (blocks[row][col] != 0) {
blocks[emptyRow][col] = blocks[row][col]
updateBlockColor(emptyRow, col)
if (emptyRow != row) {
blocks[row][col] = 0
updateBlockColor(row, col)
}
emptyRow--
}
}
}
}
生成新方块
方块下落后,生成新的随机颜色方块填补空缺
kotlin
private fun fillBlocks() {
for (col in 0 until 8) {
for (row in 0 until 8) {
if (blocks[row][col] == 0) {
blocks[row][col] = Random.nextInt(1, colors.size)
updateBlockColor(row, col)
}
}
}
}
分数计算
定义分数score,每消除一个方块得一分
kotlin
private var score = 0
private fun removeBlocks(connectedBlocks: List<Pair<Int, Int>>) {
for ((row, col) in connectedBlocks) {
blocks[row][col] = 0
updateBlockColor(row, col)
score++
}
}
private fun updateScore() {
scoreTextView.text = "分数: $score"
}
在方块消除时,分数进行计算,每消除一个方块加一分
游戏结束
kotlin
private var isGameOver = false
private fun onBlockClicked(row: Int, col: Int) {
if (isGameOver) return
val color = blocks[row][col]
if (color != 0) {
val connectedBlocks = findConnectedBlocks(row, col, color)
if (connectedBlocks.size >= 2) {
removeBlocks(connectedBlocks)
dropBlocks()
fillBlocks()
updateScore()
if (!hasValidMoves()) {
gameOver()
}
}
}
}
private fun hasValidMoves(): Boolean {
for (row in 0 until 8) {
for (col in 0 until 8) {
val color = blocks[row][col]
if (color != 0) {
if (findConnectedBlocks(row, col, color).size >= 2) {
return true
}
}
}
}
return false
}
private fun gameOver() {
isGameOver = true
scoreTextView.text = "Game Over! 最终得分: $score"
}
在每次移动后检查有没有相邻的同颜色方块,如果没有有效的移动,游戏结束。
拓展游戏
固定的颜色数量和生成规则致使游戏结束很难触发,为了增加游戏可玩性和难度,我们可以增加更多的颜色,在方块消除分数到达一定的数值后增加随机出来的颜色数量
xml
<resources>
<color name="white">#FFFFFF</color>
<color name="red">#FF0000</color>
<color name="blue">#0000FF</color>
<color name="green">#00FF00</color>
<color name="yellow">#FFFF00</color>
<color name="cyan">#00FFFF</color>
<color name="empty">#CCCCCC</color>
<color name="magenta">#FF00FF</color>
<color name="orange">#FFA500</color>
<color name="purple">#800080</color>
<color name="pink">#FFC0CB</color>
<color name="lime">#00FF00</color>
<color name="teal">#008080</color>
<color name="brown">#A52A2A</color>
<color name="navy">#000080</color>
</resources>
kotlin
private val colors = arrayOf(
R.color.empty,
R.color.red,
R.color.blue,
R.color.green,
R.color.yellow,
R.color.cyan,
R.color.magenta,
R.color.orange,
R.color.purple,
R.color.pink,
R.color.lime,
R.color.teal,
R.color.brown,
R.color.navy
)
//初始时颜色个数为5个
private var currentMaxColor = 5
private fun restartGame() {
score = 0
isGameOver = false
currentMaxColor = 5
updateScore()
for (i in 0 until 8) {
for (j in 0 until 8) {
blocks[i][j] = Random.nextInt(1, currentMaxColor)
updateBlockColor(i, j)
}
}
if (!hasValidMoves()) {
restartGame()
}
}
private fun fillBlocks() {
for (col in 0 until 8) {
for (row in 0 until 8) {
if (blocks[row][col] == 0) {
blocks[row][col] = Random.nextInt(1, currentMaxColor)
updateBlockColor(row, col)
}
}
}
}
private fun removeBlocks(connectedBlocks: List<Pair<Int, Int>>) {
for ((row, col) in connectedBlocks) {
blocks[row][col] = 0
updateBlockColor(row, col)
score++
}
if (score > 50 && currentMaxColor < 5) currentMaxColor = 5
if (score > 100 && currentMaxColor < 6) currentMaxColor = 6
if (score > 200 && currentMaxColor < 7) currentMaxColor = 7
if (score > 300 && currentMaxColor < 8) currentMaxColor = 8
if (score > 400 && currentMaxColor < 9) currentMaxColor = 9
if (score > 500 && currentMaxColor < 10) currentMaxColor = 10
if (score > 600 && currentMaxColor < 11) currentMaxColor = 11
if (score > 700 && currentMaxColor < 12) currentMaxColor = 12
if (score > 800 && currentMaxColor < 13) currentMaxColor = 13
}
经过修改后在游戏开始时只使用较少的颜色,随着分数的增加引入更多随机颜色,从而提升游戏的可玩性和难度