深圳幻海软件技术有限公司 欢迎您!

暴打七夕青蛙—HarmonyOS服务卡片小游戏

2023-02-27

想了解更多内容,请访问:51CTO和华为官方合作共建的鸿蒙技术社区https://harmonyos.51cto.com前言七夕节,令我“幸福”的是,被热心市民送来了七夕青蛙,听取一天了蛙声一片。吃饱了一整天的狗粮后,有点撑着,于是决定加班加点,用服务卡片实现了一个简单的小游戏:暴打七夕青蛙!游戏虽

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

前言

七夕节,令我“幸福”的是,被热心市民送来了七夕青蛙,听取一天了蛙声一片。吃饱了一整天的狗粮后,有点撑着,于是决定加班加点,用服务卡片实现了一个简单的小游戏:暴打七夕青蛙!游戏虽简单,但玩起来是真的爽!

效果展示视频

效果视频

效果图



编写过程

更改程序标签和程序的图标

在resources文件下的zh.element中的string.json中修改如下,就把应用的名字修改为“七夕青蛙”,而主页面也会从“Hello World!” 变成 “GoodBye World!”


在config.json文件修改icon的值,引用到media里面的青蛙。


创建JS卡片

编写卡片的样式

游戏卡片的结构非常非常非常非常简单,就是一个div容器,通过设置div容器的背景图来实现游戏效果。playGame卡片的hml代码如下:

<div style="width:100100%;" > 
    <div style="flex_direction:column;width:100%;height:100%;background-image:{{background}} ;"onclick="messageEvent" > 
    </div> 
</div> 
  • 1.
  • 2.
  • 3.
  • 4.

GamePanel的样式稍微复杂一点,但是其实也很简单。需要设置两个变量,得分score和倒计时countdown,其中得分设置成一个上下结构。hml代码如下:

<div> 
    <div class="normal_container"
        <div class="pic_title_container" onclick="settings"  > 
            <div style="flex-direction: row;" > 
            <!-- 得分 --> 
                <div style="flex-direction: column;width: 50%;margin-top: 20px;" > 
                    <text style="text-align: center;width: 100%;font-size: 25px;"
                        SCORE 
                    </text> 
                    <text style="text-align: center;width: 100%;font-size: 35px;color: ghostwhite;"
                        {{ score }} 
                    </text> 
                </div> 
            <!-- 倒计时 --> 
                <text style="text-align: center;width: 40%;font-size: 60px;color: brown;" > 
                    {{ countDown }} 
                </text> 
            </div> 
            <div style="margin-right: 10px;" > 
                <button onclick="start" type="capsule" style="opacity: 0.5;margin-right: 40px;text-align: center;width: 40%;">开始</button> 
                <button onclick="stop" type="capsule" style="opacity: 0.5;margin-right: 40px;text-align: center;width: 40%;">停止</button> 
            </div> 
        </div> 
    </div> 
</div> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

给变量赋初值

在playGame卡片的index.json文件中,修改“data”如下图,默认一开始是没有青蛙出现的。


在GamePanel卡片的index.json文件中,修改“data”如下图


设置卡片的动作事件

“actions”数组是所有事件的集合,下面跟着每个事件的名称,名称里面又包含事件的类型“action”和携带的参数“params”。

在playGame卡片的index.json文件中,修改“actions”如下图


在GamePanel卡片的index.json文件中,修改“actions”如下图


编写游戏部分

七夕青蛙的随机出现

七夕青蛙的出现上文提到过是通过设置div容器的背景图片来实现,所以可以通过产生一个随机数的方式来随机地设置div容器的背景来实现游戏过程,因此需要把div的背景设置成变量,并添加一个onclick标签。

//两种背景图的路径 
private static final String frog="url(\"/common/frog1.png\")"
private static final String hole="url(\"/common/hole.png\")"
public String rand_bg() 
   { 
       String bg; 
       double randnumber=Math.random(); 
       if(randnumber>0.65)//随机数大于0.65时把返回的字符串对应青蛙图,这个数值可以自行设定 
           bg=frog; 
       else 
           bg=hole; 
       return bg; 
   } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

