暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

一个简单的鸿蒙小游戏:记忆翻牌

鸿蒙技术社区 2022-02-07
749

本文旨在通过游戏的形式向大家展示自己学习鸿蒙应用开发的一些经验。因为没有鸿蒙手机可以调试,电脑配置也比较拉跨,项目测试的工作做得比较少,假如有点小 bug 希望大家不要见怪,能够以一种较为轻松的方式交流学习。


今天在这里教大家用 JS 语言进行鸿蒙应用开发,复刻一个经典的小游戏——记忆翻牌。


游戏规则很简单,左右两边各有 12 张生肖图片,开局随机打乱,在一定时间内记住两边图片的位置,之后盖上牌开始计时,玩家将两边相同的生肖图片进行一一配对,在限定时间内完成全部图片的配对即可取得游戏胜利。


新建工程


在 DevEco Studio 中点击 File→New Project→Empty Ability→Next,Project type 选择 Application,Language 选择 JS 语言,最后点击 Finish 完成项目创建。


项目结构


如下图:

编写页面


大致流程图及弹窗切换逻辑如下:

游戏页面:

    <div class="container">
        <div class="side" disabled="{{L_dab}}">
            <div class="row">
                <image class="image" disabled="{{ cards[0].dab }}" src="common/images/{{ cards[0].src }}.png" onclick="turnover(0)"></image>
                <image class="image" disabled="{{ cards[1].dab }}" src="common/images/{{ cards[1].src }}.png" onclick="turnover(1)"></image>
                <image class="image" disabled="{{ cards[2].dab }}" src="common/images/{{ cards[2].src }}.png" onclick="turnover(2)"></image>
                <image class="image" disabled="{{ cards[3].dab }}" src="common/images/{{ cards[3].src }}.png" onclick="turnover(3)"></image>
            </div>

            <div class="row">
                <image class="image" disabled="{{ cards[4].dab }}" src="common/images/{{ cards[4].src }}.png" onclick="turnover(4)"></image>
                <image class="image" disabled="{{ cards[5].dab }}" src="common/images/{{ cards[5].src }}.png" onclick="turnover(5)"></image>
                <image class="image" disabled="{{ cards[6].dab }}" src="common/images/{{ cards[6].src }}.png" onclick="turnover(6)"></image>
                <image class="image" disabled="{{ cards[7].dab }}" src="common/images/{{ cards[7].src }}.png" onclick="turnover(7)"></image>
            </div>

            <div class="row">
                <image class="image" disabled="{{ cards[8].dab }}" src="common/images/{{ cards[8].src }}.png" onclick="turnover(8)"></image>
                <image class="image" disabled="{{ cards[9].dab }}" src="common/images/{{ cards[9].src }}.png" onclick="turnover(9)"></image>
                <image class="image" disabled="{{ cards[10].dab }}" src="common/images/{{ cards[10].src }}.png" onclick="turnover(10)"></image>
                <image class="image" disabled="{{ cards[11].dab }}" src="common/images/{{ cards[11].src }}.png" onclick="turnover(11)"></image>
            </div>
        </div>
        <div class="middle">
            <text style="height: 20%;" disabled="{{ notice || gameset || pause || popup }}" onclick="gamestop">{{ thetime }}</text>
            <label style="height: 80%; border: 3px; border-bottom-color: black;"></label>
        </div>
        <div class="side" disabled="{{R_dab}}">
            <div class="row">
                <image class="image" disabled="{{ cards[12].dab }}" src="common/images/{{ cards[12].src }}.png" onclick="turnover(12)"></image>
                <image class="image" disabled="{{ cards[13].dab }}" src="common/images/{{ cards[13].src }}.png" onclick="turnover(13)"></image>
                <image class="image" disabled="{{ cards[14].dab }}" src="common/images/{{ cards[14].src }}.png" onclick="turnover(14)"></image>
                <image class="image" disabled="{{ cards[15].dab }}" src="common/images/{{ cards[15].src }}.png" onclick="turnover(15)"></image>
            </div>

            <div class="row">
                <image class="image" disabled="{{ cards[16].dab }}" src="common/images/{{ cards[16].src }}.png" onclick="turnover(16)"></image>
                <image class="image" disabled="{{ cards[17].dab }}" src="common/images/{{ cards[17].src }}.png" onclick="turnover(17)"></image>
                <image class="image" disabled="{{ cards[18].dab }}" src="common/images/{{ cards[18].src }}.png" onclick="turnover(18)"></image>
                <image class="image" disabled="{{ cards[19].dab }}" src="common/images/{{ cards[19].src }}.png" onclick="turnover(19)"></image>
            </div>

            <div class="row">
                <image class="image" disabled="{{ cards[20].dab }}" src="common/images/{{ cards[20].src }}.png" onclick="turnover(20)"></image>
                <image class="image" disabled="{{ cards[21].dab }}" src="common/images/{{ cards[21].src }}.png" onclick="turnover(21)"></image>
                <image class="image" disabled="{{ cards[22].dab }}" src="common/images/{{ cards[22].src }}.png" onclick="turnover(22)"></image>
                <image class="image" disabled="{{ cards[23].dab }}" src="common/images/{{ cards[23].src }}.png" onclick="turnover(23)"></image>
            </div>
        </div>
    </div>


