小试牛刀:用Compose完美复刻Flappy Bird!

之前看到fundroid大神用Compose打造了俄罗斯方块游戏,深受启发。便萌生了也打造一个游戏的想法,顺便精进一下Compose的学习。Flappy Bird是13年红极一时的小游戏,其简单有趣的玩法和变态的难度形成了强烈反差,引发全球玩家竞相把玩,欲罢不能!遂选择复刻这个小游戏,在实现的过程中向大家演示Compose工具包的UI组合、数据驱动等重要思想。Ⅰ.拆解游戏不记得这个游戏或完全没玩过的朋友,可以点击下面的链接,体验一下Flappy Bird的玩法。flappybird.io/为拆.
摘要由CSDN通过智能技术生成

之前看到fundroid大神用Compose打造了俄罗斯方块游戏,深受启发。便萌生了也打造一个游戏的想法,顺便精进一下Compose的学习。

Flappy Bird是13年红极一时的小游戏,其简单有趣的玩法和变态的难度形成了强烈反差,引发全球玩家竞相把玩,欲罢不能!遂选择复刻这个小游戏,在实现的过程中向大家演示Compose工具包的UI组合、数据驱动等重要思想。

Ⅰ.拆解游戏

不记得这个游戏或完全没玩过的朋友,可以点击下面的链接,体验一下Flappy Bird的玩法。

flappybird.io/

为拆解游戏,笔者也录了一段游戏过程。

反复观看这段GIF,可以发现游戏的一些规律:

  • 远处的建筑和近处的土壤是静止不动的
  • 小鸟一直在上下移动,伴随着翅膀和身体的飞翔姿态
  • 管道和路面则不断地向左移动,营造出小鸟向前飞翔的视觉效果

通过截图、切图、填充像素和简单的PS,可以拿到各元素的图片。

Ⅱ.复刻画面

各方卡司已就位,接下来开始布置整个画面。暂不实现元素的移动效果,先把静态的整体效果搭建好。

ⅰ.布置远近景

静止不动的建筑远景最为简单,封装到可组合函数FarBackground里,内部放置一张图片即可。

@Composable
fun FarBackground(modifier: Modifier) {
    Column {
        Image(
            painter = painterResource(id = R.drawable.background),
            contentScale = ContentScale.FillBounds,
            contentDescription = null,
            modifier = modifier.fillMaxSize()
        )
    }
}

远景的下面由分割线、路面和土壤组成,封装到NearForeground函数里。通过Modifierfraction参数控制路面和土壤的比例,保证在不同尺寸屏幕上能按比例呈现游戏界面。

@Composable
fun NearForeground(...) {
    Column( modifier ) {
        // 分割线
        Divider(
            color = GroundDividerPurple,
            thickness = 5.dp
        )

        // 路面
        Box(modifier = Modifier.fillMaxWidth()) {
            Image(
                painter = painterResource(id = R.drawable.foreground_road),
                ...
                modifier = modifier
                    .fillMaxWidth()
                    .fillMaxHeight(0.23f)
                )
            }
        }

        // 土壤
        Image(
            painter = painterResource(id = R.drawable.foreground_earth),
           ...
            modifier = modifier
                .fillMaxWidth()
                .fillMaxHeight(0.77f)
        )
    }
}

将整个游戏画面抽象成GameScreen函数,通过Column竖着排列远景和前景。考虑到移动的小鸟和管道需要呈现在远景之上,所以在远景的外面包上一层Box组件。

@Composable
fun GameScreen( ... ) {
    Column( ...  ) {
        Box(modifier = Modifier
            .align(Alignment.CenterHorizontally)
            .fillMaxWidth()
        ) {
            FarBackground(Modifier.fillMaxSize())
        }

        Box(modifier = Modifier
            .align(Alignment.CenterHorizontally)
            .fillMaxWidth()
        ) {
            NearForeground(
                modifier = Modifier.fillMaxSize()
            )
        }
    }
}

ⅱ.摆放管道

仔细观察管道,会发现一些管道具备朝上朝下、高度随机的特点。为此将管道的视图分拆成盖子和柱子两部分:

  • 盖子和柱子的放置顺序决定管道的朝向
  • 柱子的高度则控制着管道整体的高度

这样的话,只使用盖子和柱子两张图片,就可以灵活实现各种形态的管道。

先来组合盖子PipeCover和柱子PipePillar的可组合函数。

@Composable
fun PipeCover() {
    Image(
        painter = painterResource(id = R.drawable.pipe_cover),
        contentScale = ContentScale.FillBounds,
        contentDescription = null,
        modifier = Modifier.size(PipeCoverWidth, PipeCoverHeight)
    )
}

@Composable
fun PipePillar(modifier: Modifier = Modifier, height: Dp = 90.dp) {
    Image(
        painter = painterResource(id = R.drawable.pipe_pillar),
        contentScale = ContentScale.FillBounds,
        contentDescription = null,
        modifier = modifier.size(50.dp, height)
    )
}

管道的可组合函数Pipe可以根据照朝向和高度的参数,组合成对应的管道。

@Composable
fun Pipe( 
    height: Dp = HighPipe,
    up: Boolean = true
) {
    Box( ... ) {
        Column {
            if (up) {
                PipePillar(Modifier.align(CenterHorizontally), height - 30.dp)
                PipeCover()
            } else {
                PipeCover()
                PipePillar(Modifier.align(CenterHorizontally), height - 30.dp)
            }
        }
    }
}

另外,管道都是成对出现、且无论高度如何中间的间距是固定的。所以我们再实现一个管道组的可组合函数PipeCouple

@Composable
fun PipeCouple( ... ) {
    Box(...) {
        GetUpPipe(height = upHeight,
            modifier = Modifier
                .align(Alignment.TopEnd)
        )

        GetDownPipe(height = downHeight,
            modifier = Modifier
                .align(Alignment.BottomEnd)
        )
    }
}

将PipeCouple添加到FarBackground的下面,管道就放置完毕了。

@Composable
fun GameScreen( ... ) {
    Column(...) {
        Box(...) {
            FarBackground(Modifier.fillMaxSize())

            // 管道对添加远景上去
            PipeCouple(
                modifier = Modifier.fillMaxSize()
            )
        }
        ...
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值