当前位置: 首页 > news >正文

unity 红点树

红点的实现,一般都是有层级关系的,子节点的红点亮起来的时候,要把父节点的红点也给点亮,当所有子节点红点都隐藏的时候,父节点的红点也需要跟着进行隐藏。红点的逻辑在设计的时候,尽量与系统功能进行分离,我们在实现红点系统的时候,只用关心最后面那个节点也就是叶子节点的显示隐藏状态,父节点会根据子节点的状态自动进行刷新处理。

在这里插入图片描述
如图所示,当我们要实现上面这个功能的红点时候,我们可以把每一层显示的红点抽出一个红点对象 RedDotModel,然后构建一个红点树,这个红点树可以通关配置进行初始化,配置内容需要包含红点类型,红点拥有的子节点,叶子节点不需要配置子节点.
红点model中保存的有自己父节点对象和所有子节点对象
红点简单配置如下

local redDotConfig = {
    [1] = { name = "功能A 1级界面", serverRedModelName = "", clientRedModelName = "", redType = -1, functionList = {}, childrens = {2}},
    [2] = { name = "功能A 2级界面", serverRedModelName = "", clientRedModelName = "", redType = -1, functionList = {}, childrens = {3,4}},
    [3] = { name = "功能A 3级界面A", serverRedModelName = "", clientRedModelName = "ChallengeNewChapterRedDotModel", redType = 3, functionList = {}, childrens = {}},
    [4] = { name = "功能A 3级界面B", serverRedModelName = "", clientRedModelName = "ChallengeRewardRedDotModel", redType = 2, functionList = {}, childrens = {}},
    [5] = { name = "功能B 1级界面", serverRedModelName = "", clientRedModelName = "", redType = -1, functionList = {}, childrens = {6}},
    [6] = { name = "功能B 2级界面", serverRedModelName = "DayTaskFinishRedDotModel", clientRedModelName = "", redType = 1, functionList = {}, childrens = {}},
}

红点配置映射到红点集合,

function RedDotManager:init(serverRedModels)
    ---@type RedDotModel[]
    self.redModels = {}
    self.bindList = {}

    ---初始化红点列表
    for id, v in pairs(redDotConfig) do
        local redModel
        if v.clientRedModelName and v.clientRedModelName ~= "" then
            local redId = id
            printError(v.clientRedModelName)
            ---客户端红点需要重写一个继承RedDotModel的脚本,处理红点显示逻辑
            local redModelClas = require("models.redDot."..v.clientRedModelName)
            redModel = redModelClas.new(id, v)
        else
            redModel = RedDotModel.new(id, v)
        end
        self.redModels[id] = redModel
    end

    local leafRedModes = {}  --叶节点红点model列表
    ---初始父子级关系
    for i,redModel in ipairs(self.redModels) do
        local childs = redModel.config.childrens
        if childs and next(childs) then
            for _,childId in ipairs(childs) do
                if self.redModels[childId] then
                    local childModel = self.redModels[childId]
                    redModel:addChild(childModel)
                else
                    printError("子节点的配置有缺失,redId = " ..redModel.id..", childId = " .. childId)
                end
            end
        else
            table.insert(leafRedModes, redModel)
        end
    end
 end

在UI界面中,我们先从红点管理器中获取当前界面红点对象,然后把红点对象绑定到红点对象身上,当红点数据发生的时候,自动刷新关联的红点G阿么Object对象的显示隐藏状态

	local gob = UnityEngine.GameObject.New()
	gob.name = "1级界面";
	gob.transform.position = UnityEngine.Vector3.New(1,1,1)
	local redModel = RedDotManager:getRedModelById(1)
	RedDotManager:bind(gob, redModel)

完整代码如下

红点model基类

RedDotModel = class("RedDotModel")
---@class RedDotModel
local RedDotModel = RedDotModel
---RedDotModel主要内容
---1 保存子节点列表 和 父节点对象
---2 通关setNum方法设置红点数量的时候,通关回调函数刷新界面显示
function RedDotModel:ctor(id, cfg)
	self.id = id
	self.config = cfg
	---@type RedDotModel[] 子节点列表
	self.childs = {}
	---@type function[] 回调函数集合
	self.callbacks = {}
	self.num = 0
end


function RedDotModel:onInit()
	self:refreshNum()
end

---添加子节点
function RedDotModel:addChild(child)
	table.insert(self.childs, child)
	child:_addParent(self)
end



---@param trarget GameObject
---@param callback function
function RedDotModel:addCallback( trarget, callback )
	self.callbacks[trarget] = callback
end

