【问题标题】:Generating an object (with recursion) where the other objects are not生成一个对象(使用递归),而其他对象不是
【发布时间】:2019-12-24 14:18:26
【问题描述】:

我有一个游戏,我在每个关卡开始时生成许多对象。我有一个“完成孔”和一些其他大的圆形“陷阱”孔,它们也出现在关卡中。使用递归,我尝试在没有其他孔的位置随机生成我的完成孔。我正在尝试使用 OverlapSphere 来查找该区域中的对象。如果随机位置有洞,它会重新运行该方法以分配随机位置。我不希望终点洞重叠,因为那样玩家可能无法到达终点洞。

我尝试重做我的代码,调整 OverlapSphere 参数以使检查区域更大或更小,但这似乎并不能解决问题。事实上,当我把它改得太精确时,它会因为它的递归性质而卡住并导致崩溃。

下面是我在关卡生成器脚本的 Start 方法中调用时的代码。

void Start()
    {
        GenerateLevel();
    }

private void RandomiseFinishHolePos()
    {
        float _randomRangeX = Random.Range(-.75f, 4.75f);
        float _randomRangeZ = Random.Range(2f, 9f);
        FinishHolePosition = new Vector3(_randomRangeX, 0.1f, _randomRangeZ);
        holeColliders = Physics.OverlapSphere(FinishHolePosition, 2f, 1 << 11);

        if (holeColliders.Length != 0)
        {
            Debug.Log("Collider found: " + holeColliders[0].name);
            RandomiseFinishHolePos();
        }
    }

    private void CreateFinishHole()
    {
        RandomiseFinishHolePos();
        if (FinishHoleActive == null)
        {
            FinishHoleActive = Instantiate(FinishHole, FinishHolePosition, Quaternion.identity);
            Debug.Log("Finish hole is NULL. " + FinishHolePosition);
            FinishHoleSpawn = FinishHoleActive.GetComponentInChildren<ParticleSystem>();
        }
        else
        {
            FinishHoleActive.transform.position = FinishHolePosition;
            Debug.Log("Finish hole is NOT null. " + FinishHolePosition);
            FinishHoleSpawn.Play();
        }

有时当它运行它检测位置之间重叠的方法时,然后将完成孔放置在一个没有物体的新随机位置。但是,它仍然经常失败,这使得关卡无法完成,因为完成孔将在另一个对象的顶部。

希望有另一种方法来实现这一目标,因为这种方法可能受到 OverlapSphere 和递归的限制。感谢阅读。

【问题讨论】:

  • 这里没有理由使用递归。 do...while 循环就足够了,这意味着您不会用返回指针弄乱堆栈。我也不明白你为什么要区分一个空的FinishHoleActive 和一个非空的。您两次都做同样的事情(除了日志消息)。

标签: c# unity3d recursion


【解决方案1】:

这里的递归很危险,因为它会很快导致堆栈溢出。您可以继续调用相同的方法,直到找到一个位置,但是您很可能会以更集中的级别重复相同的值,并且会发现自己浪费了很多时间。我的建议是将您尝试放置孔的第一个位置随机化,然后从该位置“走出”,直到找到匹配的位置。这可以防止您重复相同的检查,但仍允许完全随机放置孔。用作参考的一个很好的算法是FloodFill algorithm。我会推荐涉及队列的替代实现,并指定检查之间的特定距离。

我还建议将您的最小/最大范围移动到一些变量,以便更容易配置,或者您可以将它们作为参数传递。 Bounds 类非常适合。

示例代码:

private void RandomiseFinishHolePos()
    {
        bool holePlaced = false;
        Bounds holeBounds = new Bounds(
            new Vector3(2f, 0.1f, 5.5f), //Center
            new Vector3(5.5f, 0f, 7f) //Size
        );
        Vector3 startingPos = new Vector2(
            Random.Range(bounds.min.x, bounds.max.x),
            Random.Range(bounds.min.y, bounds.max.y), //Will always be 0.1f because the bounds has a center of 0.1f and a size of 0f.
            Random.Range(bounds.min.z, bounds.max.z)
        );

        Queue<Vector3> offsetQueue = new Queue<Vector3>();
        offsetQueue.Enqueue(Vector3.zero);
        while(!holePlaced || offsetQueue.Count > 0)
        {
             Vector3 offset = offsetQueue.Dequeue();
             FinishHolePosition = startingPos + offset;
             //Check to make sure the hole is within our bounds, otherwise skip over it.
             if(!holeBounds.Contains(FinishHolePosition)
             {
                 Continue;
             }

             holeColliders = Physics.OverlapSphere(FinishHolePosition, 2f, 1 << 11);
             if(holeColliders.Length == 0)
             {
                 //Place your hole, there were no overlaps
                 //...Your code here
                 //Allow the while loop to finish
                 holePlaced = true;
             } else
             {
                 //try up, right, down, and left
                 Vector3 upOffset = offset + new Vector3(0f, 0f, 0.1f);
                 Vector3 rightOffset = offset + new Vector3(0.1f, 0f, 0f);
                 Vector3 downOffset = offset + new Vector3(0f, 0f, -0.1f);
                 Vector3 leftOffset = offset + new Vector3(-0.1f, 0f, 0f);
                 offsetQueue.Enqueue(upOffset);
                 offsetQueue.Enqueue(rightOffse);
                 offsetQueue.Enqueue(downOffset);
                 offsetQueue.Enqueue(leftOffset);
             }
        }
    }

您可以通过更改 up/right/down/let 偏移向量的大小来指定您希望检查的距离。即使在排队的情况下,如果你永远找不到洞的位置,循环将在无法找到边界内的任何点时结束,从而导致没有完成洞。如果发生这种情况,我建议完全重新生成关卡。

【讨论】:

    【解决方案2】:

    这有点残酷,但你必须尝试一下:

    private void RandomiseFinishHolePos()
        {
            float _randomRangeX = Random.Range(-.75f, 4.75f);
            float _randomRangeZ = Random.Range(2f, 9f);
            FinishHolePosition = new Vector3(_randomRangeX, 0.1f, _randomRangeZ);
    
            Physics.OverlapSphere(FinishHolePosition, 2f, 1 << 11); //ADD THIS LINE
    
            holeColliders = Physics.OverlapSphere(FinishHolePosition, 2f, 1 << 11);
    
            if (holeColliders.Length != 0)
            {
                Debug.Log("Collider found: " + holeColliders[0].name);
                RandomiseFinishHolePos();
            }
        }
    

    ...或者使用一些东西来减慢重叠检查过程

    【讨论】:

    • 是的,没有。让物理引擎执行两次检查,放弃第一次尝试,不会解决任何问题。
    猜你喜欢
    • 2018-06-23
    • 2020-06-27
    • 2019-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多