• 使用Jetpack Compose构建Flappy Musketeer街机游戏


    使用Jetpack Compose构建Flappy Musketeer街机游戏

    一步一步创建沉浸式移动游戏的指南

    引言

    Flappy Musketeer不仅是又一个移动游戏;它将令人上瘾的“轻点飞行”游戏玩法和引人入胜的视觉效果融合在一起,吸引玩家进入埃隆·马斯克(Elon Musk)的非凡事业,包括SpaceX和Twitter(X)。此外,玩家可以通过选择各种主题和配色方案来个性化他们的游戏体验。

    在本文中,我们将使用Jetpack Compose从头开始构建Flappy Musketeer。我们将剖析代码、逻辑和设计决策,让您了解创造沉浸式安卓游戏体验的过程。

    App实现架构

    源代码概述图表
    Game Source Code Overview Diagram

    使用Jetpack Compose 设置主题

    在Flappy Musketeer中,创建正确的氛围和视觉美学对玩家的体验至关重要。让我们更详细地看看如何实现这一点。

    1. AppTheme Composable

    我们的主题系统的核心在于AppTheme Composable。这个Composable负责将选定的配色方案应用到整个游戏的用户界面。以下是它的外观 -

    @Composable
    fun AppTheme(
        colorScheme: ColorScheme = twitter,
        content: @Composable () -> Unit
    ) {
        val view = LocalView.current
        if (!view.isInEditMode) {
            SideEffect {
                val window = (view.context as Activity).window
                window.statusBarColor = colorScheme.primary.toArgb()
                window.navigationBarColor = colorScheme.primary.toArgb()
            }
        }
    
        MaterialTheme(
            colorScheme = colorScheme,
            content = content
        )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    AppTheme Composable负责配置配色方案,并将其应用于Android设备的状态栏和导航栏。这确保了游戏中始终具有一致的视觉体验。

    1. 自定义配色方案

    Flappy Musketeer为玩家提供了各种主题和配色方案可供选择。以下是一些可用选项的概览
    Game Theme Colors

    Space.X.Mars - 受火星的锈色地形启发。
    Twitter.Doge - 一个玩味十足的主题,以Dogecoin吉祥物为特色。
    Twitter.White - 白色配色方案的清洁和极简设计。
    Space.X.Moon - 受月球宁静美丽的黑暗主题启发。

    这些主题在代码中被定义为ColorScheme对象,可以轻松自定义并应用于游戏用户界面的不同部分。

    val spaceX = darkColorScheme(
        primary = spacePurple,
        secondary = Color.Black,
        tertiary = Color.Black
    )
    
    val twitter = darkColorScheme(
        primary = earthYellow,
        secondary = twitterBlue,
        tertiary = Color.Black
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过提供各种主题,Flappy Musketeer为玩家提供了个性化的游戏体验。

    1. 主题背景

    除了配色方案,Flappy Musketeer还提供了与所选配色方案相匹配的各种背景。这些背景为游戏环境增添了深度和沉浸感。我们使用GameBackground枚举类来维护背景集合。这些背景图像根据所选主题动态加载,确保游戏的视觉效果与玩家的偏好相一致。

    enum class GameBackground(val url: String) {
        TWITTER_DOGE("https://source.unsplash.com/qIRJeKdieKA"),
        SPACE_X("https://source.unsplash.com/ln5drpv_ImI"),
        SPACE_X_MOON("https://source.unsplash.com/Na0BbqKbfAo"),
        SPACE_X_MARS("https://source.unsplash.com/-_5dCixJ6FI")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 在游戏中切换主题

    为了使主题选择过程无缝进行,Flappy Musketeer提供了一个getGameTheme函数,该函数以主题名称作为输入,并返回相应的ColorScheme。以下是其工作原理 -

    fun getGameTheme(gameId: String?): ColorScheme {
        return when (gameId) {
            GameBackground.SPACE_X.name -> spaceX
            GameBackground.TWITTER.name -> twitter
            // ... (other theme mappings)
            else -> twitter
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这个函数允许游戏根据菜单中的游戏选项选择来切换主题。

    通过Flappy Musketeer导航

    现在我们已经介绍了使用Jetpack Compose进行主题设置的基础知识,让我们将重点转向游戏的导航。Flappy Musketeer利用Navigation组件来在不同的屏幕和游戏状态之间实现无缝切换。

    1. App Composable
      导航系统的核心是App composable。这个composable使用Jetpack Compose的Navigation组件来设置游戏的导航。以下是它的样子 -
    @Composable
    fun App() {
        val navController = rememberNavController()
        NavHost(navController = navController, startDestination = AppRoutes.MENU.name) {
            composable(AppRoutes.MENU.name) {
                AppTheme(colorScheme = menuTheme) {
                    GameMenu(navController)
                }
            }
            composable("${AppRoutes.GAME.name}/{gameId}") {
                val gameId = it.arguments?.getString("gameId")
                val gameTheme = getGameTheme(gameId)
    
                AppTheme(colorScheme = gameTheme) {
                    GameScreen(navController, gameId)
                }
            }
    
            composable(AppRoutes.GAME_OVER.name) {
                XLottie(navController)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这段代码设置了导航图,定义了游戏的流程。让我们来详细解析一下:

    App composable 初始化了NavController,用于管理游戏内的导航。
    我们将MENU屏幕设置为初始目标。
    在NavHost中,我们为每个屏幕定义了可组合函数,例如游戏菜单和游戏界面。
    我们使用AppTheme为每个屏幕应用相应的主题,以保持视觉一致性。

    1. 导航目的地
      Navigation Destinations
      菜单屏幕(AppRoutes.MENU.name)- 当玩家第一次打开游戏时,他们会进入这个屏幕。AppTheme可组合函数设置了菜单主题,为游戏的主菜单创建了一个统一的外观。玩家可以在这里选择游戏。

    游戏屏幕(${AppRoutes.GAME.name}/{gameId})- 游戏屏幕根据所选的游戏动态调整其主题。使用getGameTheme函数,我们获取相应的配色方案,并使用AppTheme应用它。这确保每个游戏的主题与其环境相匹配,无论是Twitter.Doge还是Space.X.Mars。

    游戏结束屏幕(AppRoutes.GAME_OVER.name)- 当游戏结束时,显示此屏幕,并带有一个Twitter X Lottie动画。当玩家的游戏结束时,导航系统会无缝地过渡到这个屏幕。

    通过这种导航设置,玩家可以轻松浏览Flappy Musketeer的不同部分,从选择游戏到游戏过程中,再到游戏结束屏幕的体验。

    创建Game Menu

    游戏菜单通常是玩家的第一个互动点,为整个游戏体验设定了基调。在Flappy Musketeer中,游戏菜单被设计成视觉上引人入胜且用户友好的。让我们更详细地看一下它是如何实现的

    1. GameMenu可组合函数
      GameMenu可组合函数是进入Flappy Musketeer的入口点。它为玩家提供了访问各种游戏主题和重要链接的方式。以下是定义GameMenu的代码片段 -
      Game Menu
    @Composable
    fun GameMenu(navController: NavController) {
        val uriHandler = LocalUriHandler.current
    
        // ... (Theme setup)
    
        Column(modifier = Modifier.fillMaxSize()) {
            // ... (Top bar styling)
    
            Column(
                modifier = Modifier
                    .wrapContentHeight()
                    .fillMaxWidth()
                    .background(Color(0xFF0E2954)),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                // ... (Logo and app name)
    
                Spacer(modifier = Modifier.height(10.dp))
    
                Row {
                    // ... (About App button)
    
                    Spacer(modifier = Modifier.width(12.dp))
    
                    // ... (Creator button)
                }
            }
    
            Row(
                modifier = Modifier
                    .fillMaxSize()
                    .horizontalScroll(rememberScrollState(), enabled = true)
                    .background(
                        brush = Brush.verticalGradient(
                            listOf(
                                Color(0xFF0E2954),
                                Color(0xFF1F6E8C)
                            )
                        )
                    ),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Spacer(modifier = Modifier.width(30.dp))
    
                GameBackground.values().forEach {
                    MenuItem(
                        navController,
                        backgroundUrl = it.url,
                        name = it.name.replace("_", ".").uppercase(),
                        originalName = it.name
                    )
                    Spacer(modifier = Modifier.width(30.dp))
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 游戏的标志以IconButton的形式显示,当点击时会打开一个链接。这为菜单屏幕增添了一丝互动性。
    • 应用程序名称使用buildAnnotatedString进行样式设置,允许自定义字体粗细和样式。
    • 菜单以水平滚动行的形式实现,允许玩家通过滑动浏览不同的主题。每个主题都表示为一个MenuItem可组合函数,具有其背景图像和名称。
    1. MenuItem Composable
    @Composable
    fun MenuItem(
        navController: NavController,
        backgroundUrl: String,
        name: String,
        originalName: String
    ) {
        // ... (Menu item content)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这个可组合函数负责显示主题的背景图像和名称。当单击时,它会根据所选主题将玩家导航到相应的游戏屏幕。

    主屏幕逻辑

    GameScreen可组合函数是Flappy Musketeer游戏中负责管理游戏逻辑的核心组件。这个代码文件定义了游戏在游戏过程中的行为,包括处理用户输入、更新游戏状态和渲染游戏元素。

    让我们逐步分解GameScreen可组合函数,并用相关的代码片段解释每部分代码

    1. 状态初始化
    // 初始化游戏状态和得分
    var gameState by remember { mutableStateOf(GameState.NOT_STARTED) }
    var score by remember { mutableLongStateOf(0L) }
    var lastScore by remember { mutableLongStateOf(preferencesManager.getData("last_score", 0L)) }
    var bestScore by remember { mutableLongStateOf(preferencesManager.getData("best_score", 0L)) }
    var birdOffset by remember { mutableStateOf(0.dp) }
    var birdRect by remember { mutableStateOf(Rect(0f, 0f, 64.dp.value, 64.dp.value)) }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这一部分,我们初始化各种游戏状态变量,如gameState、score、lastScore、bestScore、birdOffset和birdRect。这些变量用于跟踪游戏的进展和鸟的位置。

    1. 管道(障碍物)尺寸初始化
      Pipe Dimensions
    var pipeDimensions by remember {
        mutableStateOf(Triple(0.1f, 0.4f, 0.5f))
    }
    
    • 1
    • 2
    • 3

    在这里,我们将pipeDimensions初始化为Triple,以存储顶部、间隙和底部管道的权重。这些权重确定了管道的相对大小。

    管道(障碍物)尺寸初始化
    通过remember创建一个 mutableStateOf,将 pipeDimensions 初始化为 Triple(0.1f, 0.4f, 0.5f)。这个 Triple 存储了顶部管道、间隙和底部管道的权重,这些权重决定了管道的相对大小。

    1. 更新分数回调
    val updateScoreCallback: (Long) -> Unit = {
        score += it
    }
    
    • 1
    • 2
    • 3

    updateScoreCallback 是一个回调函数,用于在必要时更新游戏的分数。

    1. 小鸟下落动画
      Bird Falling
    LaunchedEffect(key1 = birdOffset, gameState) {
        while (gameState == GameState.PLAYING) {
            delay(16)
            birdOffset += 4.dp
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这部分中,我们使用 LaunchedEffect 来持续更新 birdOffset,并在游戏处于 PLAYING 状态时模拟小鸟的下落。

    1. 更新小鸟和管道的矩形区域
    val updateBirdRect: (birdRect: Rect) -> Unit = {
        birdRect = it
        pipeDimensions = getPipeDimensions(it, screenHeight)
    }
    
    • 1
    • 2
    • 3
    • 4

    这些回调函数负责更新小鸟和管道的矩形区域。这些矩形区域对于检测小鸟和管道之间的碰撞非常重要。

    1. 碰撞检测
      Collision Detection
    val updatePipeRect: (pipeRect: Rect) -> Unit = {
        if (!it.intersect(birdRect).isEmpty) {
            // 处理与管道的碰撞
            // ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个回调函数处理小鸟和管道之间的碰撞检测。当检测到碰撞时,游戏状态转变为 COMPLETED,并更新最高分和最近分数。此外,我们还导航到游戏结束界面(详见导航部分)。

    1. 点击手势处理
    Box(
        modifier = Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                detectTapGestures(
                    onTap = {
                        if (gameState == GameState.PLAYING) {
                            // 处理小鸟跳跃
                            coroutineScope.launch {
                                var offsetChange = 80.dp
                                while (offsetChange > 0.dp) {
                                    birdOffset -= 2.dp
                                    delay(2L)
                                    offsetChange -= 2.dp
                                }
                            }
                        }
                    }
                )
            }
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里,我们设置了点击手势处理。当玩家在游戏过程中点击屏幕时,更新小鸟的位置以模拟跳跃。

    1. 游戏布局
    2. Box Composable —
    Box(
        modifier = Modifier.fillMaxSize()
    ) {
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    游戏布局被封装在一个 Box composable 中,允许在其上方放置多个组件。

    1. 背景 —
    Background()
    
    • 1

    Background composable 渲染游戏的背景,并根据选择的主题设置适当的背景图像。

    1. 管道 —
    Pipes(
        updatePipeRect = updatePipeRect,
        updateScoreCallback = updateScoreCallback,
        gameState = gameState,
        pipeDimensions = pipeDimensions.copy()
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Pipes composable 管理管道的生成和移动。它处理与小鸟的碰撞检测并更新分数。

    1. 游戏状态处理 —
    when (gameState) {
        // ...
    }
    
    • 1
    • 2
    • 3

    这部分使用 when 表达式处理不同的游戏状态 —

    GameState.PLAYING — 在游戏进行中显示小鸟、分数和暂停按钮。点击暂停按钮触发暂停回调。
    GameState.NOT_STARTED, GameState.COMPLETED — 显示“Play”按钮以开始或重新开始游戏。如果有可用的最近分数和最高分,则显示上次分数和最高分。
    GameState.PAUSE — 显示“Play”按钮以恢复游戏。

    1. 小鸟 —
    Bird(birdOffset, updateBirdRect)
    
    • 1

    Bird composable 在屏幕上渲染小鸟角色。birdOffset 决定了小鸟的垂直位置,模拟其移动。

    1. Play 按钮 —
    Play(onPlayCallback)
    
    • 1
    • Play composable 显示“Play”按钮,允许玩家在点击时开始或恢复游戏。按下按钮时触发 onPlayCallback。
    1. 地面 —
    Ground("Flappy Score", score, enablePause = true, onPauseCallback)
    
    • 1

    Ground composable 显示游戏的分数,并在 enablePause 设置为 true 时包含一个可选的暂停按钮。按下暂停按钮时触发 onPauseCallback

    @Composable
    fun Bird(birdOffset: Dp, updateBirdRect: (Rect) -> Unit) {
    
        val bird = when (MaterialTheme.colorScheme.primary) {
            spaceX.primary, spaceXMars.primary, spaceXMoon.primary -> {
                R.drawable.space
            }
    
            twitterDoge.primary -> {
                R.drawable.doge
            }
    
            else -> R.drawable.bird
        }
    
        bird.let {
            Box(
                modifier = Modifier
                    .size(64.dp)
                    .offset(y = birdOffset)
                    .padding(5.dp)
                    .onGloballyPositioned {
                        updateBirdRect(it.boundsInRoot())
                    }
            ) {
                when (MaterialTheme.colorScheme.primary) {
                    spaceX.primary, spaceXMoon.primary, spaceXMars.primary -> {
                        Image(painterResource(id = it), contentDescription = "rocket")
                    }
    
                    else -> {
                        if (MaterialTheme.colorScheme.primary == twitterDoge.primary) {
                            Image(painterResource(id = it), contentDescription = "doge rocket")
                        } else {
                            Icon(
                                painterResource(id = it),
                                tint = MaterialTheme.colorScheme.secondary,
                                contentDescription = "bird"
                            )
                        }
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    深入了解每个组件

    在本节中,我们将深入探讨《Flappy Musketeer》游戏的游戏布局。我们将探索构成游戏界面的以下关键组件:

    1. 小鸟( Bird)

    @Composable
    fun Bird(birdOffset: Dp, updateBirdRect: (Rect) -> Unit) {
    
        val bird = when (MaterialTheme.colorScheme.primary) {
            spaceX.primary, spaceXMars.primary, spaceXMoon.primary -> {
                R.drawable.space
            }
    
            twitterDoge.primary -> {
                R.drawable.doge
            }
    
            else -> R.drawable.bird
        }
    
        bird.let {
            Box(
                modifier = Modifier
                    .size(64.dp)
                    .offset(y = birdOffset)
                    .padding(5.dp)
                    .onGloballyPositioned {
                        updateBirdRect(it.boundsInRoot())
                    }
            ) {
                when (MaterialTheme.colorScheme.primary) {
                    spaceX.primary, spaceXMoon.primary, spaceXMars.primary -> {
                        Image(painterResource(id = it), contentDescription = "rocket")
                    }
    
                    else -> {
                        if (MaterialTheme.colorScheme.primary == twitterDoge.primary) {
                            Image(painterResource(id = it), contentDescription = "doge rocket")
                        } else {
                            Icon(
                                painterResource(id = it),
                                tint = MaterialTheme.colorScheme.secondary,
                                contentDescription = "bird"
                            )
                        }
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    在《Flappy Musketeer》游戏中,Bird 组合负责渲染玩家控制的角色,通常称为“小鸟”,玩家通过控制小鸟穿越管道。让我们详细了解这个组合的关键方面:

    • birdOffset - 这个参数表示小鸟的垂直偏移量,表示其在屏幕上的位置。它根据玩家的输入和重力进行更新,模拟小鸟的飞翔和下落。
    • updateBirdRect - 一个回调函数,用于更新小鸟的位置和尺寸,以进行碰撞检测。

    在composable中

    1. 根据当前主题的主色调,将 bird 变量分配给图像资源。游戏根据所选主题提供不同的小鸟图像,例如“spaceX”、“spaceXMars”、“spaceXMoon”或“twitterDoge”。
    2. 使用 Box 组合来容纳小鸟图像。它有一个固定大小为64x64密度无关像素(dp),并根据 birdOffset 在垂直方向上定位。
    3. 使用 onGloballyPositioned 修饰符来检测小鸟在屏幕上的位置,并使用其边界调用 updateBirdRect 回调函数。

    Box 的内容根据主题而变化

    • 对于太空主题(spaceX、spaceXMars、spaceXMoon),它显示一个带有火箭图像的 Image,代表小鸟。
    • 对于“twitterDoge”主题,它显示一个带有 doge 主题的火箭图像的 Image。
    • 对于其他主题,它显示一个带有小鸟图像的图标,图标的颜色会根据当前主题的次要颜色进行着色。

    这个组合允许根据游戏的主题以不同的方式呈现小鸟角色,为玩家提供与所选主题相匹配的视觉体验。

    2. 管道(障碍物)
    Twitter Pink

    A)主要组合

    管道组件负责在游戏中渲染和管理玩家必须穿过的管道。它还处理生成和移动管道的逻辑,这些都是基于游戏事件的。

    关键点

    updatePipeRect - 一个回调函数,用于更新管道的位置和尺寸,以进行碰撞检测。

    updateScoreCallback - 一个回调函数,用于更新玩家的分数。

    gameState - 表示游戏的当前状态(例如,正在播放,已完成)。

    pipeDimension - 代表顶部,间隙和底部管道的权重的元组。

    管道组件根据游戏状态和经过的时间来管理管道的创建和移动。

    B)管道数据类

    data class Pipe(
    val width:Dp = 100.dp,
    val topPipeWeight:Float,
    val gapWeight:Float,
    val bottomPipeWeight:Float,
    var position:Dp,
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Pipe是表示游戏中单个管道的数据类。它包含诸如宽度,顶部,间隙和底部管道的权重以及其在屏幕上的位置等属性。

    C)管道生成逻辑

    if(System.currentTimeMillis()- PipeTime.lastPipeAddedTime> = 2000L{
    //生成新管道并将其添加到列表中
    //...
    //添加逻辑
    val addToList = if(pipes.isNotEmpty()){
    abs(pipes.last()。position.minus(newPipe.position)。value)> 500f
    } else {
    true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如果满足条件,则添加到列表中。更新得分回调函数会在生成新管道时调用以更新玩家的分数。

    D)管道运动

    //从右到左移动管道
    LaunchedEffect(key1 = pipes.size,gameState){
    while(gameState == GameState.PLAYING){
    delay(16L)
    pipes = pipes.map {pipe->
    val newPosition = pipe.position-pipeSpeed
    pipe.copy(position = newPosition)
    } .filter {pipe->
    pipe.position>-pipeWidth)//从屏幕上删除
    }
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    使用LaunchedEffect将管道从右向左移动。该效果在游戏处于“播放”状态时运行。

    delay(16L)确保以一致的速率移动管道,提供平滑的动画效果。

    通过映射每个管道的位置来更新管道列表,减去管道速度(pipeSpeed)。从屏幕上删除的管道(position < -pipeWidth)将从列表中删除。

    E)GapPipe组合

    @Composable
    fun GapPipe(pipe:Pipe,updatePipeRect:(Rect) - > Unit){
    //...
    }
    
    • 1
    • 2
    • 3
    • 4

    GapPipe组合负责呈现单个管道及其间隙。

    .onGloballyPositioned {
    val pipeRect = it.boundsInRoot()
    updatePipeRect(pipeRect)
    }
    
    • 1
    • 2
    • 3
    • 4

    它接受一个Pipe对象和一个回调函数updatePipeRect以进行碰撞检测。

    F)管道尺寸计算

    fun getPipeDimensions(
    birdPosition:Rect,
    screenHeight:Dp
    ):Triple<Float,Float,Float> {
    //...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • getPipeDimensions函数根据鸟的位置和屏幕高度计算顶部,间隙和底部管道的权重(相对高度)。

    • 它确保生成的管道权重在一定限制范围内,以创建具有挑战性但公平的游戏。

    3. 地面和分数显示

    Flappy Musketeer游戏中的Ground组合负责呈现显示与游戏相关的信息的地面区域。以下是这个组合的说明
    Ground and Score Display

    @Composable
    fun Ground(
    label:String,
    score:Long,
    enablePause:Boolean = false,
    onPauseCallback:() - > Unit = {}{
    //...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    关键点

    • label-表示地面区域的标签或标题的字符串。

    • score-表示玩家得分的长整数。

    • enablePause-一个布尔值,指示是否启用暂停按钮。默认情况下设置为false。

    • onPauseCallback-单击暂停按钮时调用的回调函数。默认情况下,它是一个空函数。

    Ground组合创建一个可视的地面区域,显示标签,得分和可选的暂停按钮。它用于提供与游戏进度相关的信息和交互。

    4. 游戏按钮
    Play Button

    @Composable
    fun Play(onPlayCallback:() - > Unit){
    //...
    }
    
    • 1
    • 2
    • 3
    • 4

    关键点

    • onPlayCallback-此参数是单击播放按钮时将调用的回调函数。通常会触发游戏的开始或重新开始。

    • Play组合创建一个视觉上吸引人的播放按钮,与游戏的主题相匹配。

    结论


    在Flappy Musketeer游戏中,我们开始了一段充满激情的旅程,利用Jetpack Compose的强大功能创建Android移动游戏。我们深入探讨了主题、导航、游戏菜单和游戏屏幕逻辑,剖析每个方面,为您提供打造自己沉浸式游戏体验所需的工具。

    当您踏上游戏开发之旅时,请记住Jetpack Compose为创建视觉上令人惊叹和引人入胜的Android游戏开启了无限可能。因此,前进吧,释放您的创造力,构建出一些酷炫的东西!

    GitHub

    https://github.com/nirbhayph/flappymusketeer/

  • 相关阅读:
    广西建筑模板厂家批发——能强优品木业
    RK3568平台开发系列讲解(AI篇)车辆检测&车道线识别&可行驶区域分割 模型对比检测结果
    【排序】十大排序算法
    SpringBoot-自定义Starter
    132. SAP UI5 Simple Form 控件的使用方法介绍
    面向对象设计模式
    探秘STM32MDK:编译过程与文件类型解析
    kubernetes集群编排——k8s资源监控
    java计算机毕业设计海东市乐都区沙果线上线下销售管理平台MyBatis+系统+LW文档+源码+调试部署
    ch16、面向对象编程-Go语言的相关接口
  • 原文地址:https://blog.csdn.net/u011897062/article/details/134010534