HarmonyOS基于LYEVK-3861開(kāi)發(fā)童年游戲之貪吃蛇
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
介紹
在LYEVK-3861開(kāi)發(fā)板套件中,有1個(gè)OLED屏幕擴(kuò)展板,帶按鍵的照明板,本次我們用這2個(gè)擴(kuò)展板實(shí)現(xiàn)一個(gè)簡(jiǎn)易的貪吃蛇小游戲。由于現(xiàn)有的板子資源有限,綜合考慮,計(jì)劃用OLED屏幕顯示游戲運(yùn)行界面,OLED擴(kuò)展板和照明板上的按鍵復(fù)用為游戲選擇和游戲控制方向鍵。

OLED屏幕為128*64的點(diǎn)陣,使用I2C接口
OLED擴(kuò)展板上的按鍵 用于游戲難度的選擇和游戲開(kāi)始的方向控制(向左)
照明板主要是使用板子上的按鍵,按鍵用于整個(gè)游戲中的確認(rèn)操作和游戲運(yùn)行過(guò)程中的方向控制(向右)
效果演示
環(huán)境準(zhǔn)備
1、開(kāi)發(fā)環(huán)境、編譯環(huán)境搭建,參考官方文檔,此處不在贅述。參考鏈接如下:
2、OpenHarmony 2.0 Canary源碼 源碼獲取,參考:
3、LYEVK-3861 IoT物聯(lián)網(wǎng)開(kāi)發(fā)板套件
OLED模塊與SSD1306
顯示原理

板子上的OLED屏幕約為0.96寸,顯存大小128*64,分8個(gè)頁(yè),PAGE0~PAGE7,128列。
OLED與方塊
方塊點(diǎn)陣定義
貪吃蛇的蛇身采用■方塊表示,現(xiàn)有的板子的OLED的驅(qū)動(dòng)庫(kù)里是沒(méi)有■的點(diǎn)陣定義的,使用PCtoLCD2002這個(gè)工具可以生成■的字符點(diǎn)陣定義,可以按照自己的需要生成指定大小的點(diǎn)陣,我們這里使用8*8的點(diǎn)陣,取模結(jié)果如下:

