2020征文-手機(jī)深鴻會(huì)深大小組:鴻蒙HarmonyOS手機(jī)游戲之?dāng)?shù)字華容道
前言
12月16號(hào)HarmonyOS2.0手機(jī)開發(fā)者Beta版已經(jīng)發(fā)布了,作為“1+8+N”戰(zhàn)略的重要入口和生態(tài)核心,怎么能少得了手機(jī)應(yīng)用開發(fā)呢,今天將由深鴻會(huì)深大學(xué)習(xí)小組從零基礎(chǔ)開發(fā)第一個(gè)HarmonyOS手機(jī)小游戲——數(shù)字華容道(界面略丑陋,大佬別噴),此前已經(jīng)在運(yùn)動(dòng)手表上成功開發(fā)了:HarmonyOS運(yùn)動(dòng)手表游戲合并、HarmonyOS手表游戲——數(shù)字華容道,同樣是深鴻會(huì)深大小組學(xué)習(xí)完HarmonyOS后自行開發(fā)的一個(gè)鴻蒙demo,詳細(xì)講述了數(shù)字華容道在鴻蒙手機(jī)上開發(fā)思路。深鴻會(huì)深大學(xué)習(xí)小組是一群熱衷于學(xué)習(xí)鴻蒙相關(guān)知識(shí)和開發(fā)鴻蒙相關(guān)應(yīng)用的開發(fā)者們,我們的學(xué)習(xí)項(xiàng)目為:荔園Harmony、Awesome-HarmonyOS_木棉花,同時(shí)也歡迎與各位感興趣的讀者一起學(xué)習(xí)HarmonyOS開發(fā),相互交流、共同進(jìn)步。
概述
本個(gè)demo將從零基礎(chǔ)開始完成鴻蒙小游戲APP在手機(jī)上的編譯在項(xiàng)目中我們所使用到的軟件為DevEco Studio,下載地址為:DevEco Studio下載、DevEco Studio安裝教程,在項(xiàng)目中我們要實(shí)現(xiàn)的內(nèi)容為數(shù)字華容道APP的開發(fā)。
1. 打開引用首先為數(shù)字華容道的初始界面,點(diǎn)擊開始游戲即會(huì)切換到數(shù)字華容道的游戲界面。

2. 進(jìn)入數(shù)字華容道的游戲界面顯示4*4的方陣,方陣中分布有隨意打亂的1至15的數(shù)字和一個(gè)空白方格,方陣下方顯示一個(gè)“重新開始”的按鈕和一個(gè)“返回”按鈕,點(diǎn)擊“重新開始”按鈕即會(huì)重新生成隨意打亂的1至15的數(shù)字和一個(gè)空白方格的方陣,點(diǎn)擊“返回”按鈕即會(huì)切換到數(shù)字華容道的初始界面,最下方有四個(gè)指示不同方向箭頭的按鈕,點(diǎn)擊任一按鈕或向上、下、左、右任一方向滑動(dòng),空白方格周圍對(duì)應(yīng)位置的方格便會(huì)隨之向?qū)?yīng)的方向移動(dòng)一格。

3. 經(jīng)過若干次滑動(dòng)或點(diǎn)擊后,當(dāng)所有的數(shù)字按順序排列后,則會(huì)彈出游戲成功的界面,再滑動(dòng)或點(diǎn)擊也不會(huì)有任何變化。

正文
創(chuàng)建項(xiàng)目
DevEco Studio下載安裝成功后,打開DevEco Studio,點(diǎn)擊左上角的File,點(diǎn)擊New,再選擇New Project,選擇Phone選項(xiàng),選擇默認(rèn)的模板(java版),然后選擇保存路徑,將文件命名為MyPhoneApplication(文件名不能出現(xiàn)中文或者特殊字符,否則將無法成功創(chuàng)建項(xiàng)目文件),最后點(diǎn)擊Finish。


實(shí)現(xiàn)初始界面布局
首先,我們要先實(shí)現(xiàn)數(shù)字華容道的初始界面,點(diǎn)擊開始游戲即會(huì)切換到另一個(gè)空白的界面。

