[游戏开发]Unity红点系统_树实现

这篇具有很好参考价值的文章主要介绍了[游戏开发]Unity红点系统_树实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

0. 前言

刚好处理到红点系统的问题,就写个文章记录一下。本文的红点系统为一个结构,UI实现需要和红点运行逻辑剥离,防止过度耦合,现在就暂时不提及,后续在讲述。

1. 红点系统

红点是游戏中一种常见且重要的提醒方式,通常涉及到很多方面信息和界面的显示,如果不做成独立的系统的话,那么游戏逻辑将会杂乱散落在各个角落不便于开发维护。

(1)种类

红点通常是指有UI上那种带数字的小红标,或者单纯一个小红点。当然也不止这些,比如金矿建筑有无产出的信息,也可以用红点系统来处理,有的话显示个小金币图标在建筑头上。当然这个产出信息通常是和金矿建筑高度关联的,也会让金矿建筑自己来处理。这个可能要先考虑一下?

(2)结构

对于红点的信息来说,可能如下图:


主菜单--背包--道具页--新道具1
                     --新道具2
      --任务--新任务1
		    --新任务2

通常会呈现一个树状,的结构。

这个时候,子节点有一个点亮,父节应点亮。比如有”新道具1”,”主菜单”、”背包”、”道具页面”、”新道具1”应该都点亮,去引导玩家往去查看这个新道具。查看完之后,”新道具1”子节熄灭,其他父节点也应该重新检查是否点亮。

需要考虑的另一点是,叶节点可以自由控制点亮,父节点是否点亮由子节点决定,所有子节点熄灭,父节点应熄灭。(叶节点:树的末端节点,“新道具1”、“新道具2”、“新任务1”、"新任务2"为叶节点)。”道具页”是否点亮,是由有没有新道具来决定的。如果没有新道具,道具页自己亮了,只会给玩家提供错误的信息。若有需求是只让玩家看看这个道具页,比如道具页面更新了,那么应该另外设置一个子节点来处理。

(3)约定

这里我们约定一下红点由路径节点的值组成,并由“|”分隔,方便后续处理。
比如主菜单–任务–新任务1,这个红点查询时,应查询"menu|Task|newTask1"

2. 红点树

我们可以用树的数据结构来实现这个红点系统,那先来实现一个树结构

(1)树节点

所以是树节点的基础结构,值 + 子节点;

public class RedHitNode
{
    public int value;
    public List<RedHitNode> children = new List<RedHitNode>();
}

这里采用了List而不是Dictionary主要是考虑到Dictionary开销更大,而按照树结构,子节点通常不会太多,所以用List即可。另外作为红点的节点,我还希望:

  • 节点自己控制节点的增加和删除
  • 节点是否为叶子节点
  • 节点通过一个只读的key来互相区分(在同节点下)

所以给RedHitNode加上Key,以及Add,Remove,Find的功能,那么就得到

public class RedHitNode
{
    public readonly string Key;
    private List<RedHitNode> children = new List<RedHitNode>();
    public int Value = 0;
    public bool IsLeaf => children.Count == 0;

    public RedHitNode(string key)
    {
        this.Key = key;
    }

    public RedHitNode Add(string key)
    {
        RedHitNode node = null;
        int index = IndexOf(key);
        if (index == -1)
        {
            node = new RedHitNode(key);
            children.Add(node);
        }
        return node;
    }

    public RedHitNode Remove(string key)
    {
        RedHitNode node = null;
        int index = IndexOf(key);
        if (index != -1)
        {
            // 删除对应节点
            // 因为无序,所以做了位置互换,防止数据过多复制
            children[index] = children[children.Count - 1];
            children.RemoveAt(children.Count - 1);
        }
        return node;
    }

    public RedHitNode Find(string key)
    {
        RedHitNode node = null;
        int index = IndexOf(key);
        if (index != -1)
        {
            node = children[index];
        }
        return node;
    }

    protected int IndexOf(string key)
    {
        for (int i = 0; i < children.Count; i++)
        {
            if (children[i].Key == key)
            {
                return i;
            }
        }
        return -1;
    }
}

Remove作为和其他List移除略有不同的是,先将数据移动到末端再删除。因为作为红点数据,其子节点是无序的,所以可以通过这种方式,防止数据删除后,后面的数据往前复制。

(2)树_增删查

