如何在运行时优雅变更程序行为?Android Kotlin游戏模式切换求助
嘿,这个问题我之前做类似Android游戏的时候也碰到过!用字符串解析确实太折腾了,尤其是模式复杂的时候很容易出错,维护起来也头大。给你几个更靠谱的方案,都是我实际验证过或者用过的:
方案1:数据类+工厂模式(最推荐,类型安全+逻辑灵活)
这个方案完全规避了解析的麻烦,还能让每个模式的逻辑独立扩展,非常适合你的场景。
首先定义一个抽象接口来统一所有棋盘模式的核心行为:
interface BoardMode { // 棋盘基础配置 val rows: Int val cols: Int val obstaclePositions: List<Pair<Int, Int>> // 每个模式的独特逻辑,比如生成特殊道具、触发规则等 fun generateSpecialTiles(): List<Tile> }
然后每个关卡模式都实现这个接口,用Kotlin数据类来存储配置,同时自定义专属逻辑:
// 简单关卡模式 data class EasyLevelMode( override val rows: Int = 5, override val cols: Int = 5, override val obstaclePositions: List<Pair<Int, Int>> = listOf(Pair(2,2)) ) : BoardMode { override fun generateSpecialTiles(): List<Tile> { // 简单模式只生成1个炸弹道具 return listOf(Tile(1, 1, TileType.BOMB)) } } // 困难关卡模式 data class HardLevelMode( override val rows: Int = 8, override val cols: Int = 8, override val obstaclePositions: List<Pair<Int, Int>> = listOf(Pair(3,3), Pair(5,5), Pair(2,6)) ) : BoardMode { override fun generateSpecialTiles(): List<Tile> { // 困难模式生成多种特殊道具 return listOf( Tile(0,0, TileType.SHIELD), Tile(7,7, TileType.BOMB), Tile(4,4, TileType.SPEED) ) } }
接下来写一个工厂类,根据当前关卡ID返回对应的模式实例:
object BoardModeFactory { fun createMode(level: Int): BoardMode { return when(level) { 1 -> EasyLevelMode() 2 -> HardLevelMode() // 依次添加剩下的20-30种模式... else -> throw IllegalArgumentException("无效关卡:$level") } } }
运行时调用就很简单了:
val currentLevel = 2 val currentBoardMode = BoardModeFactory.createMode(currentLevel) // 用currentBoardMode的属性和方法初始化游戏棋盘
优点:完全类型安全,编译时就能发现错误;每个模式的逻辑独立,新增/修改模式不会影响其他代码;维护起来直观清晰。
缺点:模式硬编码在代码中,新增模式需要重新编译APK(不过你说最多30种,这个完全可以接受)。
方案2:JSON序列化+数据类(适合动态配置场景)
如果以后需要支持无需更新APK就能修改模式(比如从后台拉取配置),可以用Kotlin官方的序列化库来处理JSON解析,比自己写字符串解析靠谱100倍。
首先在build.gradle中添加依赖:
plugins { id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.0' } dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1' }
然后定义序列化的数据类(用密封类区分不同模式):
import kotlinx.serialization.Serializable @Serializable sealed class BoardMode { abstract val rows: Int abstract val cols: Int abstract val obstaclePositions: List<Pair<Int, Int>> } @Serializable data class EasyLevelMode( override val rows: Int = 5, override val cols: Int = 5, override val obstaclePositions: List<Pair<Int, Int>> = listOf(Pair(2,2)), val specialTileCount: Int = 1 ) : BoardMode() @Serializable data class HardLevelMode( override val rows: Int = 8, override val cols: Int = 8, override val obstaclePositions: List<Pair<Int, Int>> = listOf(Pair(3,3), Pair(5,5)), val specialTileCount: Int = 3 ) : BoardMode()
把每个模式的JSON配置放在assets/board_modes目录下,比如easy_mode.json:
{ "type": "EasyLevelMode", "rows": 5, "cols": 5, "obstaclePositions": [[2,2]], "specialTileCount": 1 }
最后写一个加载类读取并反序列化JSON:
import kotlinx.serialization.json.Json import kotlinx.serialization.decodeFromString class BoardModeLoader(private val context: Context) { private val json = Json { ignoreUnknownKeys = true } suspend fun loadMode(level: Int): BoardMode { val fileName = when(level) { 1 -> "board_modes/easy_mode.json" 2 -> "board_modes/hard_mode.json" // 对应其他关卡的JSON文件 else -> throw IllegalArgumentException("无效关卡:$level") } val jsonString = context.assets.open(fileName).bufferedReader().use { it.readText() } return json.decodeFromString<BoardMode>(jsonString) } }
优点:模式配置与代码分离,修改配置无需改代码;支持动态更新(比如从服务器下载JSON)。
缺点:需要处理IO和反序列化异常;复杂逻辑仍需在代码中判断,灵活性不如工厂模式。
方案3:枚举类(适合模式逻辑简单、仅参数不同的场景)
如果所有模式的核心逻辑一致,只是参数(比如行列数、障碍物位置)不同,用枚举类最省事。
enum class BoardMode( val rows: Int, val cols: Int, val obstaclePositions: List<Pair<Int, Int>>, val specialTileTypes: List<TileType> ) { LEVEL_1(5, 5, listOf(Pair(2,2)), listOf(TileType.BOMB)), LEVEL_2(8, 8, listOf(Pair(3,3), Pair(5,5)), listOf(TileType.SHIELD, TileType.BOMB)), // 依次添加其他20-30个模式... ; // 通用逻辑可以在这里实现 fun generateSpecialTiles(): List<Tile> { return specialTileTypes.mapIndexed { index, type -> Tile(index, index, type) } } }
运行时直接根据关卡取枚举:
val currentLevel = 1 val currentMode = BoardMode.valueOf("LEVEL_$currentLevel")
优点:代码量最少,实现最简单;枚举自带单例,性能优异。
缺点:无法为单个模式自定义复杂逻辑;新增模式仍需修改代码。
总结
- 如果每个模式有独特的规则逻辑,优先选方案1(数据类+工厂模式),类型安全又灵活;
- 如果模式仅参数不同、逻辑通用,选**方案3(枚举类)**最省心;
- 如果需要动态配置模式,选方案2(JSON序列化)。
之前的字符串解析方案不仅容易出错,维护成本还高,上面这三个方案都能完美解决你的问题~
内容的提问来源于stack exchange,提问作者Neo

