首页 > 解决方案 > BSP地牢生成不生成走廊

问题描述

我正在为我的毕业项目制作一个 2D 角色扮演游戏,并且我正处于想要随机生成我的地牢的地步。
我正在按照本教程http://www.rombdn.com/blog/2018/01/12/random-dungeon-bsp-unity/制作一个随机生成的地牢。

经过大量研究,我发现 BSP 算法可以生成我想要的游戏地牢设计。我已按照链接中提供的教程逐步进行。我提供了我的脚本供您查看:

public class BoardManager : MonoBehaviour {
    public GameObject floorTile;
    public GameObject corridorTile;

public int boardRows, boardColumns;
public int minRoomSize, maxRoomSize;

private GameObject[,] boardPositionsFloor;

public class SubDungeon {

    public SubDungeon left, right;
    public Rect rect;
    public Rect room = new Rect(-1,-1, 0, 0); // i.e null
    public int debugId;

    private static int debugCounter = 0;

    public List<Rect> corridors = new List<Rect>();

    public SubDungeon(Rect mrect) {
        rect = mrect;
        debugId = debugCounter;
        debugCounter++;
    }

    public bool IAmLeaf() {
        return left == null && right == null;
    }

    public bool Split(int minRoomSize, int maxRoomSize) {
        if (!IAmLeaf()) {
            return false;
        }

        // choose a vertical or horizontal split depending on the proportions
        // i.e. if too wide split vertically, or too long horizontally,
        // or if nearly square choose vertical or horizontal at random
        bool splitH;
        if (rect.width / rect.height >= 1.25) {
            splitH = false;
        } else if (rect.height / rect.width >= 1.25) {
            splitH = true;
        } else {
            splitH = Random.Range (0.0f, 1.0f) > 0.5;
        }

        if (Mathf.Min(rect.height, rect.width) / 2 < minRoomSize) {
            Debug.Log ("Sub-dungeon " + debugId + " will be a leaf");
            return false;
        }

        if (splitH) {
            // split so that the resulting sub-dungeons widths are not too small
            // (since we are splitting horizontally)
            int split = Random.Range (minRoomSize, (int)(rect.width - minRoomSize));

            left = new SubDungeon (new Rect (rect.x, rect.y, rect.width, split));
            right = new SubDungeon (
                new Rect (rect.x, rect.y + split, rect.width, rect.height - split));
        }
        else {
            int split = Random.Range (minRoomSize, (int)(rect.height - minRoomSize));

            left = new SubDungeon (new Rect (rect.x, rect.y, split, rect.height));
            right = new SubDungeon (
                new Rect (rect.x + split, rect.y, rect.width - split, rect.height));
        }

        return true;
    }

    public void CreateRoom() {
        if (left != null) {
            left.CreateRoom ();
        }
        if (right != null) {
            right.CreateRoom ();
        }
        if (IAmLeaf()) {
            int roomWidth = (int)Random.Range (rect.width / 2, rect.width - 2);
            int roomHeight = (int)Random.Range (rect.height / 2, rect.height - 2);
            int roomX = (int)Random.Range (1, rect.width - roomWidth - 1);
            int roomY = (int)Random.Range (1, rect.height - roomHeight - 1);

            // room position will be absolute in the board, not relative to the sub-dungeon
            room = new Rect (rect.x + roomX, rect.y + roomY, roomWidth, roomHeight);
            Debug.Log ("Created room " + room + " in sub-dungeon " + debugId + " " + rect);
        }
    }

    public Rect GetRoom() {
        if (IAmLeaf()) {
            return room;
        }
        if (left != null) {
            Rect lroom = left.GetRoom ();
            if (lroom.x != -1) {
                return lroom;
            }
        }
        if (right != null) {
            Rect rroom = right.GetRoom ();
            if (rroom.x != -1) {
                return rroom;
            }
        }

        // workaround non nullable structs
        return new Rect (-1, -1, 0, 0);
    }