function RedDotModel:removeCallback(target)
	if self.callbacks[target] then
		self.callbacks[target] = nil
	end
end		

---移除子红点(通常功能红点是不需要移除的,但是如果红点对象是某个武器,武器分解后,这个武器相关的红点是需要移除的)
---@param child RedDotModel
function RedDotModel:removeChild(child)
	local index = table.indexof(self.childs, child)
	if index and index > 0 then
		table.remove(self.childs, index)
		if #self.childs > 0 then
			self:refreshNum()
		else
			if self.parent then
				self.parent:removeChild(self)
				self.parent:refreshNum()
			end
		end
	end
	child:onDestroy()
end

function RedDotModel:removeAllChild()
	for i,child in ipairs(self.childs) do
		self:removeAllChild(child)
	end
end

function RedDotModel:getChildById(redId)
	for i,model in ipairs(self.childs) do
		if model.id == redId then
			return model
		end
	end
	
end

---设置红点数量,只用于刷新没有父节点的对象,父节点的num通关通过子节点自动获取
function RedDotModel:setNum(num)
	assert(not next(self.childs), "父节点不能直接设置数量: " .. self.id)
	self:_setNum(num)
end

---refreshNum用于刷新父节点红点数量,子节点红点发生变化时候用setNum 方法
function RedDotModel:refreshNum()
	if  next(self.childs) then
		local num = 0
		local oldNum = self.num
		--获取所有子红点数量
		for idx, childModel in ipairs (self.childs) do
			num = num + childModel:getNum()
		end
		self:_setNum(num)
	end
	
end

function RedDotModel:getNum()
	return self.num
end

---红点对象消耗时候调用
function RedDotModel:onDestroy()
	
end

---添加父节点
function RedDotModel:_addParent(parent)
	self.parent = parent
end

function RedDotModel:_setNum(num)
	local oldNum = self.num
	self.num = num
	self:_onNumChange(self.num - oldNum)
end

function RedDotModel:_onNumChange(diffNum)
	if diffNum and diffNum ~= 0 then
		for target, callback in pairs(self.callbacks) do
			callback(target)
		end
	end
	if self.parent then
		self.parent:refreshNum()
	end
end


红点管理类

RedDotManager = {}
---@class RedDotManager
local RedDotManager = RedDotManager
require("RedDot/RedDotBinder")

RedDotShowType = {
    Normal = 1, --常规红点
    Reward = 2, --奖励类型红点
    New = 3,    --“新” 红点
}

---红点配置
local redDotConfig = {
    [1] = { name = "功能A 1级界面", serverRedModelName = "", clientRedModelName = "", redType = -1, functionList = {}, childrens = {2}},
    [2] = { name = "功能A 2级界面", serverRedModelName = "", clientRedModelName = "", redType = -1, functionList = {}, childrens = {3,4}},
    [3] = { name = "功能A 3级界面A", serverRedModelName = "", clientRedModelName = "ChallengeNewChapterRedDotModel", redType = 3, functionList = {}, childrens = {}},
    [4] = { name = "功能A 3级界面B", serverRedModelName = "", clientRedModelName = "ChallengeRewardRedDotModel", redType = 2, functionList = {}, childrens = {}},
    [5] = { name = "功能B 1级界面", serverRedModelName = "", clientRedModelName = "", redType = -1, functionList = {}, childrens = {6}},
    [6] = { name = "功能B 2级界面", serverRedModelName = "DayTaskFinishRedDotModel", clientRedModelName = "", redType = 1, functionList = {}, childrens = {}},
}

function RedDotManager:init(serverRedModels)
    ---@type RedDotModel[]
    self.redModels = {}
    self.bindList = {}

    ---初始化红点列表
    for id, v in pairs(redDotConfig) do
        local redModel
        if v.clientRedModelName and v.clientRedModelName ~= "" then
            local redId = id
            printError(v.clientRedModelName)
            ---客户端红点需要重写一个继承RedDotModel的脚本,处理红点显示逻辑
            local redModelClas = require("models.redDot."..v.clientRedModelName)
            redModel = redModelClas.new(id, v)
        else
            redModel = RedDotModel.new(id, v)
        end
        self.redModels[id] = redModel
    end

    local leafRedModes = {}  --叶节点红点model列表
    ---初始父子级关系
    for i,redModel in ipairs(self.redModels) do
        local childs = redModel.config.childrens
        if childs and next(childs) then
            for _,childId in ipairs(childs) do
                if self.redModels[childId] then
                    local childModel = self.redModels[childId]
                    redModel:addChild(childModel)
                else
                    printError("子节点的配置有缺失,redId = " ..redModel.id..", childId = " .. childId)
                end
            end
        else
            table.insert(leafRedModes, redModel)
        end
    end

    ---初始化子红点方法
    for i,redModel in ipairs(leafRedModes) do
        redModel:onInit()
    end

    if serverRedModels and  next(serverRedModels) then
        ---填充服务器数据
        for i,v in ipairs(serverRedModels) do
            self:updateRedModelByServerData(v)
        end
    end
    