按照如圖所示的步驟,實(shí)現(xiàn)對(duì)方塊的取模。實(shí)際測(cè)試過(guò)程中,發(fā)現(xiàn)使用原始的取模結(jié)果,組成完整蛇的身體的過(guò)程中,每個(gè)方塊之間的間隙比較大,顯示效果不是很好。我們對(duì)取模結(jié)果做了修改,最后的方塊的8*8點(diǎn)陣表示如下:
- /*---8*8 點(diǎn)陣*/
- static const unsigned char F8X8[]=
- {
- //0x00,0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x00,
- 0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x00, //■
- };
OLED顯示方塊
關(guān)于方塊的顯示和消除,我們使用自己定義的函數(shù)來(lái)顯示整個(gè)方塊,這函數(shù)需要指定方塊的起始位置,整個(gè)方塊的處理不影響屏幕其他區(qū)域的顯示
- /*打印1個(gè)方塊或者消除方塊*/
- void Bar(uint8 x0, uint8 y0, DisOnOff onOff)
貪吃蛇
有了以上的一些基礎(chǔ),我們就可以根據(jù)我們LYEVK-3861的開(kāi)發(fā)板來(lái)設(shè)計(jì)一個(gè)簡(jiǎn)短貪吃蛇小游戲了。
貪吃蛇游戲基本定義
按鍵復(fù)用
1. OLED擴(kuò)展板上的按鍵功能:
①游戲主界面,點(diǎn)擊按鈕,可選擇不同的難度;
② 游戲界面,功能為控制蛇頭左轉(zhuǎn);
2. 照明板上的按鍵功能:
①游戲主界面、游戲結(jié)束界面、游戲通過(guò)界面,確認(rèn)操作,界面跳轉(zhuǎn)
②游戲界面,功能為控制蛇頭右轉(zhuǎn)。
主界面
顯示游戲的名字、游戲的可選難度(1、2、3,數(shù)字越大難度越大),按鍵功能選擇
游戲界面
使用整個(gè)屏幕128x64作為游戲可運(yùn)行的界面,蛇由方塊(8x8的點(diǎn)陣)組成,方塊充滿(mǎn)整個(gè)界面需要16*8個(gè)方塊。
游戲規(guī)則定義
- 每次移動(dòng)1個(gè)長(zhǎng)度單位(勻速,不同難度移動(dòng)速度不同)
- 通過(guò)按鍵,方向可以改變?yōu)樯咔斑M(jìn)方向的左邊或右邊
- 隨機(jī)生成食物
- 吃到食物長(zhǎng)度+1
- 碰到墻壁或身體結(jié)束游戲(失敗)
- 長(zhǎng)度達(dá)到最長(zhǎng)長(zhǎng)度結(jié)束游戲(成功)
貪吃蛇基本算法設(shè)計(jì)
蛇的定義
- typedef struct {
- int8 X[SNAKE_MAX_LONG];
- int8 Y[SNAKE_MAX_LONG];
- uint8 Long; //蛇的長(zhǎng)度
- gameLevel Level; // 1-簡(jiǎn)單 2- 正常 3- 困難
- snakeDirection Direction; //蛇的前進(jìn)方向 默認(rèn)向右
- } snakeType; //蛇結(jié)構(gòu)體
蛇的移動(dòng)
- 蛇的局部刷新
由于開(kāi)發(fā)板的CPU Hi3861性能和0.96寸的處理性能都很有限,在貪吃蛇游戲的運(yùn)行過(guò)程中,不能每次都刷新整個(gè)屏幕,更新蛇的身體。這里,我們采用局部刷新的方法,避免一次刷新整個(gè)屏幕,影響游戲的性能。具體處理方法如下: 蛇每移動(dòng)一個(gè)長(zhǎng)度單位(1個(gè)方塊),不管有沒(méi)有吃到食物,蛇頭方塊的位置都會(huì)被打印到屏幕上,蛇的尾部方塊在未吃到食物的情況下,會(huì)被消除,吃到食物,則本次不消除尾部方塊。這樣做的好處時(shí),蛇每一次的位置變化,除了判斷是否吃到食物,只需要對(duì)頭尾部的方塊做打印處理,不需要重復(fù)打印蛇身的所有方塊。
- 蛇的移動(dòng)規(guī)則
基于開(kāi)發(fā)板的現(xiàn)有資源,想要實(shí)現(xiàn)貪吃蛇的4個(gè)方向直接控制是不現(xiàn)實(shí)的,為了合理利用開(kāi)發(fā)板的配套資源,我們使用OLED擴(kuò)展板和照明板上的2個(gè)按鍵來(lái)控制蛇的移動(dòng)。設(shè)計(jì)方法簡(jiǎn)要說(shuō)明如下:
1、8x8的點(diǎn)陣分割整個(gè)屏幕后,每個(gè)方塊的坐標(biāo)范圍在橫向(0-15),縱向(0-7),蛇每次移動(dòng)一個(gè)方塊的位置,蛇頭的變化范圍都是在-1,0,1這個(gè)3個(gè)數(shù)字之間變化,按照蛇的4個(gè)行進(jìn)方向和是否左右轉(zhuǎn),定義以下的參數(shù):
- typedef enum {
- DIREC_STRAIGHT = -1,
- DIREC_RIGHT, //右
- DIREC_TOP, //上
- DIREC_LEFT, //左
- DIREC_BOTTOM, //底
- DIREC_MAX
- } snakeDirection;
- /*1-2 直行 3-4 左轉(zhuǎn) 5-6 右轉(zhuǎn)*/
- static int8 snakeDirectonInfo[4][6] = {
- {1, 0, 0, -1, 0, 1}, //DIREC_RIGHT
- {0, -1, -1, 0, 1, 0}, //DIREC_TOP
- {-1, 0, 0, 1, 0, -1}, //DIREC_LEFT
- {0, 1, 1, 0, -1, 0} //DIREC_BOTTOM
- };
說(shuō)明:每個(gè)方向有6個(gè)元素定義, 按照兩位一組,分別為直行、左轉(zhuǎn)、右轉(zhuǎn),這樣在處理蛇的移動(dòng)的時(shí)候,可以直接套用定義好的數(shù)組,來(lái)優(yōu)化蛇身移動(dòng)的邏輯處理。
2、沒(méi)有按鍵時(shí),蛇按照一定的頻率(刷新頻率)向前移動(dòng),每次移動(dòng)一個(gè)方塊;有按鍵時(shí),根據(jù)不同的按鍵選擇不同的處理方式:
- if (direc == DIREC_LEFT)
- { //左轉(zhuǎn)
- newPos[0] = snakeDirectonInfo[Snake.Direction][2];
- newPos[1] = snakeDirectonInfo[Snake.Direction][3];
- Snake.Direction = (Snake.Direction + 1) > DIREC_BOTTOM ? (DIREC_RIGHT) : (Snake.Direction + 1);//新的方向
- }
- else if (direc == DIREC_RIGHT)
- { //右轉(zhuǎn)
- newPos[0] = snakeDirectonInfo[Snake.Direction][4];
- newPos[1] = snakeDirectonInfo[Snake.Direction][5];
- Snake.Direction = (Snake.Direction - 1) < DIREC_RIGHT ? (DIREC_BOTTOM) : (Snake.Direction - 1);//新的方向
- }
- else
- { //前進(jìn)
- newPos[0] = snakeDirectonInfo[Snake.Direction][0];
- newPos[1] = snakeDirectonInfo[Snake.Direction][1];
- }
說(shuō)明: 當(dāng)前蛇的行進(jìn)方向在有偏轉(zhuǎn)的情況下,需要更新蛇的行進(jìn)方向,這樣設(shè)計(jì)的好處是,便于計(jì)算蛇下次的運(yùn)行位置,讓蛇在我們?cè)O(shè)計(jì)的正確的路徑上行進(jìn)。
- 蛇是否撞墻,是否吃到自己
撞墻和吃到自己的算法也簡(jiǎn)單,撞墻就判斷蛇頭坐標(biāo)是否和墻重合,吃自己函數(shù)就遍歷所有身體坐標(biāo),看是否與頭重合。是這一部分是否吃到自己的判斷可能會(huì)影響到一些游戲的性能
食物

