<<鸽鸽小羊>>,4小时开发羊了个羊核心逻辑(附关卡编辑器核心逻辑,适合新手学习)

源码现已上架,点击购买

前言

上个月,突然一款小游戏《羊了个羊》突然爆火,本来并没有啥兴趣的,但是突然我们团队策划找上

我,问我开发一款羊了个羊,难不难?


突然说想开发这款羊来蹭下热度,开发一款属于我们的羊游戏,并且带上ikun的元素,通过此举来表达我们对鸽鸽的爱慕之情;

本想拒绝,但是作为一个ikun,弘扬鸽鸽义不容辞,加上策划和美术如此热情,那我就说,先做个demo出来把。


于是第二天,趁着公司没啥事,就开始手撸代码,于是乎,一个简单的demo就出来了,当时的画风还是这样的。
06a3189785de698c6bb713d418547c33
该说不说,是不是有点像羊的样子了,现在,让我们看下核心代码具体怎么实现的把

Cocos3.6.x现已推出,欢迎大家学习 点击学习

核心实现逻辑

首先,我们需要构建卡片数据,用来生成我们的卡片数据,一份卡片数据大概长这样(最初我是照着羊,
一个一个手动填写的)

image

cards是一个数组,装载着每张卡片的数据

其中id表示卡片的唯一标识,gridx和gridy表示卡片的网格坐标,当然也可以用小数点,生成到Cocos场

景做个位置转换就行了.有了这份数据后,我们就可以生成场景的卡片了。实现逻辑:

/**根据关卡数据里卡牌数据加载卡牌并初始化卡牌 */

        for (let i = 0; i < level.cards.length; i++) {

            let card = level.cards[i];

            // console.log(card);

            let node = cc.instantiate(this.item_card);

            this.content.addChild(node, card.index);

            let com = node.getComponent(item_card);

            com.init(card.id, card.gridx, card.gridy, card.index, Game.getRandType());

            node.setPosition(card.gridx * GRID_WIDTH, card.gridy * GRID_HEIGHT);

            this._allItems.push(com);

            node.on(cc.Node.EventType.TOUCH_START, this.onClickItem, this);

        }

大家有注意到,卡片数据还有一份index的字段我们没有介绍,index代表卡片的层级关系,

用来表示卡片的遮挡关系,index相对较大的卡片总在index较小的卡片之上,

如果底下的卡片跟上面的卡片位置有重叠,那么底下的卡片就会变灰,不可点击。具体实现逻辑:

    /**
     * 判断桌面是否有新卡牌是否要冒泡,在上面显示,是则由灰色变白,可点击
     */
    public openNew() {

        for (let i = 0; i < this._allItems.length; i++) {
            let isCanOpen: boolean = true;
            for (let j = 0; j < this._allItems.length; j++) {
                if (i == j) {
                    continue;
                }
                /**判断卡片间层级关系,并判断卡片距离 */
                if (this._allItems[i].index < this._allItems[j].index) {
                    if (Math.abs(this._allItems[i].gridx - this._allItems[j].gridx) < 1 && Math.abs(this._allItems[i].gridy - this._allItems[j].gridy) < 1) {

                        isCanOpen = false;
                    }
                }
            }
            if (isCanOpen) {
                this._allItems[i].open();

            } else {
                this._allItems[i].close();
            }
        }
    }

另外,为了避免Cocos渲染错乱,我们在添加卡片的时候,还需要将index作为节点的z序传进去

   this.content.addChild(node, card.index);

以上就是我花四小时的想法,并且实现的逻辑,其他的逻辑比较简单,就不列出,有兴趣的朋友可以留言探讨,另外,为了方便策划,后续我还开发了关卡编辑器,好友排行榜的功能。

关于关卡编辑器

很多开发者,不懂怎么做关卡编辑器,只能让策划配表来实现卡片数据,但是配表有个弊端,就是无法实时预览,对策划很不友好

关卡编辑器其实逻辑也不难,无非就是如何序列化与反序列化的实现。
我们先声明关卡数据结构

export class Level {

    // randNum: number[];

    // cards: ICard[];

    public randNum: string[] = [];

    public cards: ICard[] = [];

    public level: number = 0;

    public difficulty: number = 0;

    public reward: number = 0;

}

export interface ICard {

    id: number;

    gridx: number,

    gridy: number,

    index: number,

}

其中最重要的还是cards数组,和刚刚加载的卡片数据逻辑是一样的,其他的关卡数据,无非是一些奖励,难度等配置。根据需求配置,先声明Level类型的实例对象

   private _level: Level = new Level();

此后,我们每创建一张牌(具体数据可以通过editorBox输入,或者用鼠标事件实现),就让他在界面显示,并将数据push进我们的level字段中。

/**

     * 创建卡牌

     * @param data 卡片数据

     */

    public createCard(data: ICard) {

        console.log(data);

        if (this.canCanCreate(data)) {

            let node = cc.instantiate(this.item_card);

            let com = node.getComponent(item_card);

            com.init(data.id, data.gridx, data.gridy, data.index, Math.floor(Math.random() * 10));

            node.setPosition(data.gridx * GRID_WIDTH, data.gridy * GRID_HEIGHT);

            com.initIndex(data.index);

            this._allItems.push(com);

            this.content.addChild(node, data.index);

            data.id = data.id;

            node.on(cc.Node.EventType.TOUCH_START, this.onClickCard, this);

            this._level.cards.push(data);

            this._preCreateCard = cc.v2(data.gridx, data.gridy);

            this.openNew();

            this.initNum();

        } else {

            // this.showToast();

        }

    }

等到我们创建差不多完成的时候,只需要将json数据下载保存为json格式的文本保存到本地,就大功告成了