end

---绑定红点对象
---@param targetGo GameObject
---@param redDotModel RedDotManager
function RedDotManager:bind(targetGo, redDotModel)
    if self.bindList[targetGo] then
        ---@type RedDotBinder
        local binder = self.bindList[targetGo]
        binder:unBind()
        binder:onDestroy()
    end
    ---@type RedDotBinder
    local binder = RedDotBinder.new(targetGo, redDotModel)
    self.bindList[targetGo] = binder
end

function RedDotManager:unBind(targetGo)
    if self.bindList[targetGo] then
        ---@type RedDotBinder
        local binder = self.bindList[targetGo]
        binder:unBind()
    end
end

---通关服务器数据刷新红点
function RedDotManager:updateRedModelByServerData(serverRedData)
    local redId = serverRedData.redId
    if self.redModels[redId] then
        local isShow = serverRedData.isShow
        local num = isShow and 1 or 0
        self.redModels[redId]:setNum(num)
    end
end

---获取红点数据
---@return RedDotModel
function RedDotManager:getRedModelById(redId)
    if self.redModels[redId] then
        return self.redModels[redId]
    end
    printError("没有找到红点数据,红点Id="..redId)
end



事件名称

eventTypes = {
    ChallengeChapterUnlock = "ChallengeChapterUnlock",
    ChallengeChapterFinish = "ChallengeChapterFinish",
}

事件管理器

EventManager = {}

function EventManager:init()
    self.eventListenList_ = {}
    self:addEvent("APP_ENTER_FOREGROUND_EVENT", self, self.EnterBack)
    self.sdkEventList = {}
end

function EventManager:addEvent(eventName, target, handler)
    if eventName == nil then
        return
    end
    if type(eventName) == "table" then
        for _, subEventName in pairs(eventName) do
            if type(subEventName) == "number" then
                subEventName = tostring(subEventName)
            end
            self:addEvent_(subEventName, target, handler)
        end
    elseif type(eventName) == "string" then
        self:addEvent_(eventName, target, handler)
    elseif type(eventName) == "number" then
        self:addEvent_(tostring(eventName), target, handler)
    else
        print("uncall " .. eventName)
    end
end

function EventManager:addEvent_(eventName, target, handler)
    local eventTable = self.eventListenList_[eventName] or {}
    for _, eventHandle in pairs(eventTable) do
        if eventHandle.target == target then
            eventHandle.handler = handler
            return
        end
    end
    table.insert(eventTable, { target = target, handler = handler })
    self.eventListenList_[eventName] = eventTable
end

function EventManager:pushEvent(eventName, ...)
    if self.eventListenList_ then
        local eventsTabel = self.eventListenList_[eventName]
        if eventsTabel then
            local args = { ... }
            for index, hanlderItem in pairs(eventsTabel) do
                if hanlderItem.target and hanlderItem.handler then
                    xpcall(function() 
                        hanlderItem.handler(hanlderItem.target, eventName,unpack(args))
                    end,function(err)
                        printError(err)
                    end)
                   
                end
            end
        end
    end
end

function EventManager:removeEventByName(eventName)
    self.eventListenList_[eventName] = {}
end

function EventManager:removeEventByTarget(target)
    if target then
        for name, eventTable in pairs(self.eventListenList_) do
            for index, handler in pairs(eventTable) do
                if handler.target == target then
                    eventTable[index] = nil
                    --清理没有监听的事件@libin
                    if not next(eventTable) then
                        self.eventListenList_[name] = nil
                        break
                    end
                end
            end
        end
    end
end

---@param target table
---@param eventName string
function EventManager:removeEvent(target, eventName)
    local eventslist = self.eventListenList_[eventName]
    if eventslist then
        for index, eventTable in pairs(eventslist) do
            if eventTable.target == target then
                eventslist[index] = nil
                break
            end
        end
    end
end