- 食物生成/更新
游戲開(kāi)始或者食物被吃,重新生成食物坐標(biāo),取系統(tǒng)的時(shí)鐘計(jì)數(shù)取模來(lái)生成一個(gè)隨機(jī)的坐標(biāo),若坐標(biāo)和蛇身重合,則重新生成。
- 食物是否被吃
算法也很簡(jiǎn)單,先判斷蛇頭坐標(biāo)是否和食物坐標(biāo)重合。如果重合則蛇變長(zhǎng)一節(jié),把之前儲(chǔ)存的蛇尾后面一節(jié)坐標(biāo)賦給最新一節(jié)身體。然后重新生成一次食物。
按鍵檢測(cè)
本程序使用到2個(gè)按鍵,相關(guān)代碼如下:
- IoTGpioInit(IOT_IO_NAME_GPIO_8); //button 按鍵B
- IoTGpioSetDir(IOT_IO_NAME_GPIO_8, IOT_GPIO_DIR_IN);
- IoTGpioRegisterIsrFunc(IOT_IO_NAME_GPIO_8, IOT_INT_TYPE_EDGE, IOT_GPIO_EDGE_FALL_LEVEL_LOW, OnButtonBPressed, NULL);
- IoTGpioInit(IOT_IO_NAME_GPIO_5); // oled button 按鍵A
- IoTGpioSetDir(IOT_IO_NAME_GPIO_5, IOT_GPIO_DIR_IN);
- IoTGpioRegisterIsrFunc(IOT_IO_NAME_GPIO_5, IOT_INT_TYPE_EDGE, IOT_GPIO_EDGE_FALL_LEVEL_LOW, OnButtonAPressed, NULL);
按鍵響應(yīng)函數(shù)獨(dú)立檢測(cè)按鍵:①游戲主界面,按鍵選擇難度和開(kāi)始游戲,②游戲運(yùn)行中,未按鍵時(shí),蛇身在當(dāng)前方向移動(dòng),直至蛇撞墻;有按鍵時(shí),根據(jù)不同的按程序調(diào)整蛇的移動(dòng)方向,并做其他的邏輯處理。
說(shuō)明:只是簡(jiǎn)單的對(duì)按鍵進(jìn)行響應(yīng),并未做復(fù)雜的優(yōu)先級(jí)和鎖的控制,這一部分有待后續(xù)完善。
總結(jié)
以上,完成一個(gè)基于LVEVK-3861開(kāi)發(fā)板的貪吃蛇小游戲的開(kāi)發(fā), 限于篇幅,只列出了部分代碼。 由于本人業(yè)務(wù)水平不夠,整個(gè)開(kāi)發(fā)的過(guò)程還是有不少的問(wèn)題出現(xiàn),解決了部分,也有很多沒(méi)有解決。部分功能也沒(méi)有做完善,留待后續(xù)再去補(bǔ)充吧,畢竟沒(méi)有十全十美。
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)