左右两边是三行四列的生肖图片,中间是计时器和分割线。

游戏规则说明弹窗:

    <div class="all_popup" style="height: 80%; width: 70%;" show="{{ notice }}">
        <text style="height: 60%; font-size: 30px; margin: 10px;">{{ rule }}</text>
        <button class="btn" onclick="rule_notice">知道了</button>
    </div>


开局直接显示在游戏页面上方,向玩家介绍游戏规则,点击“知道了”按钮进入难度设置环节。

难度设置弹窗:

    <div class="all_popup" style="height: 90%; width: 70%;" show="{{ gameset }}">
        <text class="title">难度设置</text>
        <div class="time_set">
            <text style="height: 80px; font-size: 26px;">记忆时间</text>
            <button class="time_btn" onclick="timeset('metime', -5)">-</button>
            <text class="seconds">{{ metime }}s</text>
            <button class="time_btn" onclick="timeset('metime', 5)">+</button>
            <text style="height: 80px; font-size: 26px;">(5~30)</text>
        </div>
        <div class="time_set">
            <text style="height: 80px; font-size: 26px;">限定时间</text>
            <button class="time_btn" onclick="timeset('maxtime', -5)">-</button>
            <text class="seconds">{{ maxtime }}s</text>
            <button class="time_btn" onclick="timeset('maxtime', 5)">+</button>
            <text style="height: 80px; ">(20~120)</text>
        </div>
        <button class="btn" onclick="gamestart">开始</button>
    </div>


我设置了记忆时间和限定时间的设置区间,玩家可以根据自己的水平自定义游戏正式开始前记忆牌堆的准备时间,以及完成图片配对的限定时间。完成难度设置后点击开始正式开始游戏。

游戏暂停弹窗:

    <div class="all_popup" style="height: 80%; width: 40%;" show="{{ pause }}">
        <text class="title">暂停中</text>
        <button class="btn" onclick="resume">继续游戏</button>
        <button class="btn" onclick="restart">重新开始</button>
        <button class="btn" onclick="toset">返回设置</button>
    </div>


游戏进行中的任何时刻都可以点击顶部的计时文本暂停游戏进行。点击“继续游戏”接着计时;点击“重新开始”在原先的难度设置上重新开始新一轮游戏;点击“返回设置”回到难度设置环节。


当玩家在记忆环节时点击暂停时,两边的牌堆会全部盖上,杜绝暂停倒计时记牌这种投机取巧的游戏方式。

游戏结束弹窗:

    <div class="all_popup" style="height: 80%; width: 50%;" show="{{ popup }}">
        <text class="title">{{ result }}</text>
        <button class="btn" onclick="restart">重新开始</button>
        <button class="btn" onclick="toset">返回设置</button>
    </div>


当玩家在限定时间内完成图片配对则会弹出游戏胜利弹窗,若游戏计时达到限定时间未完成图片配对则弹窗显示游戏超时失败。


点击“重新开始”在原先的难度设置上重新开始新一轮游戏;点击“返回设置”回到难度设置环节。

完成游戏页面的构建后,下面带大家详细讲讲游戏逻辑的具体实现代码。其实整个游戏逻辑的实现并不难,相信大家看完之后都有能力自己开发出简单的小游戏。

