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...

相关推荐
吃着火锅x唱着歌1 小时前
PHP7内核剖析 学习笔记 第三章 数据类型
android·笔记·学习
sunly_2 小时前
uniapp:公告上下轮播
android·uni-app
程序leo源4 小时前
深入理解指针
android·c语言·开发语言·汇编·c++·青少年编程·c#
俺不理解6 小时前
Android Compose 悬浮窗
android·生命周期·compose·悬浮窗
苏柘_level66 小时前
解决 Android 应用日志中 JDWP 报错问题
android
Devil枫6 小时前
安卓开发--使用android studio发布APP
android·ide·android studio
草明7 小时前
使用 ADB (Android Debug Bridge) 工具来截取 Android 设备的屏幕截图
android·adb
大熊的瓜地7 小时前
从0开始写android 之xwindow
android
冬瓜神君7 小时前
Android14 AOSP 允许system分区和vendor分区应用进行AIDL通信
android·binder
哥咫匙传说7 小时前
frameworks 之 SystemServiceRegistry
android·linux·车载系统