1. 先在entry>src>main>config.json文件中最下方"launchType": "standard"的后面添加以下代碼,并且將上方的“label”:“MyPhoneApplication”修改成"label": "數(shù)字華容道",這樣就實(shí)現(xiàn)去掉應(yīng)用上方的標(biāo)題欄和將應(yīng)用名稱改為數(shù)字華容道了
config.json最下面部分代碼:
- "orientation": "unspecified",
- "name": "com.example.myphoneapplication.MainAbility",
- "icon": "$media:icon",
- "description": "$string:mainability_description",
- "label": "數(shù)字華容道",
- "type": "page",
- "launchType": "standard",
- "metaData": {
- "customizeData": [
- {
- "name": "hwc-theme",
- "value": "androidhwext:style/Theme.Emui.Light.NoTitleBar",
- "extra": ""
- }
- ]
- }
2. 先將我們事先準(zhǔn)備好的圖片復(fù)制粘貼到entry>src>main>resources>base>media文件夾中(ctrl+c、ctrl+v復(fù)制粘貼),并且命名為game,點(diǎn)擊OK。

在entry>src>main>resources>base>layout>ability_main.xml中添加布局,先將事先存在的Text組件刪去,添加Image圖片組件,引入我們剛才復(fù)制粘貼的圖片,再添加一個(gè)Button按鈕組件,加入唯一標(biāo)識(shí)符id并配置好其他相應(yīng)的屬性。
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:orientation="vertical">
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:image_src="$media:game"
- ohos:layout_alignment="center"
- />
- ohos:id="$+id:button_game"
- ohos:height="150"
- ohos:width="515"
- ohos:text_alignment="center"
- ohos:top_margin="-810"
- ohos:left_margin="250"
- />
3. 在entry>src>main>java>com.example.myphoneapplication>slice中右鍵選擇New>Java Class增加一個(gè)空白的類以用來后面編寫數(shù)字華容道的游戲界面,并且命名為SecondAbilitySlice。

將entry>src>main>java>com.example.myphoneapplication>slice>SecondAbilitySlice中的代碼修改成如下:
- package com.example.myphoneapplication.slice;
- import com.example.myphoneapplication.ResourceTable;
- import ohos.aafwk.ability.AbilitySlice;
- import ohos.aafwk.content.Intent;
- public class SecondAbilitySlice extends AbilitySlice {
- public void onStart(Intent intent) {
- super.onStart(intent);
- }
- @Override
- public void onActive() {
- super.onActive();
- }
- @Override
- public void onForeground(Intent intent) {
- super.onForeground(intent);
- }
- }
4. 在entry>src>main>java>com.example.myphoneapplication>slice>MainAbilitySlice中的onStart函數(shù)中添加一個(gè)按鈕指向我們(2)中添加的按鈕,按鈕添加一個(gè)響應(yīng)點(diǎn)擊事件的函數(shù),用parsent函數(shù)跳轉(zhuǎn)到SecondAbilitySlice。
- package com.example.myphoneapplication.slice;
- import com.example.myphoneapplication.ResourceTable;
- import ohos.aafwk.content.Intent;
- import ohos.agp.components.Button;
- import ohos.agp.components.Component;
- public class MainAbilitySlice extends ohos.aafwk.ability.AbilitySlice {
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setUIContent(ResourceTable.Layout_ability_main);
- Button button = (Button) findComponentById(ResourceTable.Id_button_game);
- button.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- present(new SecondAbilitySlice(),new Intent());
- }
- });
- }
- @Override
- public void onActive() {
- super.onActive();
- }
- @Override
- public void onForeground(Intent intent) {
- super.onForeground(intent);
- }
- }
至此,這一部分就完成了。
實(shí)現(xiàn)數(shù)字的隨機(jī)打亂
然后我們要在數(shù)字華容道的游戲界面生成隨意打亂的1至15的數(shù)字和一個(gè)空白方格的方陣。

