Unity 代码具有我无法弄清楚的罕见的空引用

Unity code with rare null reference that I can't figure out

提问人:Tom 提问时间:4/8/2022 最后编辑:Tom 更新时间:4/8/2022 访问量:852

问:

我的 Unity 游戏代码中有一个简单的函数,我正在寻找一个“工作地点”——一个让我的 NPC 执行某些任务的空地方。

这段代码在 99.999% 的时间内运行良好,但偶尔会抛出 NULL REFERENCE EXCEPTION,我一辈子都无法弄清楚在哪里。我试图抓住每一个可能的地方,其中某些东西可能未分配并且找不到它。我一定遗漏了一些边缘情况,或者我的代码中的某些东西在某些奇怪的情况下行为不端。

所以我的代码有缺陷,我不明白在哪里。帮我改进它:

    public Transform[] workSpots;


    public Transform findAvailableWorkSpot() {
        if (workSpots == null || workSpots.Length == 0) return null; // sometimes we have no work spots
        int i=0;
        while (i<10) {
            Transform spot = workSpots[Random.Range(0, workSpots.Length)];
            if (spot != null && spot.childCount == 0) {
                return spot;
            }
            i++;
        }
        // couldn't find one randomly, let's just iterate over them:
        foreach (Transform spot in workSpots) {
            if (spot != null && spot.childCount == 0) {
                return spot;
            }
        }

        Debug.LogError("could not find free workspot on "+gameObject.name);
        return null;
    }

逻辑:

  • 首先,尝试最多 10 次以获得免费的随机工作位置。当NPC在那里工作时,我会把他带到工作地点,所以如果该地点现在没有NPC,也是如此。spot.childCount == 0
  • 如果失败了,那么我只是遍历所有这些并选择第一个免费的。
  • 可以在这里返回 null,调用代码将处理它

诊断后端告诉我,每天有 2-4 次有人在此函数中遇到 Null 引用异常,但诊断没有告诉我行号,而且我从未在本地看到过。我一遍又一遍地看着它,但我无法发现它可能在哪里。也许我的代码有更根本的问题?


其他信息:

  • workspots 在 Awake() 中初始化并且永远不会再次更改,因此我确信函数开头的测试可以正常工作,并且在函数运行时它不可能变为 null。
C# unity-game-engine 异常 null nullreferenceexception

评论

0赞 rustyBucketBay 4/8/2022
你如何初始化?考虑到最后一个索引是长度 - 1。也许当 int 等于时你超出了范围?Transform[] workSpots;workSpots[Random.Range(0, workSpots.Length)]workSpots.Length
1赞 derHugo 4/8/2022
@rustyBucketBay != ;)另请注意,对于 int 参数,的上限是非独占的!所以永远不会回来NullReferenceExceptionIndexOutOfRangeExceptionRandom.RangeRandom.Range(0, workSpots.Length)workSpots.Length
0赞 canton7 4/8/2022
在执行该方法时,另一个线程是否设置为 null?大概有什么东西在某个时候将其设置为 null,否则您将不需要初始 null 检查。可以通过在该方法的开头创建本地副本来避免这种情况workSpots
0赞 derHugo 4/8/2022
你能发布更多的背景信息吗?就像初学者一样,您的异常消息和堆栈跟踪到底说了什么?它应该已经告诉您抛出异常的确切代码行=>在那里设置一个断点并检查在什么情况下会发生这种情况=>然后找出原因
1赞 derHugo 4/8/2022
@canton7 是的,第二次看时也有同样的想法;)在极少数情况下,Component.gameObject 可以“等效于”,即在此对象被销毁的帧中调用此方法。但在这种情况下,对自己的调用应该已经抛出......除非它来自同一个班级而不是来自外部。虽然不确定..我认为这种情况宁愿抛出 Unity 的自定义案例nullfindAvailableWorkSpotMissingReferenceException

答:

-1赞 Nikola Develops 4/8/2022 #1

我不认为你正在寻找发生异常的正确位置。你编写代码的方式不可能是空或空的。workSpots

我唯一可以看到发生空引用异常的地方是,如果您尝试访问 -> 并且返回一个 null 的 Transform 对象,并且您尝试使用它(但在您的代码中,您为此做好了准备)。 也许您使用该特定方法的地方在尝试访问它之前不会检查您返回的 Transform 是否为 null。 如果你的代码写得很好,但你不相信它,那么空引用异常可能是由于尝试记录错误时引起的。如果不是这样,那么老实说我不知道。workSpots[index]gameObject.name

评论

0赞 Tom 4/8/2022
至少根据 Unity Diagnostics 的说法,此函数中肯定会发生异常。我也一遍又一遍地查看代码,但不知道它可能在哪里,这就是我询问社区的原因。
1赞 derHugo 4/8/2022 #2

这并不是对异常确切来源的直接回答。

但是,如果您愿意使用 Linq,则可以简化您的代码!

我会简单地使用

using System.Linq;

...

public Transform[] workSpots;

public bool TryFindAvailableWorkSpot(out Transform freeSpot)
{
    // First heck this component even is alive
    if (!this || workSpots == null || workSpots.Length == 0)
    {
        freeSpot = null;
        return false;
    }

    // First "Where" filters out only those spots that exist and have no children
    // then "OrderBy" randomizes these results
    // then we take the first one or null if there isn't any
    freeSpot = workSpots.Where(spot => spot && spot.childCount == 0).OrderBy(spot => Random.value).FirstOrDefault();

    // use the implicit bool operator for checking if we have found a valid reference
    return freeSpot;
}

然后而不是使用

var spot = yourClass.findAvailableWorkSpot();

并且必须再次检查和潜在的异常,您宁愿简单地在同一行中进行检查,例如null

if(yourClass.TryFindAvailableWorkSpot(out var spot))
{
    ... Do someting with spot
}
else
{
    Debug.LogError("could not find free workspot");
}

评论

0赞 Tom 4/8/2022
它可能不会直接回答问题,但有时只是重构代码并使其更精简,就可以消除晦涩难懂的错误,所以我会试一试。