作为树,应该具备基本的根节点Root,以及Insert,Delete,Search处理。而且因为作为红点树,其节点值应该完全由自己控制,所以节点的插入删除不对外暴露。

public class RedHitNode
{
	private RedHitNode root;
	private RedHitNode Insert(string[] keys){}
	private RedHitNode Delete(string[] keys){}
	private RedHitNode Search(string[] keys){}
}

首先Search就不断查找子节点直到找到,Delete也差不多,找到了就删除。实现如下

private RedHitNode Delete(string[] keys)
{
    RedHitNode cur = null;
    if (keys != null && keys.Length != 0)
    {
        // 查找父节点
        cur = root;
        int last = keys.Length - 1;
        for (int i = 0; i < last && cur != null; i++)
        {
            cur = cur.Find(keys[i]);
        }
        // 如果找到父节点,尝试删除叶节点
        if (cur != null)
        {
            cur = cur.Remove(keys[last]);
        }
    }
    return cur;
}

private RedHitNode Search(string[] keys)
{
    RedHitNode cur = null;
    if (keys != null && keys.Length != 0)
    {
        // 查找节点
        cur = root;
        for (int i = 0; i < keys.Length && cur != null; i++)
        {
            cur = cur.Find(keys[i]);
        }
    }
    return cur;
}

对于插入处理Insert,我们插入时可能和常规的插入操作有点不同。

  • 根节点外的叶子节点不应该再可以加入子节点。比如“主菜单–任务–新任务1”,那么“新任务1”的节点后面不应该再有子节点,才能符合红点树的基础规则。
  • 另外对于已经有的节点,再次插入失败时,直接返回原有节点,方便操作。

所以做了两个而外参数,普通插入直接调用Insert,而有额外需求的再传参限制。实现如下

private RedHitNode Insert(string[] keys)
{
    return Insert(keys, false, false);
}

private RedHitNode Insert(string[] keys, bool OnlyInsertOnBranchNote, bool getNoteExist)
{
    RedHitNode cur = null;
    if (keys != null && keys.Length != 0)
    {
        // 查找节点
        RedHitNode parent = root;
        cur = root;
        int i;
        for (i = 0; i < keys.Length && cur != null; i++)
        {
            parent = cur;
            cur = cur.Find(keys[i]);
        }
        if (cur == null)
        {
            // 节点未找到
            if (!OnlyInsertOnBranchNote || cur != root || !parent.IsLeaf)
            {
                cur = parent;
                for (i = i - 1; i < keys.Length; i++)
                {
                    cur = cur.Add(keys[i]);
                }
            }
        }
        else
        {
            // 节点已存在,
            if (!getNoteExist)
            {
                cur = null;
            }
        }
    }
    return cur;
}

这样的话,树的基础结构就实现了。后面做一下红点树的功能。

(3)树_红点处理

作为红点树,打开关闭红点,查看红点是必须功能。另外当红点信息有改变的时候,是需要播报出来的,这里单纯做一个回调在方便处理。也就是可以表现为

public class RedHitTree
{
	private Action<string, int> changeNoteCallBack;
    private RedHitNode root;
    private char splitChar = '|';
    
    public RedHitTree(Action<string, int> changeNoteCallBack)
    {
        this.root = new RedHitNode("");
        this.changeNoteCallBack = changeNoteCallBack;
    }
    
    public bool HasRedHit(string redHit){}
    public int GetRedHitValue(string redHit){}
    public bool TurnOnRedHit(string redHit){}
    public bool TurnOffRedHit(string redHit){}
    
	private RedHitNode Insert(string[] keys){}
	private RedHitNode Delete(string[] keys){}
	private RedHitNode Search(string[] keys){}
}

接下来依次实现如下

public bool HasRedHit(string redHit)
{
    return GetRedHitValue(redHit) > 0;
}

public int GetRedHitValue(string redHit)
{
    RedHitNode node = Search(redHit.Split(splitChar));
    int result = 0;
    if (node != null)
    {
        result = node.Value;
    }
    return result;
}

public bool TurnOnRedHit(string redHit)
{
    string[] keys = redHit.Split(splitChar);
    bool flag = false;
    RedHitNode node = Insert(keys, true, true);
    if (node != null && node.IsLeaf)
    {
        if (node.Value == 0)
        {
            ChangeNotesValue(keys, 1);
        }
        flag = true;
    }
    return flag;
}