游戏时间区间设置


设置两个变量 MeTime 和 MaxTime 分别作为游戏记忆时间和游戏限定时间的默认值。

var MeTime = 15;
var MaxTime = 60;


用 timeset 函数改变这两个属性的值,在 hml 文件中对应的加减按钮分别传参(变量名和差值)。另外在修改语句外加一个执行判断用以限定时间的设置区间。

    timeset(time, alter) {
        if(time == "metime") {
            if((5 <= this.metime+alter) && (30 >= this.metime+alter)) {
                this.metime += alter;
            }
        }
        else {
            if((20 <= this.maxtime+alter) && (120 >= this.maxtime+alter)) {
                this.maxtime += alter;
            }
        }
    },


顶部计时器


事先声明两个重复计时器 setdown 和 setadd。

var setdown;
var setadd;


开始游戏进入记牌环节,重复计时器 setdown 从设置中获取变量 metime 的值作为记忆时间,倒计时直至为 0。之后清除该计时器,启动另一重复计时器 setadd。

    //倒计时记忆时间
    memory() {
        setdown = setInterval(()=> {
            this.thetime --;
            if(0 >= this.thetime) {
                //开启可点击,开始正向计时,
                clearInterval(setdown);
                this.tick = false;
                this.L_dab = false;
                this.R_dab = false
                //盖上图片
                for(var all=0; all<24; all++) {
                    this.cards[all].src = "unknown";
                    this.cards[all].dab = false;
                }
                this.timing();
            }
        }, 1000);
    },


重复计时器 setadd 开始正向计时,直至达到限定时间 maxtime 或是玩家完成游戏。

    //正向计时,监控匹配判断,超时判断,完成判断
    timing() {
        setadd = setInterval(()=> {
            this.thetime += 1;
            console.info(JSON.stringify(this.tempqueue));
            if(1 < this.tempqueue.length) {
                //执行判断
                this.cover();
            }
            if(12 <= this.score) {
                clearInterval(setadd);
                this.result = "游戏胜利!";
                console.info("游戏胜利!");
                this.popup = true;
                this.L_dab = true;
                this.R_dab = true;
            }
            if(this.maxtime <= this.thetime) {
                clearInterval(setadd);
                this.result = "超时失败……";
                console.info("超时失败……");
                this.popup = true;
                this.L_dab = true;
                this.R_dab = true;
            }
        }, 1000);
    },


游戏过程中点击计时器暂停游戏计时,清除当前的重复计时器,点击“继续游戏”重新启动对应的重复计时器接着计时。其中以标识符变量 tick 记录当前是处于牌组记忆环节还是游戏配对环节。

    //暂停游戏
    gamestop() {
        this.pause = true;
        if(true == this.tick) {
            for(var all=0; all<24; all++) {
                this.cards[all].src = "unknown";
            }
            clearInterval(setdown);
        }
        else {
            clearInterval(setadd);
        }
    },


随机打乱牌堆


设置两个循环分别将左右牌组各十二张图片随机打乱。

    //打乱牌堆
    mess_up() {
        var Lindex, Rindex;
        var temp;       //临时置换变量
        var ran;     //随机下标
        //打乱左边图标(0~11)
        for(Lindex=0; Lindex<12; Lindex++) {
            ran = Math.floor(Math.random()*12);
            temp = this.cards[Lindex];
            this.cards[Lindex] = this.cards[ran];
            this.cards[ran] = temp;
        }

        //打乱右边图标(12~23)
        for(Rindex=12; Rindex<24; Rindex++) {
            ran = Math.floor(Math.random()*12) + 12;
            temp = this.cards[Rindex];
            this.cards[Rindex] = this.cards[ran];
            this.cards[ran] = temp;
        }

        for(var all=0; all<24; all++) {
            this.cards[all].src = this.cards[all].index;
        }

        //启动倒计时
        this.memory();
    },


为了方便测试可以在日志上打印打乱后的牌组序列进行验证。

        var LLL = new Array;
        var RRR = new Array;
        //打印左边序号
        console.info("————左边————");
        for(var l=0; l<12; l++) {
            LLL.push(this.cards[l].index);
        }
        console.info(JSON.stringify(LLL));
        //打印右边序号
        console.info("————右边————");
        for(var r=12; r<24; r++) {
            RRR.push(this.cards[r].index);
        }
        console.info(JSON.stringify(RRR));