创建一个数据体来存储卡片的信息,并使用MAP将其存储起来

不同卡片的回调事件都是共用一个回调方法的,所以想要区分到底是哪一种,哪一个卡片发出的回调,就需要把卡片的信息:卡片的名称,卡片的ID,卡片的相关参数等记录下来。这里采用编写一个卡片数据类来存储1*2格式的卡片的信息。

public class GameWigetData 

    public String background; 
    public long FormId; 
    public GameWigetData()  
    { 
        super(); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
public static Map<Long, GameWigetData> gameWidgetDataMap=new HashMap<>()//键是FormId,值是数据体 
  • 1.

修改onCreateForm()方法

onCreateForm()方法在两种情况被调用。第一种是上滑呼出卡片的时候,这时候上滑卡片是哪一种卡片,就会调用一次onCreateForm()方法生成一张该种卡片;

第二种情形是长按应用,然后点击"服务卡片",此时会显示应用的所有卡片,并每一张卡片都会回调一次onCreateForm()方法并生成一个卡片,当选择了其中某一张卡片添加到桌面之后,其他卡片回调onDeleteForm()方法来删除卡片。所有卡片都是调用同一个方法一起生成的,所以需要对卡片的名称进行一次判断,以确定卡片的种类。在onCreateForm()中添加如下代码:

if(formName.equals("GamePanel"))//如果是游戏控制面板卡片,则有如下操作 
        { 
            if(gamePanelFormId==0) 
            {//如果放置了两个,那么只有放置的第一个有作用,应该游戏控制面板只需要一个 
                gamePanelFormId=formId; 
            } 
        } 
//如果是游戏卡片,那么创建一个数据体实例,并把它的卡片id和数据体实例一同传入Map中 
        else if(formName.equals("playGame")) 
        { 
            GameWigetData gameWidgetData = new GameWigetData(); 
            gameWigetDataMap.put(formId, gameWidgetData); 
           // System.out.println("formID->"+formId); 
        } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

修改onDeleteForm()方法

在onDeleteForm()方法中,要补充两种卡片删除时的设置,这里很重要,一开始我没有对游戏卡片进行设置,结果运行会抛出没有对应的FormId的错误,查看卡片的时候所有卡片都会调用onCreateForm()方法,然后所有12卡片的ID都写入Map里面,但是当其中一个卡片放置到桌面,而其他卡片回调onDeleteForm()进行删除的时候,12卡片的信息并没有从Map中移除。这就会导致并不是每一个Map中的FormId都有对应的卡片。

if(gamePanelFormId==formId){ 
           gamePanelFormId=0; 
       } 
else
           gameWigetDataMap.remove(formId); 
 
       
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

编写变量的更新方法

由于后面的操作需要频繁地用到更新,修改卡片上的变量的操作,所以在进行下面的操作之前,我们先编写一个修改变量的方法。

//更新值是字符串时 
 private void updateWidget(long formId,String key, String value) { 
        try { 
            ZSONObject zsonObject = new ZSONObject(); 
            zsonObject.put(key, value); 
            FormBindingData formBindingData = new FormBindingData(zsonObject); 
            updateForm(formId, formBindingData); 
        } catch (Exception e) { 
            System.out.println(e.getMessage()); 
        } 
    } 
//更新值是整数时 
    public void updateWidget(long formId,String keyint value) { 
        updateWidget(formId, key, String.valueOf(value)); 
 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

修改onTriggerFormEvent()方法

在这个游戏中,总共有三个点击事件需要响应:开始键,停止键,打青蛙。同样的,这三个事件共用一个回调方法,因此需要通过事件所携带的参数来判断到底是哪一个事件回调了方法。在onTriggerFormEvent()中添加如下代码:

//接受事件传递的参数 
 ZSONObject zsonObject=ZSONObject.stringToZSON(message); 
 String message1=zsonObject.getString("message"); 
 
//如果是开始键触发的事件,则把开始标志设置为真 
        if (message1.equals("start")) 
        { 
            startFlag=true
            System.out.println("start"); 
        } 
//如果是停止键触发的事件,则把开始标志设置为假,并重置面板上的数据 
        else if (message1.equals("stop")) 
        { 
            startFlag = false
            score = 0; 
            countdown1 = 60; 
            updateWidget(gamePanelFormId,"score",score); 
            updateWidget(gamePanelFormId,"countdown",countdown1); 
        } 
//如果是“打青蛙”事件 
        else 
        { 
            if(startFlag)//如果游戏在进行中 
            { 
                //判断现在面板中是不是青蛙 
                GameWigetData gameFormData=gameWigetDataMap.get(formId); 
                if(gameFormData.background.equals(frog)) 
                { 
                    score =score+10;//达到一个加十分 
                    System.out.println("现在的分数是"+score); 
                    gameFormData.background=hole;//打完重新设置为洞 
                    updateWidget(gamePanelFormId,"score",score); 
                    updateWidget(formId,"background",gameFormData.background); 
                } 
            } 
            else 
                System.out.println("游戏已经结束了"); 
        } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

编写游戏线程

在onStart()方法中添加游戏线程如下:

if(gameThread==null
        {//如果还未创建游戏线程,则创建游戏线程 
            gameThread=new Thread(() -> { 
                while(true
                { 
                    try { 
                        Thread.sleep(50); 
                        if(startFlag) 
                        { 
                            //对所有的卡片都随机地设置背景 
                            for(GameWigetData gameWigetData:gameWigetDataMap.values()) 
                            { 
                                gameWigetData.background=tool.rand_bg(); 
                            } 
                            //对所有的1*2卡片进行更新 
                            for(long gameWigetFormId:gameWigetDataMap.keySet()) 
                            { 
                                GameWigetData gameWigetData=gameWigetDataMap.get(gameWigetFormId); 
                                updateWidget(gameWigetFormId,"background",gameWigetData.background); 
                            } 
                        } 
                        Thread.sleep(750); 
                    }catch (Exception e) 
                    { 
                        System.out.println(e.getMessage()); 
                        startFlag=false
                    } 
                } 
            }); 
            gameThread.start(); 
        } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

编写倒计时线程

在onStart()方法中编写倒计时线程如下:

if(countDownThread==null
        { 
            countDownThread=new Thread(new Runnable() 
            { 
                public void run() 
                { 
                    while(true
                    { 
                        try{ 
                            Thread.sleep(50); 
                            if(startFlag) 
                            { 
                                if(countdown1>0) 
                                { 
                                    updateWidget(gamePanelFormId,"countdown",countdown1); 
                                    countdown1--; 
                                    System.out.println("现在剩余的时间是"+countdown1); 
                                } 
                                else//countdown==0的时候,复位 
                                { 
                                    updateWidget(gamePanelFormId,"countdown",0); 
                                    startFlag=false
                                    countdown1=60; 
                                    System.out.println("游戏结束!"); 
                                } 
                            } 
                        Thread.sleep(1000); 
                        }catch (Exception e) 
                        { 
                            System.out.println(e.getMessage()); 
                        } 
                    } 
                } 
            }); 
            countDownThread.start(); 
        } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

项目踩的小坑

1.score要设置成静态变量,否则被释放,导致尽管打中很多次都只能到10分,而不能够往上累积。

2.onDeleteForm()方法要记得从Map中remove掉已经删除掉的卡片的ID

最后

最后祝有情人终成眷属啦,祝单身狗早日脱单!还有就是感谢我那群为我瞎操心的朋友们!(文后附上脱单压缩包)

Card_Game_the_frog.zip

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com