public bool TurnOffRedHit(string redHit)
{
    string[] keys = redHit.Split(splitChar);
    bool flag = false;
    RedHitNode node = Search(keys);
    if (node != null && node.IsLeaf && node.Value > 0)
    {
        if (node.Value == 1)
        {
            ChangeNotesValue(keys, -1);
        }
        flag = true;
    }
    return flag;
}

上面我们在更改红点数据的时候使用到了ChangeNotesValue,这个需要从根节点一直改到叶子节点,并每次更改时做一个回调。如下:

private void ChangeNotesValue(string[] keys, int value)
{
    RedHitNode cur = root;
    string str = "";
    for (int i = 0; i < keys.Length; i++)
    {
        cur = cur.Find(keys[i]);
        if (cur == null)
        {
            break;
        }
        cur.Value += value;
        // 改变
        str += keys[i];
        changeNoteCallBack(str, cur.Value);
    }
}

那么到这里的时候,树就完成了!!

3. 封装、检查

(1)检查

检查一下,树能否按预期正常运行

  • 红点开启是否正常
TurnOnRedHit("menu|Task|newTask1"); //True
HasRedHit("menu|Task|newTask1");    //True
HasRedHit("menu|Task|newTask2");    //False
HasRedHit("menu|Task"); //True
HasRedHit("menu|Knapsack"); //False
  • 子节点有一个点亮时,父节点点亮,子节点全熄灭时,父节点熄灭
TurnOnRedHit("menu|Knapsack|Prop|newProp1");    //True
TurnOnRedHit("menu|Knapsack|Prop|newProp2");    //True
HasRedHit("menu|Knapsack"); //True
TurnOnRedHit("menu|Knapsack|Prop|newProp1");    //True
HasRedHit("menu|Knapsack"); //True
TurnOnRedHit("menu|Knapsack|Prop|newProp2");    //True
HasRedHit("menu|Knapsack"); //False
  • 父节点是否可以自由控制
TurnOnRedHit("menu|Task|newTask1"); //True
TurnOffRedHit("menu|Task"); //False
TurnOffRedHit("menu|Task|newTask1");	//True

ok,符合预期咯。

(2)UGF封装为组件

因为用的是UGF的框架,所以用UGF做了一下封装。

using GameFramework;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityGameFramework.Runtime;
using System;
using EventArg;

namespace GDT
{
    /// <summary>
    /// 自定义红点组件
    /// </summary>
    public class RedHitComponent : GameFrameworkComponent
    {
        private RedHitTree tree;
        private RedHitChangeEventArgs arg;

        protected override void Awake()
        {
            base.Awake();

            tree = new RedHitTree(OnRedHitChange);
            arg = new RedHitChangeEventArgs();
        }

        public bool HasRedHit(string redHit)
        {
            return tree.HasRedHit(redHit);
        }
        
        public int GetRedHitValue(string redHit)
        {
            return tree.GetRedHitValue(redHit);
        }

        public void TurnOnRedHit(string redHit)
        {
            if (!tree.TurnOnRedHit(redHit))
            {
                Debug.LogError("Failed to turn on the redHit, redHit:" + redHit);
            }
        }

        public void TurnOffRedHit(string redHit)
        {
            if (!tree.TurnOffRedHit(redHit))
            {
                Debug.LogError("Failed to turn off the redHit, redHit:" + redHit);
            }
        }

        public void OnRedHitChange(string key, int value)
        {
            arg.Key = key;
            arg.Value = value;
            GameEntry.Event.Fire(this, arg);
        }
    }
}

4. 结束咯

这样的话我们的红点系统就完成了。另外我在考虑用字典的方式来做这个红点系统,后面有做的话,可能再处理吧。最后,参考文章:https://linxinfa.blog.csdn.net/article/details/121899276文章来源地址https://www.toymoban.com/news/detail-739902.html