在entry>src>main>java>com.example.myphoneapplication>slice>SecondAbilitySlice編寫代碼。
先定義個(gè)一個(gè)位置布局layout和一個(gè)二維數(shù)組grids,創(chuàng)建函數(shù)initializeinitialize()分別對(duì)其初始化,在onStart函數(shù)中調(diào)用函數(shù)initializeinitialize()。
- private float starX, starY, distanceX, distanceY;
- private DirectionalLayout layout;
- private int[][] grids;
- public void onStart(Intent intent) {
- super.onStart(intent);
- initialize();
- }
- public void initialize(){
- layout = new DirectionalLayout(this);
- grids = new int[][]{{1, 2, 3, 4}, {5, 6, 7, 8,}, {9, 10, 11, 12}, {13, 14, 15, 0}};
- }
然后定義函數(shù)drawGrids(int[][] grids)用于繪制4*4方陣和其二維數(shù)組對(duì)應(yīng)的數(shù)字。
- public void drawGrids(int[][] grids){
- layout.setLayoutConfig((new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT,ComponentContainer.LayoutConfig.MATCH_PARENT)));
- Component.DrawTask task=new Component.DrawTask() {
- public void onDraw(Component component, Canvas canvas) {
- Paint mPaint = new Paint();
- mPaint.setColor(Color.GRAY);
- RectFloat rect=new RectFloat(2,230,1078,1306);
- canvas.drawRect(rect,mPaint);
- for(int row = 0; row < 4; row++){
- for(int column = 0; column < 4; column++){
- mPaint.setColor(Color.CYAN);
- RectFloat rectFloat=new RectFloat(22+column*262,250+row*262,272+column*262,500+row*262);
- canvas.drawRect(rectFloat,mPaint);
- mPaint.setColor(Color.YELLOW);
- mPaint.setTextSize(125);
- if(grids[row][column]!=0){
- if(grids[row][column]<10){
- canvas.drawText(mPaint, String.valueOf(grids[row][column]),105+column*262,425+row*262);
- }
- else{
- canvas.drawText(mPaint, String.valueOf(grids[row][column]),65+column*262,425+row*262);
- }
- }
- }
- }
- }
- };
- layout.addDrawTask(task);
- setUIContent(layout);
- }
再定義函數(shù)changeGrids(int[][] grids,int direction),每次接收一個(gè)方向,2表示上移,-1表示左移,1表示右移,-2表示下移,找出空白方格所在位置對(duì)應(yīng)的二維數(shù)組下標(biāo),對(duì)應(yīng)的方格和空白方格對(duì)應(yīng)的二維數(shù)組的數(shù)值對(duì)調(diào)。
- public void changeGrids(int[][] grids,int direction){
- int row_0 = 3;
- int column_0 = 3;
- int temp;
- for(int row = 0; row < 4; row++) {
- for (int column = 0; column < 4; column++) {
- if(grids[row][column] == 0){
- row_0 = row;
- column_0 = column;
- }
- }
- }
- if(direction == -1 && (column_0 + 1) <= 3){
- temp = grids[row_0][column_0 + 1];
- grids[row_0][column_0 + 1] = grids[row_0][column_0];
- grids[row_0][column_0] = temp;
- }else if (direction == 1 && (column_0 - 1) >= 0) {
- temp = grids[row_0][column_0 - 1];
- grids[row_0][column_0 - 1] = grids[row_0][column_0];
- grids[row_0][column_0] = temp;
- } else if (direction == 2 && (row_0 + 1) <= 3) {
- temp = grids[row_0 + 1][column_0];
- grids[row_0 + 1][column_0] = grids[row_0][column_0];
- grids[row_0][column_0] = temp;
- } else if (direction == -2 && (row_0 - 1) >= 0) {
- temp = grids[row_0 - 1][column_0];
- grids[row_0 - 1][column_0] = grids[row_0][column_0];
- grids[row_0][column_0] = temp;
- }
- }
定義函數(shù)createGrids(int[][] grids)用于隨機(jī)生成一個(gè)表示方向的數(shù)字,循環(huán)調(diào)用函數(shù)changeGrids(grids,direction)用于隨機(jī)打亂二維數(shù)組對(duì)應(yīng)的數(shù)字。
- public void createGrids(int[][] grids){
- int[] array = {-1,-2,1,2};
- for(int i = 0; i < 100; i++){
- int random = (int)Math.floor(Math.random()*4);
- int direction = array[random];
- changeGrids(grids,direction);
- }
- }
最后在initialize()函數(shù)中調(diào)用createGrids(grids)函數(shù)和drawGrids(grids)函數(shù)。
- public void initialize(){
- layout = new DirectionalLayout(this);
- grids = new int[][]{{1, 2, 3, 4}, {5, 6, 7, 8,}, {9, 10, 11, 12}, {13, 14, 15, 0}};
- createGrids(grids);
- drawGrids(grids);
- }
至此,這一部分完成了。
實(shí)現(xiàn)滑動(dòng)或點(diǎn)擊調(diào)換數(shù)字
添加“重新開始”和“返回”按鈕,在最下方添加四個(gè)指示不同方向箭頭的按鈕,點(diǎn)擊任一按鈕或向上、下、左、右任一方向滑動(dòng),空白方格周圍對(duì)應(yīng)位置的方格便會(huì)隨之向?qū)?yīng)的方向移動(dòng)一格。

