前言
项目地址:github 开源地址,码云开源地址
项目背景:刚刚结束的暑假小学期中,和我的两个神仙队友WYX,XPF(我的队友超级棒!)共同完成的第一个安卓开发项目——俄罗斯方块游戏。
框架和语言:kotlin+ jetpack compose (compose 是谷歌最新推出的开发框架,AndroidStudio刚出了支持compose的稳定版本)
参考项目:参考了https://github.com/leavesC/compose_tetris
在此基础上增加了许多丰富的功能(如切换游戏难度,切换背景音乐,记录最高分…)
俄罗斯方块开发文档
应用名称:鹅螺蛳方块
应用类型:休闲游戏
小组成员:WYX、ZYW、XPF
开发时间:2021年7月26日 - 2021年8月14日
1.摘要
本组使用 Android studio 作为集成式开发环境,完全自主学习 Kotlin 编程语言和 Jetpack Compose 框架,编写了一个功能齐全、具有动态界面的俄罗斯方块安卓游戏。
主要工作包括:搭建 Compose Activity 项目环境,全栈开发实现俄罗斯方块的后端游戏逻辑、界面交互、前端界面设计、音效播放、动画效果等功能(详见“设计需求”一节)。
学习使用全新的 Jetpack Compose 框架是本项目最大的挑战。Jetpack Compose 是谷歌推出的新式声明性界面工具包 ,包括一整套全新的渲染、布局、事件、刷新机制,需要一段时间才能入门,但有效提高了本小组的开发效率。
本文档用于描述《计算机科学与技术专业实践与训练》课程所编写程序的设计方案,文档阅读对象为本课程授课教师及本课堂同学。
2.开发工具选取
Jetpack Compose 是用于构建原生界面的最新的 Android 工具包,结合了反应式编程模型和 Kotlin 编程语言的简洁性和易用性。
2.1.Compose 的自身优点
- 采用声明式 UI 设计
- 提供丰富的 Material 组件,加速开发
- 具有直观的 Kotlin API
- 拥有更简单的、自定义的实时交互预览功能
2.2.数据驱动界面
游戏的执行程序可以概括为不断等待用户的输入信息,进行状态查询,渲染界面的过程。而这种游戏模型的逻辑非常符合前端开发的思想:数据驱动界面。

当下的小游戏多以前端为主,客户端开发成本较大,而使用 Compose 可以降低开发成本。Compose 实现数据驱动是依赖类似 Flutter 的 Provide 一样的更新机制,但并没有采用采用了传统 UI 的多层继承结构,而是多个 View 组合成一个 View ,更新数据只要在使用的实体上面加注解 @Model ,在更新实体的同时,会通知 UI 进行修改。
3.设计需求
3.1.功能需求
3.1.1.基本游戏功能
- 实现俄罗斯方块的游戏的基本逻辑:控制方块的运动和旋转
- 实现游戏整体功能控制:开始游戏、暂停游戏、重新开始、音乐开启关闭;
3.1.2.拓展功能
- 实现游戏高级设置:调整游戏难度,游戏界面主题切换,更改游戏背景音乐;
- 增加计分系统,在游戏界面实时显示目前得分,统计历史最高得分;
- 增加玩家档案信息,使玩家可以输入保存自己的姓名;
- 增加游戏说明的信息,使得玩家了解游戏规则和技巧;
- 实现关于我们页面:展示应用版本信息、开发团队的留言、产品设计理念;
3.2.界面需求
3.2.1.整体界面设计
- 设计导航栏使得界面的跳转更加简明快捷。
- 设计垂直滚动式页面增加单个页面展示的空间。
- 设计白天与黑夜两种模式的界面配色,启动应用时会自动根据手机夜间模式切换,界面内包括太阳和月亮的动画。
- 设计多种模式的渐变色按钮,增加按钮的质感以提升玩家的体验。
- 设计多种风格的字体样式以区分不同的内容和突出重点。
- 设计多种矢量图标,使得信息的展现更加形象。
3.2.2.特色界面设计
- 游戏屏幕可由用户自己调节大小,并当调节范围超出最大和最小的界限时,设计弹窗提醒用户。
- 游戏屏幕边框实现色彩的渐变,增添游戏的炫酷感。
- 设计手势控制的旋转圆形头像,可变带宽和模糊度的圆环边框,可变模糊度和曲线形状的顶部背景图片。
- 设计 3D 翻折动画,隐藏和展开游戏说明,使得界面更加简洁灵动。
- 设计水墨渲染动画,通过手势将灰度图片点亮,增添界面的高级感。
- 设计上下两个图层,通过下图层的阴影使得可360°翻转的图片有肉眼3D的效果
- 设计星空背景和火箭发射的动画,更形象的展示我们的理念和深层寓意。
- 设计色彩渐变的字体,是界面更加有趣美观。
- 仿照胶片样式,设计横向滚动的图像卡片,展示开发者留言。
- 设计关键信息可复制的文本,展示项目地址,开发者联系方式。
4.项目文件及其功能
4.1.游戏架构基于 MVI 设计
MVI 即 Model-View-Intent ,提倡一种单向数据流的设计思想,非常适合在 Compose 项目中实现逻辑部分,可以彻底贯彻“数据驱动 UI”的核心思想。

- View 层:基于 Compose 打造,所有 UI 元素都由代码实现
- Model 层:ViewModel 维护 State 的变化,游戏逻辑交由 reduce 处理
- V-M 通信:通过 StateFlow 驱动 Compose 刷新,用户事件由 Action 分发至 ViewModel
4.2.源文件功能简述表
如下表所示,所有源代码部分都在 com.android_tetris 包内:


4.3 功能索引



5.遇到的困难和解决方案
5.1.零基础学习新的语言和框架
本程序完全使用 Kotlin 语言开发,并选用 Jetpack Compose 框架作为前端框架。
我们完全自主学习了 Jetpack Compose (以下简称 Compose )这个还未推出正式版的声明式 UI 框架。由于谷歌暂未推出 Compose 的正式版本,现在网上相关资料和教程都还非常稀少,API文档还是全英文的,我们常常在意想不到的问题上被卡住还没有什么办法。
虽然入门过程极其艰难,但是在学习使用 Compose 的全新框架开发的过程中,我们的英语水平、信息检索能力、解决问题的能力,都得到了极大的锻炼,相信这段艰苦的自主学习经历对我们之后的编程之路一定有所帮助。
5.2.使用 Gitee 托管仓库
由于这是一个工程量比较大的项目,我们采用 Gitee 进行版本管理。但在使用过程中,我们上传经常遇到“文件超过 100M ”的错误提示。
为解决这一问题,我们学习了 .gitignore 文件的使用方法:在该文件按优先级从高到低,写明让 Git 仓库上传时忽略掉的文件目录/后缀名, Git 就会主动忽略这些文件。
我们可以用这一办法处理项目编译产生的文件和庞大的开发环境文件。
5.3.编辑 build.gradle 环境配置文件
开发初期,由于 Kotlin 和 Compose 还在频繁更新 API ,版本迭代很快,因此我们遇到了很多次 build 不成功的情况,报错位置都出现在 build.gradle 文件。
查阅资料发现, Project 层级和 Module 层级各有相对应的两个 build.gradle 文件,在 Android studio 中分别显示为 build.gradle (Project: 项目名) 和 build.gradle (Module: 项目名.app) 。
在 build.gradle (Project: 项目名) 中, buildscript { } 代码块是该项目的 gradle 配置,只存放用到的代码托管库和项目构建级别的依赖:
buildscript {
ext {
compose_version = '1.0.0-beta08'
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.0-alpha02'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
// (此处的注释提示:不要把应用程序的依赖库在此引用)
}
}
在 build.gradle (Module: 项目名.app) 中, build.gradle 主要用于配置模块级别的配置信息和依赖:
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.android_tetris"
minSdk 21
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
// 输出 APK 用到的应用签名信息
signingConfigs {
releaseConfig {
storeFile file("..\\key.jks")
storePassword "123456"
keyAlias "key0"
keyPassword "123456"
v1SigningEnabled true
v2SigningEnabled true
}
}
buildTypes {
debug {
signingConfig signingConfigs.releaseConfig
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
signingConfig signingConfigs.releaseConfig
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion '1.5.10'
}
}
dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
...
}
5.4.用 Kotlin 编写游戏逻辑实现 Model 层次
为遵守 MVI 设计逻辑,对于游戏界面,所有会由游戏内行为发出再导致前端显示刷新的变化,都集中存放在 TetrisState.kt 文件中。界面的所有变化都依赖后端 State 的变化而刷新。
俄罗斯方块共有 7 种形状,每种形状有随着旋转还会有几种不同形态,本项目在 MockData.kt 文件中选用 44 的二维 IntArray 数组储存方块形状,在 TetrisState.kt 中选用 1024 的二维 IntArray 数组表示存放方块的屏幕。
以下是 TetrisState.kt 文件中一部分重要函数和类的代码:
// class Tetris:对于单个俄罗斯方块的 State 状态类
data class Tetris constructor(
val shapes: List<List<Location>>, // 此方块所有可能的旋转结果
val type: Int, // 用于标记当前处于哪种旋转状态
val offset: Location, // 方块相对屏幕左上角的偏移量
) {
... }
// class TetrisState:游戏的 State 状态类
data class TetrisState(
val brickArray: Array<IntArray>, // 屏幕坐标系
val tetris: Tetris, // 下落的方块
val gameStatus: GameStatus = GameStatus.Welcome, // 游戏状态
val soundEnable: Boolean = true, // 是否开启音效
val nextTetris: Tetris = Tetris(), // 下一个方块
var currentScore: Int = infoStorage.currentScore // 当前分数
) {
... }
// class Action:接受用户 View 层次传入信号的 Action 类
sealed class Action {
object Welcome : Action()
object Start : Action()
object Pause : Action()
object Reset : Action()
object Sound : Action()
object Settings : Action()
object Background : Action()
object Resume : Action()
data class Transformation(val transformationType: TransformationType) : Action()
}
// fun combinedPlayListener() 函数:Action 行为的监听器,监听器接收到信号后,分别再发送给 private fun TetrisState.onStart() 等 TetrisState 的私有函数,进行处理
fun combinedPlayListener(
onStart: () -> Unit = {
},
onPause: () -> Unit = {
},
onReset: () -> Unit = {
},
onSound: () -> Unit = {
},
onSettings: () -> Unit = {
},
onTransformation: (TransformationType) -> Unit = {
}
) = PlayListener(
onStart = onStart,
onPause = onPause,
onReset = onReset,
onSound = onSound,
onSett

本文档详细介绍了使用Kotlin和JetpackCompose开发的俄罗斯方块游戏,涵盖项目背景、开发工具选择、设计需求、功能实现、遇到的困难和解决方案。游戏具备完整的功能,如游戏逻辑、界面交互、音效和动画,并实现了数据驱动界面。开发过程中,团队克服了新语言和框架的学习挑战,实现了游戏的开始、暂停、重新开始、音效控制等功能,并设计了丰富的界面元素和动画效果。
1060

被折叠的 条评论
为什么被折叠?



