1. 項目的背景
Flappy Bird是一款2013年發(fā)布并在2014年特別流行的一款橫向卷抽游戲。玩家控制一只鳥,試圖在綠色管道之間飛行而不撞到它們。
因為OpenHarmony具備多設(shè)備的窗口框架能力,可以支持不同設(shè)備類型的圖形界面的靈活性。今天我們就一起看看如何能用OpenHarmony學習做個FlappyBird。本文中引用的圖片資源均來自與Github。游戲的效果如下:
項目源碼如下:
https://gitee.com/wshikh/ohosflappybird
2. HAP應用建立2.1HAP簡介
HAP文件是在OpenHarmony系統(tǒng)下編譯生成的可執(zhí)行文件。HAP 包是由代碼、資源、第三方庫以及應用配置文件打包生成的模塊包,主要分為兩種類型:entry 和 feature。
OpenHarmony 用戶應用程序包可以只包含一個基礎(chǔ)的 entry 包,也可以包含一個基礎(chǔ)的 entry 包和一個或多個功能型的 feature 包。
entry:應用的主模塊,作為 OpenHarmony 應用的入口,提供了應用的基礎(chǔ)功能。
feature:應用的動態(tài)特性模塊,作為應用能力的擴展,可以根據(jù)用戶的需求和設(shè)備的類型進行選擇性安裝。
2.2HAP開發(fā)工具
本開發(fā)采用:DevEco Studio 3.0 Beta4
HUAWEI DevEco Studio For OpenHarmony是基于IntelliJ IDEA Community開源版本打造,面向OpenHarmony全場景多設(shè)備的一站式集成開發(fā)環(huán)境(IDE),DevEco Studio 3.0支持在HarmonyOS 3.0 Beta版上開發(fā)應用及服務,并已適配ArkUI聲明式編程范式、ArkCompiler方舟編譯,同時提供低代碼開發(fā)、雙向預覽、全新構(gòu)建工具、模擬器、調(diào)試調(diào)優(yōu)、信息中心等功能,為開發(fā)者提供工程模板創(chuàng)建、開發(fā)、編譯、調(diào)試、發(fā)布等E2E的OpenHarmony應用/服務開發(fā)。
下載鏈接:
https://developer.harmonyos.com/cn/develop/deveco-studio#download_beta
2.3ETS
ETS是基于TS擴展的聲明式開發(fā)范式的方舟開發(fā)框架是一套開發(fā)極簡、高性能、跨設(shè)備應用的UI開發(fā)框架,支持開發(fā)者高效的構(gòu)建跨設(shè)備應用UI界面。
基于TS擴展的聲明式開發(fā)范式提供了一系列基礎(chǔ)組件,這些組件以聲明方式進行組合和擴展來描述應用程序的UI界面,并且還提供了基本的數(shù)據(jù)綁定和事件處理機制,幫助開發(fā)者實現(xiàn)應用交互邏輯。
//用@Entry裝飾的自定義組件用作頁面的默認入口組件,也可以理解為頁面的根節(jié)點。 一個頁面有且僅能有一個@Entry,只有被@Entry修飾的組件或者其子組件,才會在頁面上顯示。
//@Component裝飾的struct表示該結(jié)構(gòu)體具有組件化能力,能夠成為一個獨立的組件,這種類型的組件也稱為自定義組件,在build方法里描述UI結(jié)構(gòu)。
struct Hello { //在聲明式UI中,所有的頁面都是由組件構(gòu)成。組件的數(shù)據(jù)結(jié)構(gòu)為struct
string = 'World'
myText: build() { //build函數(shù)用于定義組件的聲明式UI描述,在build方法中以聲明式方式進行組合自定義組件或系統(tǒng)內(nèi)置組件。
Column() { //Column:沿垂直方向布局的容器。
Text('Hello') //Text:顯示一段文本的組件。
.fontSize(30)
Text(this.myText)
.fontSize(32)
Divider() //Divider:提供分隔器組件,分隔不同內(nèi)容塊/內(nèi)容元素。
Button() { //Button:按鈕組件,可快速創(chuàng)建不同樣式的按鈕,通常用于響應用戶的點擊操作。
Text('Click me')
.fontColor(Color.Red)
}.onClick(() => {
this.myText = 'UI'
})
.width(500)
.height(200)
}
}
}
2.4HAP基本概念
-
裝飾器:方舟開發(fā)框架定義了一些具有特殊含義的裝飾器,用于裝飾類、結(jié)構(gòu)、方法和變量。裝飾器就是某一種修飾,給被裝飾的對象賦予某一種能力,比如@Entry就是頁面入口的能力,@Component就是組件化能力。
-
自定義組件:可重用的UI單元,可以與其他組件組合,如@Component裝飾的struct Hello。
-
UI描述:聲明性描述UI結(jié)構(gòu),例如build()方法中的代碼塊。
-
內(nèi)置組件:框架中默認內(nèi)置的基本組件和布局組件,開發(fā)者可以直接調(diào)用,僅用于解釋UI描述規(guī)范。如Column、Text、Divider、Button等。
-
屬性方法:用于配置組件屬性,如fontSize()、width()、height()、color()等。
-
事件方法:在事件方法的回調(diào)中添加組件響應邏輯。例如,為Button組件添加onClick方法,在onClick方法的回調(diào)中添加點擊響應邏輯。
2.5page文件
基于TS擴展的聲明式開發(fā)范式提供了一系列基礎(chǔ)組件,如:基礎(chǔ)組件,容器組件,媒體組件,繪制組件和畫布組件等,本節(jié)我們主要使用畫布組件。
這里我們就不贅述Hap項目的建立過程,以下就是基礎(chǔ)的Hap的page文件:index.ets
build() {
Row() {
Column() {
Canvas(this.context)
.width('100%')
.height('100%')
.onClick((ev: ClickEvent) => {
console.info("click!!")
//響應鼠標左擊
this.doClick()
})
.onReady(() =>{
//繪制基礎(chǔ)
this.context.imageSmoothingEnabled = false
this.drawBlock()
})
}
.width('100%')
}
.height('100%')
.backgroundImage($r("app.media.backgroundday"))
.backgroundImageSize(ImageSize.Cover)
}
build是基礎(chǔ)頁面的構(gòu)造函數(shù),用于界面的元素構(gòu)造,其他的頁面的生命周期函數(shù)如下:
declare class CustomComponent {
/**
* Customize the pop-up content constructor.
* @since 7
*/
build(): void;
/**
* aboutToAppear Method
* @since 7
*/
aboutToAppear?(): void;
/**
* aboutToDisappear Method
* @since 7
*/
aboutToDisappear?(): void;
/**
* onPageShow Method
* @since 7
*/
onPageShow?(): void;
/**
* onPageHide Method
* @since 7
*/
onPageHide?(): void;
/**
* onBackPress Method
* @since 7
*/
onBackPress?(): void;
}
3. Canvas畫布介紹canvas是畫布組件用于自定義繪制圖形,具體的API頁面如下:
https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-components-canvas-canvas-0000001333641081
頁面顯示前會調(diào)用aboutToAppear()函數(shù),此函數(shù)為頁面生命周期函數(shù)。
canvas組件初始化完畢后會調(diào)用onReady()函數(shù),函數(shù)內(nèi)部實現(xiàn)小游戲的初始頁面的繪制。
3.1初始化頁面數(shù)據(jù)
drawBlock() {
this.context.clearRect(0,0,this.context.width,this.context.height)
this.context.drawImage( this.baseImg,this.baseX,this.baseY,500,300)
switch(this.flappyState) {
case 0:
this.context.drawImage( this.messageImg,this.startX,this.startY,300,500)
this.drawBird()
break;
case 1:
this.drawBird()
this.context.drawImage( this.pipegreenImg,this.pipeX,this.pipeY,50,150)
break;
case 2:
this.context.drawImage( this.gameoverImg,this.startX,this.startY*3,300,90)
break
}
}
頁面狀態(tài)有三:
-
0:等待開始界面
-
1:游戲進行
-
2:游戲結(jié)束
3.2繪制Bird
drawBird() {
switch(this.birdType) {
case 0:
this.context.drawImage( this.midbirdImg,this.slotX,this.slotY,this.birdH,this.birdW)
break
case 1:
this.context.drawImage( this.upbirdImg,this.slotX,this.slotY,this.birdH,this.birdW)
break;
case 2:
this.context.drawImage( this.downbirdImg,this.slotX,this.slotY,this.birdH,this.birdW)
break;
default:
break;
}
}
小鳥飛行狀態(tài)有三種:
-
翅膀在中間:0
-
翅膀在上:1
-
翅膀在下:2
4.1 主體游戲邏輯:
簡單的小游戲主體游戲邏輯為:等待開始,開始,結(jié)束流程圖如下:
graph LR
等待開始 --> click[點擊]
click[點擊] --> 游戲開始
游戲開始 --> 點擊 --> |游戲開始|小鳥飛,水管動 --> |小鳥碰到水管| 游戲結(jié)束 --> 點擊 --> |游戲結(jié)束| 等待開始
小鳥飛,水管動 --> |小鳥沒碰到水管| 游戲繼續(xù) --> 點擊
doClick() {
switch (this.flappyState) {
case 0:
{
// 開始
this.flappyState = 1
break
}
case 1:
{
//上下飛
// this.flappyState = 2
this.slotY -= this.flyHeight
console.log(this.slotY.toString())
break
}
case 2:
{
//由結(jié)束到待開始
this.flappyState = 0
this.slotY = this.slotStartY
this.pipeX = this.pipeStartX
break
}
default:
break
}
this.drawBlock()
}
4.2 完整游戲邏輯:
struct Index {
'Hello World'
message: string = private baseImg:ImageBitmap = new ImageBitmap("common/images/base.png")
private messageImg:ImageBitmap = new ImageBitmap("common/images/message.png")
private zeroImg:ImageBitmap = new ImageBitmap("common/images/0.png")
private gameoverImg:ImageBitmap = new ImageBitmap("common/images/gameover.png")
private upbirdImg:ImageBitmap = new ImageBitmap("common/images/bluebirdupflap.png")
private midbirdImg:ImageBitmap = new ImageBitmap("common/images/bluebirdmidflap.png")
private downbirdImg:ImageBitmap = new ImageBitmap("common/images/bluebirddownflap.png")
private pipegreenImg:ImageBitmap = new ImageBitmap("common/images/pipegreen.png")
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private flappyState: number = 0
private startX = 30;
private startY = 100;
private slotStartY = 410;
private slotX = 50;
private slotY = this.slotStartY;
private baseX = 0;
private baseY = 650;
private pipeStartX = 330;
private pipeX = this.pipeStartX;
private pipeY = 500;
private birdH = 60;
private birdW = 50;
private birdTimer: number;
private birdType: number = 0;
private count = 1;
private flyHeight = 20;
private pipeMove = 10;
drawBird() {
switch(this.birdType) {
case 0:
this.context.drawImage( this.midbirdImg,this.slotX,this.slotY,this.birdH,this.birdW)
break
case 1:
this.context.drawImage( this.upbirdImg,this.slotX,this.slotY,this.birdH,this.birdW)
break;
case 2:
this.context.drawImage( this.downbirdImg,this.slotX,this.slotY,this.birdH,this.birdW)
break;
default:
break;
}
}
drawBlock() {
this.context.clearRect(0,0,this.context.width,this.context.height)
this.context.drawImage( this.baseImg,this.baseX,this.baseY,500,300)
switch(this.flappyState) {
case 0:
this.context.drawImage( this.messageImg,this.startX,this.startY,300,500)
this.drawBird()
break;
case 1:
this.drawBird()
this.context.drawImage( this.pipegreenImg,this.pipeX,this.pipeY,50,150)
break;
case 2:
this.context.drawImage( this.gameoverImg,this.startX,this.startY*3,300,90)
break
}
}
doClick() {
switch (this.flappyState) {
case 0:
{
// 開始
this.flappyState = 1
break
}
case 1:
{
//上下飛
// this.flappyState = 2
this.slotY -= this.flyHeight
console.log(this.slotY.toString())
break
}
case 2:
{
//由結(jié)束到待開始
this.flappyState = 0
this.slotY = this.slotStartY
this.pipeX = this.pipeStartX
break
}
default:
break
}
this.drawBlock()
}
doFly(): void {
console.log("dofly ------ !!")
this.birdType += 1
if (this.birdType/5 == 0) {
this.message = "dofly ---555--- !!"
}
}
async sleep(ms: number) {
return new Promise((r) => {
setInterval(() => {
this.birdType += 1
this.message = this.birdType.toString()
if (this.birdType == 3) {
this.birdType = 0
}
console.log(this.message)
if (this.flappyState == 1) {
this.pipeX -= this.pipeMove
if (this.pipeX < 0) {
this.pipeX = 330
}
this.slotY += this.flyHeight/5
}
if ((((this.pipeX-this.slotX) <= this.birdW) && ((this.pipeY-this.slotY) <= this.birdH)) ||
this.pipeY >= this.baseY) {
this.flappyState = 2
}
this.drawBlock()
}, ms)
})
}
aboutToDisappear() {
}
aboutToAppear() {
this.sleep(200)
}
build() {
Row() {
Column() {
Canvas(this.context)
.width('100%')
.height('100%')
.onClick((ev: ClickEvent) => {
console.info("click!!")
this.doClick()
})
.onReady(() =>{
this.context.imageSmoothingEnabled = false
this.drawBlock()
})
}
.width('100%')
}
.height('100%')
.backgroundImage($r("app.media.backgroundday"))
.backgroundImageSize(ImageSize.Cover)
}
}
5. 游戲的瑕疵-
水管只在下層顯示:可以在上層顯示;
-
地面沒有讓動
-
游戲聲音問題:目前ohos不支持音頻播放資源音頻,看之后版本是否支持
-
DevEcoy用setInterval重繪canvas會導致ide崩潰
本文完
寫在最后我們最近正帶著大家玩嗨OpenHarmony。如果你有好玩的東東,歡迎投稿,讓我們一起嗨起來!有點子,有想法,有Demo,立刻聯(lián)系我們:合作郵箱:zzliang@atomsource.org
原文標題:玩嗨OpenHarmony:基于OpenHarmony的小游戲:一起學做FlappyBird
文章出處:【微信公眾號:開源技術(shù)服務中心】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
-
開源技術(shù)
+關(guān)注
關(guān)注
0文章
389瀏覽量
7978 -
OpenHarmony
+關(guān)注
關(guān)注
25文章
3732瀏覽量
16452
原文標題:玩嗨OpenHarmony:基于OpenHarmony的小游戲:一起學做FlappyBird
文章出處:【微信號:開源技術(shù)服務中心,微信公眾號:共熵服務中心】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論