在entry>src>main>java>com.example.myphoneapplication>slice>SecondAbilitySlice編寫代碼。
先定義一個(gè)函數(shù)drawButton()用于繪制所有的按鈕,四個(gè)指示不同方向箭頭的按鈕分別添加四個(gè)響應(yīng)點(diǎn)擊事件的函數(shù),分別調(diào)用對(duì)應(yīng)的changeGrids(grids,direction)函數(shù)實(shí)現(xiàn)空白方格周圍對(duì)應(yīng)位置的方格便會(huì)隨之向?qū)?yīng)的方向移動(dòng)一格,并調(diào)用drawGrids(grids)函數(shù)用于繪制新的方陣。
- public void drawButton(){
- Button button=new Button(this);
- button.setText("重新開始");
- button.setTextSize(100);
- button.setTextAlignment(TextAlignment.CENTER);
- button.setTextColor(Color.WHITE);
- button.setMarginTop(1400);
- button.setMarginLeft(80);
- button.setPadding(20,20,20,20);
- ShapeElement background = new ShapeElement();
- background.setRgbColor(new RgbColor(174, 158, 143));
- background.setCornerRadius(100);
- button.setBackground(background);
- layout.addComponent(button);
- Button button0=new Button(this);
- button0.setText("返回");
- button0.setTextSize(100);
- button0.setTextAlignment(TextAlignment.CENTER);
- button0.setTextColor(Color.WHITE);
- button0.setMarginTop(-170);
- button0.setMarginLeft(680);
- button0.setPadding(20,20,20,20);
- button0.setBackground(background);
- layout.addComponent(button0);
- ShapeElement background0 = new ShapeElement();
- background0.setRgbColor(new RgbColor(174, 158, 143));
- background0.setCornerRadius(100);
- Button button1=new Button(this);
- button1.setText("↑");
- button1.setTextAlignment(TextAlignment.CENTER);
- button1.setTextColor(Color.WHITE);
- button1.setTextSize(100);
- button1.setMarginLeft(500);
- button1.setMarginTop(70);
- button1.setPadding(10,0,10,0);
- button1.setBackground(background0);
- button1.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- changeGrids(grids,2);
- }
- });
- layout.addComponent(button1);
- Button button2=new Button(this);
- button2.setText("←");
- button2.setTextAlignment(TextAlignment.CENTER);
- button2.setTextColor(Color.WHITE);
- button2.setTextSize(100);
- button2.setMarginTop(10);
- button2.setMarginLeft(400);
- button2.setPadding(10,0,10,0);
- button2.setBackground(background0);
- button2.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- changeGrids(grids,-1);
- }
- });
- layout.addComponent(button2);
- Button button3=new Button(this);
- button3.setText("→");
- button3.setTextAlignment(TextAlignment.CENTER);
- button3.setTextColor(Color.WHITE);
- button3.setTextSize(100);
- button3.setMarginLeft(600);
- button3.setMarginTop(-130);
- button3.setPadding(10,0,10,0);
- button3.setBackground(background0);
- button3.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- changeGrids(grids,1);
- }
- });
- layout.addComponent(button3);
- Button button4=new Button(this);
- button4.setText("↓");
- button4.setTextAlignment(TextAlignment.CENTER);
- button4.setTextColor(Color.WHITE);
- button4.setTextSize(100);
- button4.setMarginLeft(500);
- button4.setMarginTop(10);
- button4.setPadding(10,0,10,0);
- button4.setBackground(background0);
- button4.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- changeGrids(grids,-2);
- }
- });
- layout.addComponent(button4);
- drawGrids(grids);
- }
然后添加一個(gè)函數(shù)slideGrids()為布局layout添加一個(gè)滑動(dòng)事件,并獲取滑動(dòng)開始與結(jié)束的坐標(biāo),并計(jì)算出大致的滑動(dòng)方向,分別調(diào)用對(duì)應(yīng)的changeGrids(grids,direction)函數(shù)實(shí)現(xiàn)空白方格周圍對(duì)應(yīng)位置的方格便會(huì)隨之向?qū)?yīng)的方向移動(dòng)一格,并調(diào)用drawGrids(grids)函數(shù)用于繪制新的方陣,并在開頭添加相應(yīng)的變量。
- private float starX, starY, distanceX, distanceY;
- public void slideGrids(){
- layout.setTouchEventListener(new Component.TouchEventListener() {
- @Override
- public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
- MmiPoint point = touchEvent.getPointerScreenPosition(0);
- switch (touchEvent.getAction()) {
- case TouchEvent.PRIMARY_POINT_DOWN:
- starX = point.getX();
- starY = point.getY();
- break;
- case TouchEvent.PRIMARY_POINT_UP:
- distanceX = point.getX() - starX;
- distanceY = point.getY() - starY;
- break;
- }
- if (gameover() == false){
- if (Math.abs(distanceX) > Math.abs(distanceY)) {
- if (distanceX > 200) {
- changeGrids(grids,1);
- } else if (distanceX < -200) {
- changeGrids(grids,-1);
- }
- } else if (Math.abs(distanceX) < Math.abs(distanceY)){
- if (distanceY > 200) {
- changeGrids(grids,-2);
- } else if (distanceY < -200) {
- changeGrids(grids,2);
- }
- }
- }
- drawGrids(grids);
- return false;
- }
- });
- }
最后在initialize()函數(shù)中調(diào)用slideGrids()函數(shù)和drawButton()函數(shù)。
- public void initialize(){
- layout = new DirectionalLayout(this);
- grids = new int[][]{{1, 2, 3, 4}, {5, 6, 7, 8,}, {9, 10, 11, 12}, {13, 14, 15, 0}};
- createGrids(grids);
- slideGrids();
- drawButton();
- drawGrids(grids);
- }
至此,這一部分完成了。
實(shí)現(xiàn)游戲成功界面
點(diǎn)擊“重新開始”按鈕即會(huì)重新生成隨意打亂的1至15的數(shù)字和一個(gè)空白方格的方陣,點(diǎn)擊“返回”按鈕即會(huì)切換到數(shù)字華容道的初始界面,經(jīng)過若干次滑動(dòng)或點(diǎn)擊后,當(dāng)所有的數(shù)字按順序排列后,則會(huì)彈出游戲成功的界面,再滑動(dòng)或點(diǎn)擊也不會(huì)有任何變化。