到了这里,关于[游戏开发]Unity红点系统_树实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity游戏开发:场景切换的实现

    在unity中可以将不同场景的背景和道具放置在不同的Scene当中,通过对Scene的加载和卸载来实现场景之间的切换。同时创建一个基础场景(Control Scene)来对整个游戏系统进行管理,在基础场景(Control Scene)中不放置背景图片或者游戏道具而只添加各种控制单元和Canvas。 在场景

    2024年02月15日
    浏览(33)
  • 游戏开发小结——Unity 2.5D 平台游戏:跳墙(新输入系统)

    到目前为止,在这个项目中,我已经介绍了如何创建具有双跳功能的物理基础角色控制器。我添加了动态平台、收藏品和事件驱动的电梯。 今天我将回到角色控制器脚本(Player)并添加墙跳功能。这将允许玩家检测墙壁的表面并从一堵墙跳到另一堵墙。 场景设置 我的场景有

    2024年02月01日
    浏览(32)
  • Unity开发日记-进入游戏按钮和退出游戏按钮的逻辑实现

    本篇文章只是总结一下UGUI中进入游戏和退出游戏的逻辑代码的两种实现方式,方便以后查阅,以后如果有其他的方法也会随时更新(Unity版本为2021) 首先是在UI的Image(背景图)上挂载脚本 代码如下 两种方法各有各的好。方法一代码量少但项目大的时候管理比较麻烦;方法

    2024年02月11日
    浏览(40)
  • unity使用PhotonEngine实现多人联机游戏开发(一)

    先来了解一下PhotonEngine(光子引擎),这是德国ExitGame公司开发的网络引擎,photonengine简单易上手,很多游戏公司开发的网络游戏都是使用的这个。这个网络引擎里面包括了PhotonCloud(光子云)、photonServer(光子服务器)、PhotonQuantum(确定性量子引擎)、PhotonVoice(光子语音)

    2024年02月07日
    浏览(40)
  • Unity 游戏开发、01 基础知识大全、简单功能脚本实现

    Unity默认窗口布局 Hierarchy 层级窗口 Scene 场景窗口,3D视图窗口 Game 游戏播放窗口 Inspector 检查器窗口,属性窗口 Project 项目窗口 Console 控制台窗口 恢复默认布局 Window | Layouts | Default 调大页面字体 Preference | UI Scaling 新项目默认创建了 SampleScene 场景 {摄像机,平行光} SampleScen

    2024年02月09日
    浏览(52)
  • Unity 游戏开发、01 基础篇 | 知识大全、简单功能脚本实现

    Unity默认窗口布局 Hierarchy 层级窗口 Scene 场景窗口,3D视图窗口 Game 游戏播放窗口 Inspector 检查器窗口,属性窗口 Project 项目窗口 Console 控制台窗口 恢复默认布局 Window | Layouts | Default 调大页面字体 Preference | UI Scaling 新项目默认创建了 SampleScene 场景 {摄像机,平行光} SampleScen

    2024年02月09日
    浏览(45)
  • 【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏4(附项目源码)

    本期紧接着上一篇,本期主要内容是实现商店、购买、出售、升级等功能。 新增TurretSettings 配置不同炮塔参数 绘制商店UI 新增TurretCard,渲染商品数据,如果前面有绘制价格文本,还可以渲染价格文本,这里我就偷懒不弄了 挂载脚本,并配置 新增TurretShopManager,商店管理器

    2024年01月21日
    浏览(41)
  • 【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏1(附项目源码)

    当今,塔防游戏已经成为游戏市场上备受欢迎的一类游戏类型。《保卫萝卜》作为其中的经典之作,深受玩家喜爱。本项目旨在基于《保卫萝卜》的玩法和特点,开发一个Unity2D塔防游戏,让玩家可以在游戏中体验到精彩的策略对抗与刺激的关卡挑战。 本项目将通过Unity引擎进

    2024年02月04日
    浏览(42)
  • 【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏5(附项目源码,完结)

    本期是本项目的最后一篇,主要内容是配置环境、生成不同敌人、结束重开。 我用的环境素材 https://assetstore.unity.com/packages/2d/environments/2d-sugar-world-asset-pack-urp-256585

    2024年02月05日
    浏览(42)
  • Unity之Photon PUN2开发多人游戏如何实现组队功能

    Photon Unity Networking 2 (PUN2) 是一款基于Photon Cloud的Unity多人游戏开发框架。它提供了一系列易于使用的API和工具,使开发者可以快速构建多人戏,并轻松处理多人游戏中的网络同步、房间管理、玩家匹配等问题。 我们在查看Pun2的Demo时,会发现Demo中自带了一个简易的PhotonTeam和

    2024年02月11日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包