    public void CreateCorridorBetween(SubDungeon left, SubDungeon right) {
        Rect lroom = left.GetRoom ();
        Rect rroom = right.GetRoom ();

        Debug.Log("Creating corridor(s) between " + left.debugId + "(" + lroom + ") and " + right.debugId + " (" + rroom + ")");
        Debug.Log ("hello");
        // attach the corridor to a random point in each room
        Vector2 lpoint = new Vector2 ((int)Random.Range (lroom.x + 1, lroom.xMax - 1), (int)Random.Range (lroom.y + 1, lroom.yMax - 1));
        Vector2 rpoint = new Vector2 ((int)Random.Range (rroom.x + 1, rroom.xMax - 1), (int)Random.Range (rroom.y + 1, rroom.yMax - 1));

        // always be sure that left point is on the left to simplify the code
        if (lpoint.x > rpoint.x) {
            Vector2 temp = lpoint;
            lpoint = rpoint;
            rpoint = temp;
        }

        int w = (int)(lpoint.x - rpoint.x);
        int h = (int)(lpoint.y - rpoint.y);

        Debug.Log ("lpoint: " + lpoint + ", rpoint: " + rpoint + ", w: " + w + ", h: " + h);

        // if the points are not aligned horizontally
        if (w != 0) {
            // choose at random to go horizontal then vertical or the opposite
            if (Random.Range (0, 1) > 2) {
                // add a corridor to the right
                corridors.Add (new Rect (lpoint.x, lpoint.y, Mathf.Abs (w) + 1, 1));

                // if left point is below right point go up
                // otherwise go down
                if (h < 0) {
                    corridors.Add (new Rect (rpoint.x, lpoint.y, 1, Mathf.Abs (h)));
                } else {
                    corridors.Add (new Rect (rpoint.x, lpoint.y, 1, -Mathf.Abs (h)));
                }
            } else {
                // go up or down
                if (h < 0) {
                    corridors.Add (new Rect (lpoint.x, lpoint.y, 1, Mathf.Abs (h)));
                } else {
                    corridors.Add (new Rect (lpoint.x, rpoint.y, 1, Mathf.Abs (h)));
                }

                // then go right
                corridors.Add (new Rect (lpoint.x, rpoint.y, Mathf.Abs (w) + 1, 1));
            }
        } else {
            // if the points are aligned horizontally
            // go up or down depending on the positions
            if (h < 0) {
                corridors.Add (new Rect ((int)lpoint.x, (int)lpoint.y, 1, Mathf.Abs (h)));
            } else {
                corridors.Add (new Rect ((int)rpoint.x, (int)rpoint.y, 1, Mathf.Abs (h)));
            }
        }

        Debug.Log ("Corridors: ");
        foreach (Rect corridor in corridors) {
            Debug.Log ("corridor: " + corridor);
        }
    }
}

public void CreateBSP(SubDungeon subDungeon) {
    Debug.Log ("Splitting sub-dungeon " + subDungeon.debugId + ": " + subDungeon.rect);
    if (subDungeon.IAmLeaf()) {
        // if the sub-dungeon is too large
        if (subDungeon.rect.width > maxRoomSize
            || subDungeon.rect.height > maxRoomSize
            || Random.Range(0.0f,1.0f) > 0.25) {

            if (subDungeon.Split (minRoomSize, maxRoomSize)) {
                Debug.Log ("Splitted sub-dungeon " + subDungeon.debugId + " in "
                    + subDungeon.left.debugId + ": " + subDungeon.left.rect + ", "
                    + subDungeon.right.debugId + ": " + subDungeon.right.rect);

                CreateBSP(subDungeon.left);
                CreateBSP(subDungeon.right);
            }
        }
    }
}
public void DrawRooms(SubDungeon subDungeon) {
    if (subDungeon == null) {
        return;
    }
    if (subDungeon.IAmLeaf()) {
        for (int i = (int)subDungeon.room.x; i < subDungeon.room.xMax; i++) {
            for (int j = (int)subDungeon.room.y; j < subDungeon.room.yMax; j++) {
                GameObject instance = Instantiate (floorTile, new Vector3 (i, j, 0f), Quaternion.identity) as GameObject;
                instance.transform.SetParent (transform);
                boardPositionsFloor [i, j] = instance;
            }
        }
    } else {
        DrawRooms (subDungeon.left);
        DrawRooms (subDungeon.right);
    }
}

void DrawCorridors(SubDungeon subDungeon) {
    if (subDungeon == null) {
        return;
    }

    DrawCorridors (subDungeon.left);
    DrawCorridors (subDungeon.right);

    foreach (Rect corridor in subDungeon.corridors) {
        for (int i = (int)corridor.x; i < corridor.xMax; i++) {
            for (int j = (int)corridor.y; j < corridor.yMax; j++) {
                if (boardPositionsFloor [i, j] == null) {
                    GameObject instance = Instantiate (corridorTile, new Vector3 (i, j, 0f), Quaternion.identity) as GameObject;
                    instance.transform.SetParent (transform);
                    boardPositionsFloor [i, j] = instance;
                }
            }
        }
    }
}

void Start () {
    SubDungeon rootSubDungeon = new SubDungeon (new Rect (0, 0, boardRows, boardColumns));
    CreateBSP (rootSubDungeon);
    rootSubDungeon.CreateRoom ();

    boardPositionsFloor = new GameObject[boardRows, boardColumns];
    DrawCorridors (rootSubDungeon);
    DrawRooms (rootSubDungeon);
}
}

房间正在完美创建,没有错误消息。我注意到 CreateCorridorBetween 没有被从任何地方调用,即使是在作者的博客上;因此,没有创建走廊。如何在我的代码中调用它?

标签: c#unity3dprocedural-generationspace-partitioning

解决方案


考虑到CreateCorridorBetweenare的参数SubDungeon leftSubDungeon right您可能希望在创建房间后调用该方法。我假设您已经阅读了您链接到的博客,但正如作者所说

连接房间:在房间之间创建走廊

孤立的房间不是很有用,所以我们将在它们之间添加走廊。为此,我们将把每个叶子连接到它的兄弟节点。然后在树的上一层,我们将重复连接父子地下城的过程,直到最后我们连接两个初始子地下城(请参阅 stackexchange 答案以获取插图)。

这意味着除了基本的“只需调用它”解决方案之外,还有更多内容。

希望这对您的项目有所帮助并祝您好运!


推荐阅读