在entry>src>main>java>com.example.myphoneapplication>slice>SecondAbilitySlice編寫代碼。
首先定義一個(gè)函數(shù)drawText()用于繪制游戲成功字樣。
- public void drawText(){
- Text text=new Text(this);
- text.setText("游戲成功");
- text.setTextSize(100);
- text.setTextColor(Color.BLUE);
- text.setTextAlignment(TextAlignment.CENTER);
- text.setMarginsTopAndBottom(-2000,0);
- text.setMarginsLeftAndRight(350,0);
- layout.addComponent(text);
- setUIContent(layout);
- }
然后定義一個(gè)函數(shù)gameover()用于判斷二維數(shù)組的數(shù)字是否按順序排列,當(dāng)二維數(shù)組的數(shù)字按順序排列時(shí)返回true,否則返回false。
- public boolean gameover() {
- int[][] gameoverGrids = {{1, 2, 3, 4}, {5, 6, 7, 8,}, {9, 10, 11, 12}, {13, 14, 15, 0}};
- for (int row = 0; row < 4; row++) {
- for (int column = 0; column < 4; column++) {
- if (grids[row][column] != gameoverGrids[row][column]) {
- return false;
- }
- }
- }
- return true;
- }
再在drawButton()函數(shù)中重新開始按鈕中添加一個(gè)響應(yīng)點(diǎn)擊事件的函數(shù),用于調(diào)用函數(shù)initialize()實(shí)現(xiàn)重新生成隨意打亂的1至15的數(shù)字和一個(gè)空白方格的方陣,返回按鈕中添加一個(gè)響應(yīng)點(diǎn)擊事件的函數(shù),用parsen函數(shù)返回?cái)?shù)字華容道的初始界面,四個(gè)指示不同方向箭頭的按鈕的響應(yīng)點(diǎn)擊事件的函數(shù)中增加一個(gè)判斷,當(dāng)函數(shù)gameover()返回為false時(shí)才調(diào)用各自的changeGrids(grids,direction)函數(shù),最后增加一個(gè)判斷,當(dāng)函數(shù)gameover()返回為true時(shí)調(diào)用函數(shù)drawText()。
- public void drawButton(){//部分代碼沒有貼出,可自行下載源代碼查看
- button.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- initialize();
- }
- });
- button0.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- present(new SecondAbilitySlice(),new Intent());
- }
- });
- button1.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- if (gameover() == false){
- changeGrids(grids,2);
- }
- }
- });
- button2.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- if (gameover() == false){
- changeGrids(grids,-1);
- }
- }
- });
- button3.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- if (gameover() == false){
- changeGrids(grids,1);
- }
- }
- });
- button4.setClickedListener(new Component.ClickedListener() {
- @Override
- public void onClick(Component component) {
- if (gameover() == false){
- changeGrids(grids,-2);
- }
- }
- });
- if(gameover()){
- drawText();
- }
- }
在函數(shù)slideGrids()函數(shù)中增加一個(gè)判斷,當(dāng)函數(shù)gameover()返回為false時(shí)才調(diào)用changeGrids(grids,direction)函數(shù),最后增加一個(gè)判斷,當(dāng)函數(shù)gameover()返回為true時(shí)調(diào)用函數(shù)drawText()。
- public void slideGrids(){//部分代碼沒有貼出,可自行下載源代碼查看
- if (gameover() == false){
- //{...}
- }
- if(gameover()){
- drawText();
- }
- }
至此,整個(gè)demo全部完成了。
結(jié)語
以上就是數(shù)字華容道小游戲在手機(jī)的主要編寫思路以及代碼,源碼將放在附件中,歡迎大家下載,感興趣的讀者可以自行跟著編寫學(xué)習(xí),相信你們也能夠完成的。更多深鴻會(huì)深大小組學(xué)習(xí)項(xiàng)目可以查看荔園Harmony,如果有遇到什么問題,或者查找出其中的錯(cuò)誤之處,或者能夠優(yōu)化代碼和界面,也歡迎各位在評(píng)論區(qū)留言討論,讓我們一起學(xué)習(xí)進(jìn)步!
©著作權(quán)歸作者和HarmonyOS技術(shù)社區(qū)共同所有,如需轉(zhuǎn)載,請(qǐng)注明出處,否則將追究法律責(zé)任
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
https://harmonyos.51cto.com/#zz