You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

技能树闭环检测问题:CanDisableSkill函数逻辑异常修复求助

修复Unity技能树CanDisableSkill的闭环检测逻辑

问题根源

原逻辑在检测闭环时,仅通过简单的路径回溯判断是否能回到起点,未严格区分节点是否属于完全由已激活节点构成的强连通分量(SCC)。当存在内嵌小闭环时,遍历路径会误将连接到大闭环的非闭环节点判定为处于闭环内,导致错误允许关闭本不应操作的节点。

修复思路

采用强连通分量(SCC)检测算法(如Tarjan算法),仅针对已激活节点构建子图,精准识别闭环:

  • 若待关闭节点所在的强连通分量大小≥2(即存在有效闭环),则允许关闭;
  • 若所在分量大小为1(无闭环),则不允许关闭。

具体代码实现

替换原CanDisableSkill函数逻辑,以下是基于Tarjan算法的可落地示例:

using System.Collections.Generic;
using UnityEngine;

public class SkillTreeManager : MonoBehaviour
{
    // 技能节点核心结构(根据项目实际定义调整)
    public class SkillNode
    {
        public bool isActive;
        public List<SkillNode> connectedNodes; // 技能节点间的双向/单向连接
        public int uniqueId; // 节点唯一标识,用于SCC检测
    }

    private int _traversalIndex;
    private Dictionary<SkillNode, int> _nodeIndexMap;
    private Dictionary<SkillNode, int> _lowLinkMap;
    private Stack<SkillNode> _nodeStack;
    private HashSet<SkillNode> _nodesOnStack;
    private List<List<SkillNode>> _sccCollection;

    public bool CanDisableSkill(SkillNode targetNode)
    {
        // 未激活节点直接禁止关闭
        if (!targetNode.isActive)
            return false;

        // 初始化SCC检测工具
        _traversalIndex = 0;
        _nodeIndexMap = new Dictionary<SkillNode, int>();
        _lowLinkMap = new Dictionary<SkillNode, int>();
        _nodeStack = new Stack<SkillNode>();
        _nodesOnStack = new HashSet<SkillNode>();
        _sccCollection = new List<List<SkillNode>>();

        // 仅遍历已激活节点构建子图
        List<SkillNode> activeNodes = CollectAllActiveNodes();
        foreach (var node in activeNodes)
        {
            if (!_nodeIndexMap.ContainsKey(node))
                TarjanTraverse(node);
        }

        // 查找目标节点所属的SCC并判断是否允许关闭
        foreach (var component in _sccCollection)
        {
            if (component.Contains(targetNode))
            {
                return component.Count >= 2;
            }
        }

        return false;
    }

    private void TarjanTraverse(SkillNode node)
    {
        _nodeIndexMap[node] = _traversalIndex;
        _lowLinkMap[node] = _traversalIndex;
        _traversalIndex++;
        _nodeStack.Push(node);
        _nodesOnStack.Add(node);

        // 仅处理已激活的相邻节点
        foreach (var neighbor in node.connectedNodes)
        {
            if (!neighbor.isActive)
                continue;

            if (!_nodeIndexMap.ContainsKey(neighbor))
            {
                TarjanTraverse(neighbor);
                _lowLinkMap[node] = Mathf.Min(_lowLinkMap[node], _lowLinkMap[neighbor]);
            }
            else if (_nodesOnStack.Contains(neighbor))
            {
                _lowLinkMap[node] = Mathf.Min(_lowLinkMap[node], _nodeIndexMap[neighbor]);
            }
        }

        // 提取完整的强连通分量
        if (_lowLinkMap[node] == _nodeIndexMap[node])
        {
            List<SkillNode> component = new List<SkillNode>();
            SkillNode currentNode;
            do
            {
                currentNode = _nodeStack.Pop();
                _nodesOnStack.Remove(currentNode);
                component.Add(currentNode);
            } while (currentNode != node);

            _sccCollection.Add(component);
        }
    }

    // 辅助函数:收集所有已激活的技能节点
    private List<SkillNode> CollectAllActiveNodes()
    {
        // 替换为项目中获取所有技能节点的实际逻辑
        List<SkillNode> allNodes = new List<SkillNode>();
        List<SkillNode> activeNodes = new List<SkillNode>();
        foreach (var node in allNodes)
        {
            if (node.isActive)
                activeNodes.Add(node);
        }
        return activeNodes;
    }
}

关键细节说明

  • 激活节点子图限定:仅对已激活节点做SCC检测,排除未激活节点对闭环判定的干扰;
  • 精准闭环识别:内嵌小闭环会被识别为独立的SCC,不会误判外部连接节点处于闭环内;
  • 连接方向适配:若技能树是单向激活逻辑(如父节点→子节点),需调整connectedNodes的遍历方向,确保检测的是激活路径上的闭环。

内容的提问来源于stack exchange,提问作者RiP Hunt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 19:51:02