“盖上”图片


这里的“盖上”图片是指将原本的图片替换成统一的未知问号图片:
  • 在记忆准备环节点击暂停会“盖上”所有图片

  • 在倒计时结束进入游戏配对环节后,会“盖上”所有的图片

  • 在配对失败后会重新“盖上”图片


代码如下:
                //盖上图片
                for(var all=0; all<24; all++) {
                    this.cards[all].src = "unknown";
                    this.cards[all].dab = false;
                }


翻看图片


点击某个问号图片可以翻开显示,之后锁定这一边为不可点击状态,玩家只能点击翻开另一边牌组的某个图片进行配对。

    //翻牌
    turnover(index) {
        console.info("点击了" + index);
        if(this.cards[index].src != "unknown") {
            console.info("请翻别的牌");
            return;
        }
        this.tempqueue.push(index);
        this.cards[index].src = this.cards[index].index;
        this.cards[index].dab = true;
        if(index < 12) {
            this.L_dab = true;
        }
        else {
            this.R_dab = true;
        }
        if((true == this.L_dab) && (true == this.R_dab)) {
            this.L_dab = false;
            this.R_dab = false;
        }
    },


配对逻辑


配对逻辑判断置于重复计时方法中,每次翻看图片后会将其下标入队,计时器每秒都会监控队列长度,当队列长度大于 1 时,则两两一组执行配对判断。

当配对成功后配对成功计数器加一,配对失败则将这组图片重新“盖上”,再将队首的两个元素出队。当限定时间内计数器达到 12 时弹出游戏胜利弹窗。

    //重新盖牌
    cover() {
        if(this.cards[this.tempqueue[0]].index != this.cards[this.tempqueue[1]].index) {
            console.info("配对失败");
            this.cards[this.tempqueue[0]].src = "unknown";
            this.cards[this.tempqueue[0]].dab = false;
            this.cards[this.tempqueue[1]].src = "unknown";
            this.cards[this.tempqueue[1]].dab = false;
        }
        else {
            console.info("配对成功");
            this.score += 1;
        }
        this.tempqueue.splice(02);
    },


游戏环节切换


暂停游戏:

    //暂停游戏
    gamestop() {
        this.pause = true;
        if(true == this.tick) {
            for(var all=0; all<24; all++) {
                this.cards[all].src = "unknown";
            }
            clearInterval(setdown);
        }
        else {
            clearInterval(setadd);
        }
    },


继续游戏:

    //继续游戏
    resume() {
        this.pause = false;
        if(true == this.tick) {
            for(var all=0; all<24; all++) {
                this.cards[all].src = this.cards[all].index;
            }
            this.memory();
        }
        else {
            this.timing();
        }
    },


重新开始:

    //重新开始
    restart() {
        this.L_dab = true;
        this.R_dab = true;
        this.tempqueue = [];
        this.score = 0;
        this.popup = false;
        this.pause = false;
        this.gameset = false;
        this.tick = true;
        clearInterval(setdown);
        clearInterval(setadd);
        this.thetime = this.metime;
        for(var all=0; all<24; all++) {
            this.cards[all].src = "unknown";
            this.cards[all].dab = false;
        }
        this.mess_up();
    },


返回设置:

    //返回设置弹窗
    toset() {
        this.L_dab = true;
        this.R_dab = true;
        this.thetime = 0;
        this.tempqueue = [];
        this.score = 0;
        this.popup = false;
        this.pause = false;
        this.gameset = true;
        for(var all=0; all<24; all++) {
            this.cards[all].src = (all)%12 + 1;
            this.cards[all].dab = false;
        }
    }


希望这个小游戏能吸引更多的开发者加入鸿蒙应用开发的行列中来,共同学习,共同进步,一起为鸿蒙生态共建出力。


附件下载:

https://harmonyos.51cto.com/posts/10272


虎年春节  有奖话题

👇点击关注鸿蒙技术社区👇
了解鸿蒙一手资讯


求分享

求点赞

球在看

文章转载自鸿蒙技术社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论