public onClickCreate() {

        if (this._level.cards.length % 3 !== 0) {

            alert('卡片数必须为3的倍数才能保证消除');

            return;

        }

        let str = this.edit_rand.string;

        str = str.replace(/,/g, ',');

        this._level.randNum = str.split(',');

        this._level.level = Number(this.edit_level.string) || 0;

        this._level.difficulty = Number(this.edit_diff.string) || 0;

        this._level.reward = Number(this.edit_reward.string) || 1;

        this.saveAs(`level_${this._level.level}.json`, this._level);

    }

其中saveAs为js保存数据下载到本地的方法,这个百度就可以百度到。
保存后文件就长这样

相信大家都会用bundle.load()的方法加载json文件把?

那么,现在数据已经序列完成了,如何读取数据并且反序列化呢,其实也很简单

public onClickLoad() {

        var inputObj = document.createElement('input')

        inputObj.setAttribute('id', '_ef');

        inputObj.setAttribute('type', 'file');

        inputObj.setAttribute("style", 'visibility:hidden');

        document.body.appendChild(inputObj);

        //选中文件时触发的方法

        let self = this;

        inputObj.onchange = () => {

            if (window.FileReader) {

                var file = inputObj.files[0];

                var reader = new FileReader();

                reader.onload = function (event: any) {

                    self.onLoadFile(event.target.result)

                }

                reader.readAsText(file);

            }

        };

        inputObj.click();

    }

这段方法是点击后,会弹一个框让你选择文件的功能(之前是加载,现在是读取,同样可以百度到),其中后面的event.target.result就是我们的json文本内容了,这时候我们只要稍加解析就好了

//加载本地数据(反序列化)

    private onLoadFile(text) {

        try {

            this._allItems.length = 0;

            let json: Level = JSON.parse(text);

            this._level.level = json.level || 0;

            this._level.randNum = json.randNum || [];

            this._level.cards.length = 0;

            this._level.difficulty = json.difficulty || 0;

            this._level.reward = json.reward || 0;

            this.content.removeAllChildren();

            for (let i = 0; i < json.cards.length; i++) {

                const data = json.cards[i];

                this.createCard(data);

            }

            this.edit_level.string = this._level.level.toString();

            this.edit_rand.string = this._level.randNum.join(',');

            this.edit_diff.string = this._level.difficulty.toString();

            this.edit_reward.string = this._level.reward.toString();

        } catch (error) {

            console.error(error);

        }

    }

初始下level数据,并且将cards数据解析出来就好了。到此,关卡编辑器的所有核心逻辑也全部完成了

关于微信开放域(好友排行榜)

好友排行榜这块,可以参考下皮皮的帖子点击跳转
我是全程仿他的帖子来做的。。。好吧,我也是第一次弄这个

我们先建立排行榜的ui


如图所示,其中在需要显示子域数据的地方,如图main节点,添加SubContextView(注意main的size,

这个很重要,一会会用到),一会,我们在子域里,将会在这里添加Scrollview组件。

在关键时候,比如玩家通关,调用微信上传数据的接口

    public uploadScore(value: number) {
        if (this.channel == Channel.WECHAT) {
            console.log('[uploadScore]', value);
            wx.setUserCloudStorage({
                KVDataList: [{
                    key: 'score',
                    value: value.toString()
                }],
                success: () => {
                    console.log('[setScore]', 'success');
                },
                fail: () => {
                    console.log('[setScore]', 'fail');
                }
            });
        }

    }

我们打开ui脚本 添加如下代码

    protected onLoad() {
        this.getRank();
    }
    protected onClose() {
        this.node.destroy();
    }
    protected onDestroy() {

    }
    /**
     * 获取排行榜
     */
    public getRank() {
        wx.postMessage({
            event: 'getRank'
        });
    }

其中wx.postMessage是主域向子域发送获取排行榜的消息,其实这里第一次打开时,子域是接收不到消

息的,因为这时子域还没有创建完成,但是为什么要加呢,主要是为了重复进入时,刷新用户的数据

有测试过,在子域onEanble函数调用getRank函数,但是onEanble并没有执行到,不知道为啥。

点击构建,选择微信小游戏


开放数据域代码目录写wxSubContext,至于为啥是wxSubContext呢,等下会提到

准备好这些,其他东西就跟主域没关系了,现在,打开陈皮皮子域工程

陈皮皮- 微信好友排行榜通用模板地址

打开唯一的场景,还记得我们刚刚说的main尺寸嘛,这里唯一要注意的点就是,子域场景的size,一定要跟主域中SubContextView组件的size一样,不然会导致变形。

代码皮皮都帮我们写好了,点击构建就好了


要注意的是,这里的小游戏名称,跟我们刚刚主包构建时候填写的小游戏开放数据域目录,是对应的

发布平台是微信小游戏开放数据域,发布路径是主包的构建好的wechatgame目录下

两个包都构建完成,这时我们在运行微信开发者工具,就能看到好友排行了

如果没有数据,又没有报错,可能是你还没有上传数据,哈哈!

最后

我是ikun,我为鸽鸽代言,守护最好的坤坤,你干嘛哦哎呦!我与小黑子不共戴天
点击购买源码

4赞

感谢楼主的分享!这个资源目前是 Store 上《羊了个羊》游戏玩法中,第一个带微信排行榜的 :+1:

1赞

妙啊!~~~~~

来捧个场啊

欢迎

离1小时开发了个羊了个羊不远了。

请买 那首歌的版权,然后作为背景音乐

他甚至加了一行把中文逗号换成英文逗号的语句,我哭了,一看就是被策划深深伤害过的人

1赞

基操.勿六