function EventManager:dump()

    --debugLuaObj(self.eventListenList_,"ddd",true)
    for eventName, tbl in pairs(self.eventListenList_) do

        local eventsTabel = self.eventListenList_[eventName]
        if eventsTabel and not table_is_empty(eventsTabel) then
            print("eventName: [" .. eventName .. "]")

            for index, hanlderItem in pairs(eventsTabel) do
                if hanlderItem.target and hanlderItem.target.name then
                    print(hanlderItem.target.name)
                else
                    local obj = hanlderItem.target

                    local t = type(obj)
                    local mt
                    if t == "table" then
                        mt = getmetatable(obj)
                    elseif t == "userdata" then
                        -- mt = tolua.getpeer(obj)
                    end

                    if mt then
                        print(mt.__cname)
                    else

                        --print("mt == null")
                        local key = table.keyof(_G, obj)
                        if key then
                            print(key)
                        else
                            dump(obj, "", 3)
                        end
                    end
                end

                --if hanlderItem.handler then
                --	print("hanlderItem.handler")
                --	print(hanlderItem.handler)
                --end
                print("_______________________________________")
            end
        end
    end

end

function EventManager:destory(...)
    self.eventListenList_ = {}
    _G["EventManager"] = nil
end

EventManager:init()

return EventManager

红点绑定关系处理类

RedDotBinder = class("RedDotBinder")
---@class RedDotBinder
local RedDotBinder = RedDotBinder
function RedDotBinder:ctor(targetGo, redDotModel)
    ---@type GameObject
    self.targetGo = targetGo
    --self.targetImage = self.targetGo:GetComponent(typeof(Image))
    
    self:bind(redDotModel)
end

---绑定红点数量变化事件
---@param redModel RedDotModel
function RedDotBinder:bind(redModel)
    self:unBind()
    ---@type RedDotModel
    self.redDotModel = redModel
    self.redDotModel:addCallback(self.targetGo,  function()
        self:onRedNumChange()
    end)
    self:onRedNumChange() --初始化显示刷新
end

function RedDotBinder:unBind()
    if self.redDotModel then
        self.redDotModel:removeCallback(self.targetGo)
    end
end


---红点数量变化时候需要处理的内容
function RedDotBinder:onRedNumChange()
    self:refreshRedShow()
end

---刷新红点显示
function RedDotBinder:refreshRedShow()
    local redNum = self.redDotModel:getNum()
    local isShow = redNum > 0
    self.targetGo:SetActive(isShow)
    if isShow then  --刷新红点显示的图片
        --如果是父节点类型红点,需要获取所有显示的叶子节点,根据优先级进行排序,筛选出需要显示的红点类型
        self:collectEndChild(self.redDotModel, true)
        table.sort(self.collectList, function (a, b)
            if a.config.redType < b.config.redType then
                return a.id < b.id
            else
                return a.config.redType < b.config.redType
            end
        end)

        ---根据优先级最高的红点类型,刷新红点image需要显示的图片
        local redShowType = self.collectList[1].config.redType
        local spriteId
        if redShowType == RedDotShowType.Reward then
            spriteId = 10001
        elseif redShowType == RedDotShowType.New then
            spriteId = 10002
        else
            spriteId = 10001
        end
        print("显示红点图片:"..spriteId)
        -- self.targetImage.sprite = ResourceManage.getSpriteById(spriteId)
    end
end

---获取红点显示类型
---@return RedDotShowType
function RedDotBinder:getRedShowType()
    local redType = RedDotShowType.Normal
    if next(self.redDotModel.childs) then --存在子红点
        local visibleRedList = {}

    else --不存在子红点
        redType = self.redDotModel.config.redType
    end

    return redType
end

---获取所有红点num大于0的叶子节点对象
---@param redModel RedDotModel
---@param isRoot boolean
function RedDotBinder:collectEndChild(redModel, isRoot)
    if isRoot then
        self.collectList = {}
    end
    
    if next(redModel.childs) then --存在子节点
        for i,v in ipairs(redModel.childs) do
            self:collectEndChild(v)
        end
    else
        if redModel:getNum() > 0 then
            table.insert(self.collectList, redModel)
        end
    end
end



function RedDotBinder:onDestroy()
    
end


具体实现逻辑的红点类

ChallengeNewChapterRedDotModel = class("ChallengeNewChapterRedDotModel", RedDotModel)
---@class ChallengeNewChapterRedDotModel:RedDotModel
local ChallengeNewChapterRedDotModel = ChallengeNewChapterRedDotModel

-- function ChallengeNewChapterRedDotModel:ctor(id, cfg)
--     RedDotModel.ctor(self, id, cfg)
-- end

function ChallengeNewChapterRedDotModel:onInit()
    EventManager:addEvent(eventTypes.ChallengeChapterUnlock,self, self.check)
    --添加监听事件,处理红点刷新
    self.isHaveNewChapter = true
    self:check()
    
