::: hljs-center
体验者:半个月亮团 学校:中原工学院
:::
本篇文章主要说明的是在大禹200的开发板上,开发的一个弹球小游戏。以此体验大禹200对OpenHarmony操作系统的支持,以及OpenHarmony操作系统上应用开发。
1. 选择设备
目前,能够承载OpenHarmony操作系统的开发板是有一些的,但是作为一穷二白的我想获取一个能用的还是有不少困难的。因此,与其说是选择设备,还不如说是设备没有选择。
荣幸的是大禹体验官活动给了我们机会。所以,无论如何必须插入一条硬广告,广告词“大禹200将引领未来”。 拿到的大禹200的开发板子是下面这个样子的。如图1。
::: hljs-center
图1 大禹200开发板
:::
2. 升级系统
OpenHarmony操作系统的版本更新是非常快的,为了获得更好的体验,拿到大禹200后的第一项工作是升级操作系统。升级系统其实就是把系统重新在开发板上烧录一下。
2.1 烧录前准备
将设备连接电源线,以及USB设备烧写线,如图2所示。
::: hljs-center
图2 连接大禹200
:::
到ci.openharmony.cn网站上选择对应的版本下载固件,http://ci.openharmony.cn/dailys/dailybuilds 如图3。
::: hljs-center
图3 固件下载页面
:::
2.2 烧录步骤
安装 USB 驱动,这个过程比较简单,可以说是傻瓜式安装,这里跳过。
打开瑞芯微开发烧写工具(可以自己下载安装),默认打开的是 Maskrom 模式,如图4所示。
::: hljs-center
图4 烧写工具
:::
如果出现问题,说明需要进入loader模式,先按住recovery不要放,接着按一下reset释放,等待一两秒释放recovery按键,界面会加载出,发现一个LOADER设备,如图5。
::: hljs-center
图5 进入LOADER模式
:::
接下来选择固件进行烧录,如图6。
::: hljs-center
图6 选择固件
:::
点击执行按钮,开始烧写,直到完成,如图7。
::: hljs-center
图7 烧写完成
:::
成功后,重启设备,即可进入新版本的OpenHarmony系统,如图8。
::: hljs-center
图8 OpenHarmony系统
:::
至此,设备和鸿蒙系统都准好了,接下来可以开发我们的游戏了。
3. 创建项目
开发鸿蒙应用需要下载华为提供的DevEco Studio,目前针对OpenHarmony的是DevEco Studio 3.0 Beta3 for OpenHarmony。下载地址为:https://developer.harmonyos.com/cn/develop/deveco-studio/。下载后,运行安装即可。
安装完成DevEco Studio后,启动该集成开发环境,并通过向导创建项目。本弹球小游戏的项目结构如下:
::: hljs-center
图9 项目结构
:::
其中,entry:OpenHarmony工程模块,编译构建生成一个Hap包。
src > main > js:用于存放js源码。
src > main > js > MainAbility:应用/服务的入口。
src > main > js > MainAbility > i18n:用于配置不同语言场景资源内容,比如应用文本词条、图片路径等资源。
src > main > js > MainAbility > pages:MainAbility包含的页面。
src > main > js > MainAbility > app.js:承载Ability生命周期。
src > main > resources:用于存放应用/服务所用到的资源文件,如图形、多媒体、字符串、布局文件等。
base>element:包括字符串、整型数、颜色、样式等资源的json文件。每个资源均由json格式进行定义。
base>media:多媒体文件,如图形、视频、音频等文件,支持的文件格式包括:.png、.gif、.mp3、.mp4等。
src > main > config.json:模块配置文件,主要包含HAP包的配置信息、应用在具体设备上的配置信息以及应用的全局配置信息。
entry > build-profile.json5:当前的模块信息、编译信息配置项,包括buildOption、targets配置等。
entry > hvigorfile.js:模块级编译构建任务脚本,开发者可以自定义相关任务和代码实现。
build-profile.json5:应用级配置信息,包括签名、产品配置等。
hvigorfile.js:应用级编译构建任务脚本。
需要说明的是,针对本文实现的打砖块小游戏,这里多数文件不用修改。
4. 具体实现弹球游戏
4.1 游戏效果
打砖块小游戏主要有两个界面,第一个是进入游戏界面,第一个是玩游戏界面。如图10、11所示。
::: hljs-center
图10 进入界面
图11 玩游戏
:::
进入界面的功能实现在项目的index目录中,包括index.css、index.hml和index.js三个文件。
玩游戏的功能及控制功能实现在项目的second目录中,包括second.css、second.hml和second.js三个文件。如图12。
::: hljs-center
图12 项目功能实现文件
:::
在基于JS的鸿蒙应用中,可以有多个页面(Pages),每一个页面有三个文件构成,分别是样式文件(css)、页面文件(hml)、脚本代码(js)。
这里的打砖块游戏,第一个界面(图10)功能比较简单,主要是一个响应进入游戏按钮,第二界面(图11)为玩游戏界面,控制游戏过程,稍微复杂。这里重点说一下游戏控制基本思路。
4.2 游戏思路
图11上半部分有一个放置砖块盒子,砖块在second.hml文件中创建,在second.js文件中根据其id名取出进行操作;下方放置小球和滑块并设置相应样式;底部防止两个按键“开始游戏”和“再来一局”。
砖块是通过div表示的,由样式定义砖块的大小和颜色等。当判断出小球碰撞到砖块时,将砖块的visibility样式变为隐藏,达到砖块消失的效果,即砖块被打掉。
当按下开始游戏按键时会触发小球运动的函数,采取每20毫秒返回一次的数据来定位小球的实时位置,从而达到小球运动轨迹的显现。小球在碰到砖块时会把砖块打掉并反弹,在碰到左、上、右边墙壁时会反弹,在碰到滑块时也会反弹,掉到底部则游戏结束。
触摸滑块时会有三种事件被触发。开始触摸时,会提示“开始拖动”;拖拽过程中会使滑块跟随手指一起移动;结束拖拽时,会提示“拖动结束”。
4.3 实现基本过程
1) 创建项目
2) 创建page
3) 实现代码
4) 调试运行
对于1)创建项目基本过程是打开DevEco Studio -> 选择创建项目(Create Project) -> 选择模板 -> 点击Next。如图13所示。
::: hljs-center
图13 创建项目
:::
接着进入配置项目页面(Configure Your Project),输入项目名称(Project Name)等基本信息,由于这里采用的JS开发的,因此语言选择JS,最后点击完成(Finish)按钮即可完成项目的创建。如图14所示。
::: hljs-center
图14 填写项目基本信息
:::
创建好的项目如图15所示,默认已经建立好了项目的基本结构,且已经创建了一个默认的起始页(index),不过代码还需要修改。
::: hljs-center
图15 创建的默认项目
:::
接下来创建第二个界面(second),把鼠标发到pages上,点击右键,选择New -> Page,如图16所示。
::: hljs-center
图16 创建Page
:::
在弹出的如图17所示的对话框中输入名称,然后点击完成(Finish)即可创建第二个界面(second)的基本代码文件结构。
::: hljs-center
图17 输入Page名称
:::
至此,基本的项目文件基本结构已经创建成功,接下来需要实现玩游戏的逻辑了。
::: hljs-center
![]() |
![]() |
---|---|
图1 进入界面 | 图2 玩游戏 |
:::
进入界面的功能实现在项目的index目录中,包括index.hml、index.css和index.js三个文件。玩游戏的功能及控制功能实现在项目的second目录中,包括second.hml、second.css和second.js三个文件。
1 进入页面实现
1.1 index.hml
```html/xml
<div class="container">
<text class="title">
弹砖块
</text>
<input class="btn" type="button" value="进入游戏" onclick="onclick">
</input>
</div>
### 1.1 index.css
```html/xml
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
.title {
font-size: 60px;
text-align: center;
width: 100%;
height: 40%;
margin: 10px;
}
.btn {
font-size: 60px;
font-weight: bold;
text-align: center;
width: 40%;
height: 8%;
margin-top: 20px;
}
@media screen and (device-type: phone) and (orientation: landscape) {
.title {
font-size: 60px;
}
}
@media screen and (device-type: tablet) and (orientation: landscape) {
.title {
font-size: 100px;
}
}
1.1 index.js
import router from '@system.router';
export default {
data: {
title: ""
},
onInit() {
this.title = this.$t('strings.world');
},
onclick: function(){
router.push({
uri: "pages/second/second"
})
}
}
2 玩游戏实现
2.1 second.hml
```html/xml
<div class="container">
<div class="brickBox" id="brickBox">
<div class="brick" id="brick1" style="visibility: visible;">
</div>
<div class="brick" id="brick2" style="visibility: visible;">
</div>
<div class="brick" id="brick3" style="visibility: visible;">
</div>
<div class="brick" id="brick4" style="visibility: visible;">
</div>
<div class="brick" id="brick5" style="visibility: visible;">
</div>
<dialog id="hintDialog" style="margin-bottom: 50%;">
<div class="dialog-div">
<div class="inner-txt">
<text class="txt">弹砖块</text>
</div>
<text class="text">游戏结束</text>
<div class="inner-btn">
<button type="text" value="确定" onclick="sethintDialog" class="btn-txt"></button>
</div>
</div>
</dialog>
</div>
<div class="ball" id="ball"></div>
<div class="slider" id="slider" ondragstart="dragstart" ondrag="drag" ondragend="dragend" style="position: absolute;left:{{left}};" ></div>
<div class="Top">
<button class="buttons" onclick="Rebound">
开始游戏
</button>
<button class="buttons" onclick="restart" >
再来一局
</button>
</div>
</div>
### 2.2 second.css
```html/xml
.container {
background-color: bisque;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
.buttons {
margin-top: 15px;
width: 45%;
height: 80px;
text-align: center;
font-size: 60px;
border-radius: 10px;
background-color: chocolate;
}
.title {
font-size: 13px;
margin-top: 60px;
margin-left: 20px;
color: grey;
}
.Top {
width: 100%;
height: 100px;
position: relative;
background-color: chocolate;
}
.brickBox {
width: 100%;
height: 100%;
display: flex;
justify-content: space-around;
align-items: center;
background-color: burlywood;/**自动换行**/
flex-wrap: wrap;
}
.brick {
width: 20%;
height: 60px;
background-color: brown;
border: 3px solid black;
}
.ball {
width: 50px;
height: 50px;
background-color: darkgreen;
border-radius: 250px;
position: absolute;
bottom: 200px;
left: 50%;
}
.slider {
width: 30%;
height: 50px;
background-color: cadetblue;
position: absolute;
left: 50%;
bottom: 150px;
}
.dialog-div {
flex-direction: column;
align-items: center;
}
.inner-txt {
width: 80%;
height: 100px;
align-items: center;
flex-direction: column;
justify-content: space-around;
}
.inner-btn {
width: 80%;
height: 100px;
align-items: center;
justify-content: space-around;
}
.txt {
font-size: 18px;
color: #000000;
font-weight: bold;
}
.text {
font-size: 16px;
}
2.3 second.js
import prompt from '@system.prompt';
export default {
data: {
left: 250,
},
onInit() {
},
drag(e) {
this.left = e.globalX;
},
// 实现小球上下左右不断移动
// 小球反弹(速度*-1)
Rebound() {
var brickBox = this.$element('brickBox');
var ball = this.$element('ball');
var slider = this.$element('slider');
var brick1 = this.$element('brick1');
var brick2 = this.$element('brick2');
var brick3 = this.$element('brick3');
var brick4 = this.$element('brick4');
var brick5 = this.$element('brick5');
var hintDialog = this.$element('hintDialog');
// 小球运动
var interId = null;
var speedX = this.getRandom(5, 10);
var speedY = -this.getRandom(5, 10);
interId = setInterval(function () {
// 设定x轴方向的移动速度
var lf = ball.getBoundingClientRect().left + speedX;
// 设定y轴方向的移动速度
var tp = ball.getBoundingClientRect().top + speedY;
// 碰撞销毁砖块
// if进行判断,判断小球与砖块接触
if ((lf + ball.getBoundingClientRect().width / 2) >= brick1.getBoundingClientRect().left && (lf + ball.getBoundingClientRect().width / 2) <= (brick1.getBoundingClientRect().left + brick1.getBoundingClientRect().width) && (brick1.getBoundingClientRect().top + brick1.getBoundingClientRect().height) >= ball.getBoundingClientRect().top) {
brick1.setStyle(
"visibility", "hidden"
)
// Y轴的移动速度
speedY = 5;
}
if ((lf + ball.getBoundingClientRect().width / 2) >= brick2.getBoundingClientRect().left && (lf + ball.getBoundingClientRect().width / 2) <= (brick2.getBoundingClientRect().left + brick2.getBoundingClientRect().width) && (brick2.getBoundingClientRect().top + brick2.getBoundingClientRect().height) >= ball.getBoundingClientRect().top) {
brick2.setStyle(
"visibility", "hidden"
)
// Y轴的移动速度
speedY = 5;
}
if ((lf + ball.getBoundingClientRect().width / 2) >= brick3.getBoundingClientRect().left && (lf + ball.getBoundingClientRect().width / 2) <= (brick3.getBoundingClientRect().left + brick3.getBoundingClientRect().width) && (brick3.getBoundingClientRect().top + brick3.getBoundingClientRect().height) >= ball.getBoundingClientRect().top) {
brick3.setStyle(
"visibility", "hidden"
)
// Y轴的移动速度
speedY = 5;
}
if ((lf + ball.getBoundingClientRect().width / 2) >= brick4.getBoundingClientRect().left && (lf + ball.getBoundingClientRect().width / 2) <= (brick4.getBoundingClientRect().left + brick4.getBoundingClientRect().width) && (brick4.getBoundingClientRect().top + brick4.getBoundingClientRect().height) >= ball.getBoundingClientRect().top) {
brick4.setStyle(
"visibility", "hidden"
)
// Y轴的移动速度
speedY = 5;
}
if ((lf + ball.getBoundingClientRect().width / 2) >= brick5.getBoundingClientRect().left && (lf + ball.getBoundingClientRect().width / 2) <= (brick5.getBoundingClientRect().left + brick5.getBoundingClientRect().width) && (brick5.getBoundingClientRect().top + brick5.getBoundingClientRect().height) >= ball.getBoundingClientRect().top) {
brick5.setStyle(
"visibility", "hidden"
)
// Y轴的移动速度
speedY = 5;
}
//lf:x tp:y
if (lf < 0) {
speedX = -speedX;
}
if (lf >= (brickBox.getBoundingClientRect().width - ball.getBoundingClientRect().width)) {
speedX = -speedX;
}
if (tp <= 0) {
speedY = 5;
} else if ((ball.getBoundingClientRect().top + ball.getBoundingClientRect().height) >= slider.getBoundingClientRect().top && (ball.getBoundingClientRect().left + ball.getBoundingClientRect().width / 2) >= slider.getBoundingClientRect().left && (ball.getBoundingClientRect().left + ball.getBoundingClientRect().width / 2) <= (slider.getBoundingClientRect().left + slider.getBoundingClientRect().width)) {
speedY = -5;
} else if (ball.getBoundingClientRect().top >= slider.getBoundingClientRect().top) {
// 游戏结束
// 弹框提示游戏该结束
hintDialog.show();
// 清除间隔
clearInterval(interId);
}
//实时改变小球位置
ball.setStyle("left", lf + "px")
ball.setStyle("top", tp + "px")
}, 20)
},
// 封装获取随机数的函数
getRandom(a, b) {
var max = Math.max(a, b);
var min = Math.min(a, b)
return Math.floor(Math.random() * (max - min + 1)) + min;
},
//关闭弹窗
sethintDialog(e) {
this.$element('hintDialog').close()
},
//拖拽结束
dragend(e) {
prompt.showToast({
message: '拖动结束'
})
},
//拖拽开始
dragstart(e) {
prompt.showToast({
message: '开始拖动'
})
},
//再来一局
restart() {
var slider = this.$element('slider');
var ball = this.$element('ball');
var brick1 = this.$element('brick1');
var brick2 = this.$element('brick2');
var brick3 = this.$element('brick3');
var brick4 = this.$element('brick4');
var brick5 = this.$element('brick5');
slider.setStyle("left", 50 + "%");
slider.setStyle("bottom", 150 + "px")
ball.setStyle("left", 50 + "%")
ball.setStyle("bottom", 200 + "px")
brick1.setStyle(
"visibility", "visible"
)
brick2.setStyle(
"visibility", "visible"
)
brick3.setStyle(
"visibility", "visible"
)
brick4.setStyle(
"visibility", "visible"
)
brick5.setStyle(
"visibility", "visible"
)
this.Rebound();
}
}
3 玩游戏
下面通过动画看一下玩的效果,请看:
::: hljs-center
![]() |
![]() |
---|
:::
4 问题
尽管本文给大家展示了一个可以玩的弹球游戏,但是还有好多问题直到思考和进一步体验。
1)如何实现多关? 当完成一关后进入下一关,这样游戏就更好玩了。。。
2)如何实现实现砖块的动态产生?针对砖块动态产生,本次体验也尝试了一些方法但是没有成功。。。
3)如何使得游戏更加流畅?这个是一个值得进一步实验的问题,涉及到硬件和算法。。。 <br>
最后,需要说明的是目前该游戏还有很多不足,还可以继续改进完善。。。
想了解更多关于开源的内容,请访问:
51CTO 开源基础软件社区
https://ost.51cto.com/#bkwz