GridLayout玩出花系列-使用Android原生GridLayout控件打造方块消除小游戏

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方法进行消除。

查找相邻方块实现原理:

  1. 初始化:
    • 创建一个 8x8 的布尔数组 visited,用于标记已访问的方块。
    • 创建一个可变列表 connectedBlocks,用于存储找到的相连方块。
  2. 深度优先搜索(DFS):
    • 定义一个内部函数 dfs,使用当前方块的行和列作为参数。
    • DFS 从给定的起始方块开始,然后递归地探索相邻的方块。
  3. 边界和有效性检查:
    • 检查当前位置是否在游戏范围内(r < 0 || r >= 8 || c < 0 || c >= 8)。
    • 检查当前方块是否已被访问(visited[r][c])。
    • 检查当前方块的颜色是否与目标颜色相同(blocks[r][c] != color)。
    • 如果以上任一条件不满足,则返回,不继续探索。
  4. 标记和记录:
    • 通过了所有检查,将当前方块标记为已访问(visited[r][c] = true)。
    • 将当前方块添加到相连方块列表中(connectedBlocks.add(Pair(r, c)))。
  5. 递归探索:
    • 对当前方块的上、下、左、右四个相邻方块递归调用 DFS。
    • 保证所有相连的同色方块都会被探索到。
  6. 返回结果:
    • 搜索完成后,返回找到的所有相连方块的列表。

填补空缺

消除方块后,上方的方块会下落填补空缺

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
}

经过修改后在游戏开始时只使用较少的颜色,随着分数的增加引入更多随机颜色,从而提升游戏的可玩性和难度

完整代码

Github源码github.com/Reathin/Sam...

相关推荐
sun0077004 小时前
android ndk编译valgrind
android
AI视觉网奇5 小时前
android studio 断点无效
android·ide·android studio
jiaxi的天空5 小时前
android studio gradle 访问不了
android·ide·android studio
No Silver Bullet6 小时前
android组包时会把从maven私服获取的包下载到本地吗
android
catchadmin6 小时前
PHP serialize 序列化完全指南
android·开发语言·php
tangweiguo030519878 小时前
Kable使用指南:Android BLE开发的现代化解决方案
android·kotlin
00后程序员张10 小时前
iOS App 混淆与资源保护:iOS配置文件加密、ipa文件安全、代码与多媒体资源防护全流程指南
android·安全·ios·小程序·uni-app·cocoa·iphone
柳岸风12 小时前
Android Studio Meerkat | 2024.3.1 Gradle Tasks不展示
android·ide·android studio
编程乐学12 小时前
安卓原创--基于 Android 开发的菜单管理系统
android
whatever who cares14 小时前
android中ViewModel 和 onSaveInstanceState 的最佳使用方法
android