[开发者心得] 【数据结构】关于简单的红点树的使用

[复制链接]
1060 |2
醉歌离人 发表于 2023-3-31 11:43:14 | 显示全部楼层 |阅读模式
本帖最后由 醉歌离人 于 2023-11-1 14:25 编辑


在游戏开发中经常会遇到多层级的UI上的红点的问题。例如,需要提醒邮件,邮件内容会有一个红点,邮件图标也有一个红点,而邮件图标的红点是由邮件内容的红点决定的,这样就形成多层级的关系。


看这段视频,这块我们会发现右上角menu的红点会跟随界面中角色,邮箱背包的按钮的红点而变化,当且仅当所有下级按钮的红点全部消失时menu按钮的红点才会消失。我们下面就会讲如何实现这一功能。



这种情况下子节点一定会影响父节点的红点的显示情况,这个时候我们就可以用树来控制这种层级关系,每一个节点会存储一个子节点红点的数量+自身是否显示红点的信息来控制当前节点是否该显示红点,下面是对于红点树方法的介绍与代码。

先建下树。

    /**
     * 用来存储树名及对应的节点信息
     */
    public treeMap: Map<string, Map<number, Link>> = new Map();

    /**
     * 创建一棵树
     * @param treeName 树名
     * @param redPointUI 根节点的红点的UI图片
     */
    creatTree(treeName: string, redPointUI: mw.Image) {
        this.treeMap.set(treeName, new Map());
        //根节点  根节点的父节点
        this.insertNode(treeName, 0, -1, redPointUI);

    }

这里时Link存储的信息


class Link {
    constructor(public preNode: number, public RedPointUI: mw.Image, public value: number = 0) {

    }

}




这里我们需要用一条链表来存储该单个点的信息

我们可以看到建树的时候会要求插入一个根节点,所以下面我们要插入以及删除一个节点信息。先看代码



    /**
     * 插入树的节点
     * @param curNode
     * @param preNode 父节点编号(根节点编号为0)
     * @param redPointUI
     */
    public insertNode(treeName: string, curNode: number, preNode: number, redPointUI: mw.Image) {
        if (!this.checkTree(treeName)) {
            return;
        }
        let tree = this.treeMap.get(treeName)
        let treeNode = new Link(preNode, redPointUI);
        tree.set(curNode, treeNode);
    }

    /**
     * 检查这棵树是否存在
     * @param treeName
     */
    private checkTree(treeName: string): boolean {
        if (!this.treeMap.has(treeName)) {
            console.error("尚未创建这棵树");
            return false
        } else {
            return true
        }



    /**
     * 删除节点(只能时叶子节点)
     * @param treeName 被删除节点的树名
     * @param curNode 被删除的节点
     * @returns
     */

    public deleteNode(treeName: string, curNode: number) {
        if (!this.checkTree(treeName)) {
            return;
        }
        let tree = this.treeMap.get(treeName)

        for (let e of tree) {
            if (e[1].preNode === curNode) {
                console.log(`删除的节点不是叶子节点`);
                return;
            }
        }
        this.changeRedPointCondition(treeName,curNode,false);

        tree..delete(curNode);
    }

这段代码就是将一个节点信息初始化并连接到指定父节点

这里我们可以看到插入节点时需要用到当前节点的编号以及父节点的编号,推荐非叶子节点使用枚举会更加清晰一点。
想一下是不是平时玩游戏删除节点的话一般只会删除叶子节点。
所以该方法只能删除叶子节点,如果有特殊需要删除非叶子节点  则需要注意遍历该树将所有子节点删除或是List多存一个字节点的数组。


-------------------------------------------------    建树完成  ------------------------------------------------

OK树建立的基本的操作就是完成了,接下来我们需要改变红点的状态。   注意:大多数情况下我们依旧只会修改叶子节点的数据



    /**
     * 改变红点状态,会递归调用改变所有父节点状态
     * @param curNode 当前节点编号
     * @param isVisible 当前节点显示还是隐藏
     * @param callBack 隐藏或显示当前红点的方法
     */
    public changeRedPointCondition(treeName: string, curNode: number, isVisible: boolean) {
      


        if (!this.checkTree(treeName)) {
            return;
        }
        let tree = this.treeMap.get(treeName)

        if (!tree.has(curNode)) {
            console.error("错误红点树内尚无该节点");
            return;
        }
        //遍历该树的信息
        for (let e of tree) {
            if (e[1].preNode === curNode) {
                console.log(`修改的节点不是叶子节点`);
                return;
            }
        }
        //显隐相同则无需修改
        if (isVisable === tree.get(curNode).RedPointUI.visible) {
            return;
        }
        let value = isVisable === true ? 1 : -1;
        //递归修改所有父节点
        this.recursionPar(treeName, curNode, value);

    }

修改叶子节点后我们需要同时修改所有父节点的状态,我们使用递归来遍历所有的父节点以后该他们的状态。


    /**
     * 对父节点进行递归
     * @param curNode
     * @param value
     * @returns
     */
   
    private recursionPar(treeName: string, curNode: number, value: number) {

        if (!this.checkTree(treeName)) {
            return;
        }
        let tree = this.treeMap.get(treeName)

        this.claValue(treeName, curNode, value);
        //到根节点了
        if (curNode === 0) {
            return;
        }
        this.recursionPar(treeName, tree.get(curNode).preNode, value);

    }



这里看到还有一点需要判断自身是否需要显示红点,这里就是我们开头提到的计数。即,子节点每有一个红点,他的所有父节点的计数就要+1。只要计数>0我们就可以判断它有子节点的红点是显示状态,如果是==0则为隐藏状态。看代码




    /**
     * 判断当前节点是否符合显隐条件
     * @param curNode
     * @param value
     */
    private claValue(treeName: string, curNode: number, value: number) {
        if (!this.checkTree(treeName)) {
            return;
        }
        let tree = this.treeMap.get(treeName)

        tree.get(curNode).value += value
        if (tree.get(curNode).value > 0) {
            tree.get(curNode).RedPointUI.visibility = mw.SlateVisibility.Visible;
        } else {
            tree.get(curNode).RedPointUI.visibility = mw.SlateVisibility.Hidden;
        }
        console.log("当前节点" + curNode + "value is " + tree.get(curNode).value);

    }












回复

使用道具 举报

今晚月亮缺席 发表于 2023-3-31 16:13:05 | 显示全部楼层
删除节点那是不是少了一步递归改变父节点的value
回复

使用道具 举报

醉歌离人楼主 发表于 2023-3-31 18:27:23 | 显示全部楼层
今晚月亮缺席 发表于 2023-3-31 16:13
删除节点那是不是少了一步递归改变父节点的value

感谢提醒,因为我想的话一般删除节点了的话应该是红点是处于隐藏状态的,然后现在加上代码把这个节点调整为隐藏状态了更保险一点
回复

使用道具 举报

热门版块
快速回复 返回顶部 返回列表