栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

android-使用compose写一个贪吃蛇游戏

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

android-使用compose写一个贪吃蛇游戏

关注公众号学习更多知识

灵感来源

前段时间看到了大佬fundroid使用compose编写俄罗斯方块的文章,深受启发,当时便决定也要把诺基亚的贪吃蛇搬到android上来,因此才有了这篇文章

本篇文章界面和思路参考于 fundroid的文章

fundroid俄罗斯方块传送: https://blog.csdn.net/vitaviva/article/details/115878190

最终效果

提前发一下效果,让读者有个心理预期,逻辑真不复杂,务必耐心看完

思路

我们的界面分为两部分,上半部分是游戏的动态显示区域,下半部分是操作区域

显示区域

显示区域也可以细分为两部分,

  • 边框+黑线边框
  • 屏幕动态显示区域

所以我们把这两部分分成两个组合@Composable baseScreen、@Composable GameScreen

baseScreen用于绘制边框和黑线边框,这部分只需要绘制一次即可,所以我们需要抽离出来

GameScreen用于绘制动态区域,主要是绘制基础的浅色砖块矩阵,和蛇身深色砖块、游离蛇身深色砖块

操作区域

操作区域也有两部分,

  • 开始重置按钮、恢复暂停按钮,这两个按钮用于操作游戏状态
  • 方向键按钮,用于操纵蛇头方向

操作区域组合名称为@Composable OperateBody。

其中操作状态按钮使用Row包裹两个@Composable GameButton来实现

方向键按钮使用一个Box来包裹四个@Composable DirectionButtom来实现

逻辑 如何数据通信

使用StateFlow.collectAsState方法来获取Compose状态,从而在状态更改的时候刷新界面

如何刷新游戏状态

使用LauncherEffect内部定义一个死循环来维护时钟,保证我们可以在自定义的时间间隔内更新游戏状态。

蛇身状态

我们使用一个数组来维护所有蛇身的砖块,然后就是蛇移动、蛇吃游离蛇身、蛇撞墙几个逻辑

蛇移动

蛇单纯移动的话,我们可以通过构建一个新数据的方式来实现,新数组的第一个元素就是蛇身下一个要移动到的砖块,然后将老的蛇身数组addAll到新的蛇身数组中,最后移除新蛇身数组的最后一个蛇身。刷新StateFlow状态

蛇吃游离蛇身

如果蛇下次要移动到的砖块位置刚好是游离蛇身的位置的话,那么我们直接将游离蛇身插入到数组的第一个元素位置,并且不移除蛇身数组最后位置的砖块

蛇撞墙

整个屏幕被我们栅格化为Width_Matrix * Height_Matrix数组,如果蛇头的位置超出栅格数组那么蛇撞墙死,游戏结束。

代码分析 StateFlow声明和使用

声明StateFlow,这部分不需要引入任何依赖

private val _flow = MutableStateFlow(SnakeState(action = Action.GameTick))
val stateFlow = _flow.asStateFlow()

使用StateFlow获取状态

 val model = viewModel()
 val state = model.stateFlow.collectAsState().value

这里的SnakeState就是我们定义的状态实体,代码如下:

data class SnakeState(
    var action: Action,
    val direction: Direction = Direction.Bottom,
    val snakeBody: MutableList = mutableListOf(SnakePart(10, 10)),
    val freeSnakePart: SnakePart = SnakePart(15, 15),
    var Width_Matrix: Int = 0,
    var Height_Matrix: Int = 0,
    var isRunning: Boolean = true
)
游戏状态时钟代码

使用LauncherEffect维持死循环,300ms分发一次时钟

val model = viewModel()
    LaunchedEffect(key1 = Unit) {
        while (isActive) {
            delay(300)
            model.dispatch()
        }
    }
蛇移动、吃游离蛇身、撞墙代码
fun dispatch() {
        val state = stateFlow.value
        if (state.action!=Action.GameTick){
            return
        }
        if (!state.isRunning) {//暂停状态不更新数据
            return
        }
        val firstPart = state.snakeBody.first()
        val newList = mutableListOf()
        if (state.direction == Direction.Left || state.direction == Direction.Right) {
            val x = firstPart.x + state.direction.increase
            if (x == state.freeSnakePart.x && firstPart.y == state.freeSnakePart.y) {//吃游离蛇身
                newList.add(state.freeSnakePart)
                newList.addAll(state.snakeBody)
                emit(state.copy(snakeBody = newList, freeSnakePart = newFreeSnakePart()))
                return
            }
            if (x > state.Width_Matrix || x < 0) {//撞墙
                emit(
                    state.copy(
                        action = Action.GameOver,
                    )
                )
                return
            } else {
                newList.add(SnakePart(x, firstPart.y))
                newList.addAll(state.snakeBody)
                newList.removeLast()//删除蛇身的最后一节
            }
        } else {
            val y = firstPart.y + state.direction.increase
            if (y == state.freeSnakePart.y && firstPart.x == state.freeSnakePart.x) {//吃游离蛇身
                newList.add(state.freeSnakePart)
                newList.addAll(state.snakeBody)
                emit(state.copy(snakeBody = newList, freeSnakePart = newFreeSnakePart()))
                return
            }
            if (y > state.Height_Matrix || y < 0) {//撞墙
                emit(
                    state.copy(
                        action = Action.GameOver,
                    )
                )
                return
            } else {
                newList.add(SnakePart(firstPart.x, y))
                newList.addAll(state.snakeBody)
                newList.removeLast()//删除蛇身的最后一节
            }
        }
        emit(state.copy(snakeBody = newList))
    }

    fun emit(state: SnakeState) {
        _flow.value = state
    }
未实现部分
  • 蛇撞自身蛇身逻辑
  • 蛇撞墙后游戏结束的动画

这部分有兴趣的可以fork代码后自己实现一下,相信会有所收获。

代码:https://github.com/ananananzhuo-blog/HappySnake

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/306171.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号