【问题标题】:Infinite loop for each procedurally generated level (C# Unity3D)每个程序生成的关卡的无限循环(C# Unity3D)
【发布时间】:2015-12-15 14:36:19
【问题描述】:

我正在尝试在我的 Unity 项目中以程序方式在随机位置生成恒定数量的建筑物。但是,我得到一个无限循环。

这是我的代码(C#):

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class GenerateMap : MonoBehaviour {

public GameObject plane;

public LayerMask unwalkableMask;
Node[,] Map;
public Vector2 gridWorldSize;

GameObject thisBuilding;

public float nodeRadius;
float nodeDiameter;
int gridSizeX, gridSizeY;

int scale;
public int numBuildings = 5;
public int numPrefabs;
public List<Vector3> positions = new List<Vector3> ();
public List<GameObject> buildingPrefabs = new List<GameObject>();



void Awake(){
    plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
    scale = 15; // scaling the plane gives an 5*scale x 5*scale (x-axis x z-axis) plane, set to 50
    plane.transform.localScale = new Vector3 (scale, 1, scale); //scales only in x and z dimensions
}
// Use this for initialization
void Start () {
    //GameObject building1 = Resources.Load("Buildings/building1") as GameObject;
    //GameObject building2 = (GameObject)Resources.Load ("Buildings/building2");
    //GameObject building3 = (GameObject)Resources.Load ("Buildings/building3");

    nodeDiameter = nodeRadius*2;
    gridSizeX = Mathf.RoundToInt(gridWorldSize.x/nodeDiameter);
    gridSizeY = Mathf.RoundToInt(gridWorldSize.y/nodeDiameter);

    Generate();

}

//int i = 0;

void Generate(){

    for(int i =0; i<numBuildings; i++){
        //while(i < numBuildings){

            CreateGrid();
            List<Node> unwalkables = getUnwalkables();
            thisBuilding =(GameObject)InstantiatePrefab(); i++;
            CreateGrid();
            List<Node> unwalkables2 = getUnwalkables(thisBuilding);


                foreach(Node n in unwalkables){

                    //Debug.Log(n.worldPosition);
                    bool breaking = false;
                    foreach(Node m in unwalkables2){
                    if(n.worldPosition==m.worldPosition){
                    Destroy(thisBuilding);
                    i=i-1;
                    Debug.Log("i after fail " + i);
                    breaking = true;
                    break;

                    }
                    if(breaking)
                    break;
                }

            }
    }
}


Object InstantiatePrefab() {
    int number = Random.Range (0, numPrefabs);
    Vector3 position = new Vector3 (Random.Range (-scale*5, scale*5), 0, Random.Range (-scale*5, scale*5)); //random position in the x,z-plane
    positions.Add (position);
    position.y = buildingPrefabs [number].transform.position.y; //make sure they spawn on top of the plane instead of y=0 w.r.t. their pivot point

    Object building;
    if (number != 2) {
        building = Instantiate (buildingPrefabs [number], position, Quaternion.Euler (-90f, 0f, 0f));
    } else {
        building = Instantiate (buildingPrefabs [number], position, Quaternion.identity);
    }
    return building;
}

void CreateGrid(){

    Map = new Node[gridSizeX, gridSizeY];
    Vector3 worldBottomLeft = transform.position - Vector3.right * gridWorldSize.x/2 - Vector3.forward * gridWorldSize.y/2;

    for(int x=0; x<gridSizeX; x++){
        for(int y=0; y<gridSizeX; y++){
            Vector3 worldPoint = worldBottomLeft + Vector3.right * (x*nodeDiameter + nodeRadius) + Vector3.forward * (y*nodeDiameter+ nodeRadius);
            bool walkable = !(Physics.CheckSphere(worldPoint, nodeRadius, unwalkableMask));
            Map[x,y] = new Node(walkable, worldPoint, x, y);
        }
    }

}


void OnDrawGizmos()
{
    Gizmos.DrawWireCube(transform.position, new Vector3(gridWorldSize.x, 1, gridWorldSize.y));

    if (Map != null)
    {

        foreach (Node n in Map)
        {
            Gizmos.color = (n.walkable)?Color.white:Color.red;

            Gizmos.DrawCube(n.worldPosition, new Vector3(nodeDiameter-.1f, nodeDiameter*0.5f, nodeDiameter-.1f));
                //Vector3.one * (nodeDiameter-.1f));

        }
    }
}

List<Node> getUnwalkables(){
    List<Node> unwalk = new List<Node>();
    foreach(Node n in Map){
        if(!n.walkable)
        unwalk.Add(n);
    }
    return unwalk;
}

List<Node> getUnwalkables(GameObject obj){
    List<Node> unwalk = new List<Node>();


    int borderWidth = 8; //x-dir
    int borderHeight = 7; //z-dir

    float RightBorder = obj.transform.position.x+nodeRadius+borderWidth*nodeDiameter;
    float LeftBorder = obj.transform.position.x-nodeRadius-borderWidth*nodeDiameter;
    float TopBorder = obj.transform.position.z+nodeRadius+borderHeight*nodeDiameter;
    float DownBorder = obj.transform.position.z-nodeRadius-borderHeight*nodeDiameter;

    //Vector3 worldBottomLeft = transform.position - Vector3.right * gridWorldSize.x/2 - Vector3.forward * gridWorldSize.y/2;

    foreach(Node n in Map){

                if(n.worldPosition.x<RightBorder && n.worldPosition.x>LeftBorder 
                    && n.worldPosition.z>DownBorder && n.worldPosition.z<TopBorder)
                unwalk.Add(n);

    }

    return unwalk;
}

}

这个方法好像有问题:

void Generate(){

    for(int i =0; i<numBuildings; i++){
        //while(i < numBuildings){

            CreateGrid();
            List<Node> unwalkables = getUnwalkables();
            thisBuilding =(GameObject)InstantiatePrefab(); i++;
            CreateGrid();
            List<Node> unwalkables2 = getUnwalkables(thisBuilding);


                foreach(Node n in unwalkables){

                    //Debug.Log(n.worldPosition);
                    bool breaking = false;
                    foreach(Node m in unwalkables2){
                    if(n.worldPosition==m.worldPosition){
                    Destroy(thisBuilding);
                    i=i-1;
                    Debug.Log("i after fail " + i);
                    breaking = true;
                    break;

                    }
                    if(breaking)
                    break;
                }

            }
    }
}

如果我删除该行:i=i-1;我没有无限循环,但我最终会得到比预期更少的建筑物。此行导致 for 循环再重复一次迭代,因此它将始终生成至少 'numBuildings' 数量的建筑物。

我在“foreach”循环中添加了中断语句,这样如果条件似乎多次满足,“i”就不会继续减少。但这似乎不起作用,一旦 numBuildings 大约为 5 或更多,它就会创建一个无限循环。发生这种情况时,我只能通过强制结束任务才能退出。

有谁知道我做错了什么,或者是否有更好的解决方案来始终生成大量建筑物?

如有必要,我已准备好回答有关代码/场景的任何问题。

【问题讨论】:

  • 这可能是个人喜好,但您的代码格式使其难以阅读。
  • 一般是别人要求完整的代码输出,所以提前贴出来。但要指出正确的方向:第二个代码块包含问题。你可以问任何看起来不清楚的问题。
  • 您是否确定总是可能(在您的规则限制内)将numBuildings 建筑物选址?
  • 抛开这个问题不谈,一般来说,在循环体中胡乱摆弄循环变量会使成功推理代码的行为变得更加困难。重新设计以避免这种情况。

标签: c# unity3d foreach infinite-loop procedural-generation


【解决方案1】:

事情是这样的:Destroy 函数不能立即工作。据我了解您的代码(我没有深入了解所有细节),通常它会执行以下操作:

  1. 创建一些建筑物
  2. 找到所有“无法行走”的建筑物并摧毁它们(您可以通过测试建筑物周围的空闲区域来做到这一点)
  3. 重复此操作,直到您拥有所需数量的建筑物

无限循环的发生是因为Destroy 实际上并没有破坏任何东西——它只是在帧结束时标记要破坏的对象。问题是整个循环是在单帧中运行的,所以实际的破坏永远不会发生。这样一来,“Unwalkables”的数量就会随着每次迭代而不断增长。

有很多快速方法可以解决这个问题,也有一个好方法可以解决这个问题,选择权在你。

快速方法是:

好的方法是考虑算法的替代实现。与其先放置建筑物,然后再破坏放置不当的建筑物,不如尝试为建筑物找到一个好的开始位置,然后再放置它。最好这样做,因为即使您使用Destroy 函数解决了问题,您的算法仍然存在逻辑问题:如果您根本没有足够的空间容纳所有建筑物怎么办?您不知道何时停止当前的实施。您只会在无限循环中不断生成和破坏建筑物。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-04
    • 2015-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-06
    • 2014-10-31
    相关资源
    最近更新 更多