end

function ChallengeNewChapterRedDotModel:check()
    ---判断是否有没有查看过的新章节
    local num = self.isHaveNewChapter and 1 or 0
    self:setNum(num)
    self.isHaveNewChapter = not self.isHaveNewChapter
end

function ChallengeNewChapterRedDotModel:onDestroy()
    --移除监听事件
    EventManager:removeEventByTarget(self)
end

return ChallengeNewChapterRedDotModel
ChallengeRewardRedDotModel = class("ChallengeRewardRedDotModel", RedDotModel)
---@class ChallengeRewardRedDotModel:RedDotModel
local ChallengeRewardRedDotModel = ChallengeRewardRedDotModel


function ChallengeRewardRedDotModel:onInit()
    EventManager:addEvent(eventTypes.ChallengeChapterFinish, self, self.check)
    self.isHaveRewards = true
    --添加监听事件,处理红点刷新
    self:check()

end

function ChallengeRewardRedDotModel:check()
    ---判断是否有没有需要领取的章节奖励
    local num = self.isHaveRewards and 1 or 0
    self:setNum(num)
    self.isHaveRewards = not self.isHaveRewards
end

function ChallengeRewardRedDotModel:onDestroy()
    --移除监听事件
    EventManager:removeEventByTarget(self)
end

return ChallengeRewardRedDotModel

红点显示状态测试

创建红点对象

function testRedModle(...)
	local gob = UnityEngine.GameObject.New()
	gob.name = "1级界面";
	gob.transform.position = UnityEngine.Vector3.New(1,1,1)
	local redModel = RedDotManager:getRedModelById(1)
	RedDotManager:bind(gob, redModel)

	local gob2 = UnityEngine.GameObject.New()
	gob.transform.position = UnityEngine.Vector3.New(11,1,1)
	gob2.name = "2级界面";
	local redModel2 = RedDotManager:getRedModelById(2)
	RedDotManager:bind(gob2, redModel2)

	local gob3 = UnityEngine.GameObject.New()
	gob3.name = "3级界面";
	gob.transform.position = UnityEngine.Vector3.New(31,1,1)
	local redModel3 = RedDotManager:getRedModelById(3)
	RedDotManager:bind(gob3, redModel3)

	local gob4 = UnityEngine.GameObject.New()
	gob4.name = "4级界面";
	gob.transform.position = UnityEngine.Vector3.New(41,1,1)
	local redModel4 = RedDotManager:getRedModelById(4)
	RedDotManager:bind(gob4, redModel4)
end

模拟状态变化时候,发送事件刷新显示

  EventManager:pushEvent(eventTypes.ChallengeChapterFinish)
 EventManager:pushEvent(eventTypes.ChallengeChapterUnlock)

相关文章:

  • 网络安全应急响应中主机历史命令被删除 网络安全事件应急响应
  • JAVA面试常见题_基础部分_mybatis面试题
  • Spark RDD持久化机制深度解析
  • sql server 复制从备份初始化数据
  • Ubuntu中dpkg命令和apt命令的关系与区别
  • 大模型算法工程师的技术图谱和学习路径
  • AI 自动化编程:从效率革命到未来教育的革新
  • 请求Geoserver的WTMS服务返回200不返回图片问题-跨域导致
  • 【leetcode hot 100 42】接雨水
  • unity学习57: toggle/选项/切换, 实现单选和多选效果
  • ptaC语言4-3 求给定精度的简单交错序列部分和
  • VScode在Windows11中配置MSVC
  • 为AI聊天工具添加一个知识系统 之125 详细设计之66 逻辑和平台
  • VMware虚拟机系统扩容
  • 【Linux】线程详解
  • 事故02分析报告:慢查询+逻辑耦合导致订单无法生成
  • unity学习60: 滑动条 和 滚动条 滚动区域
  • NocoBase 本周更新汇总:新增路由管理
  • 点云处理入门--PointNetPointNet++论文与代码详解
  • python制图之小提琴图
  • 中国一重集团有限公司副总经理陆文俊被查
  • 巴基斯坦信德省卡拉奇发生爆炸
  • 深入贯彻中央八项规定精神学习教育中央第六指导组指导督导中国工商银行见面会召开
  • 马克思主义理论研究教学名师系列访谈|曾瑞明:想通了才可能认准,认准了才能做好
  • 观察|印巴交火开始升级,是否会升级为第四次印巴战争?
  • 青年与城市共成长,第六届上海创新创业青年50人论坛将举办