游戏中的过程生成——binary space partitioning(BSP)生成房间地形

时间:2022-07-27 15:26:17

参考: https://gamedevelopment.tutsplus.com/tutorials/how-to-use-bsp-trees-to-generate-game-maps--gamedev-12268

binary space partitioning往往较多的在渲染中被提到,用于实现场景管理。这里我们说说用BSP实现的过程生成。

现在假设要生成一个由很多小房间组成的地形。我们假设用笨办法,不断地生成一个房间,然后不断挪动,把它放到能放的位置,接着再生成下一个……

这个办法看上去没什么问题,但是实际上如何让生成的房间排列的看起来比较正常,是很让人头疼的问题。

而我们使用BSP生成的话,问题便迎刃而解了。

方法很简单:先用BSP做随机的空间划分,然后在所有划分的最小格里放上一个房间即可,房间的位置大小根据需求来。

游戏中的过程生成——binary space partitioning(BSP)生成房间地形
上文参考中做的空间划分

根据BSP如何进行“随机的划分”,最终结果可以变得非常有序,也可以“随心所欲”。

游戏中的过程生成——binary space partitioning(BSP)生成房间地形
往划好的格子里填房间

在参考文中的最后建立连通性时,他通过空间和子空间的关系确定建立哪些连接来让房间连通。在我自己的实现中我还是用了传统的最小生成树的办法。

不过这里要提到一点是,在游戏中我们往往不需要寻求最优解或者是正确解,比如上一篇中提到的官方教程的代码的小错误,反而让结果变得更好。我在实现最小生成树连通时,给了一定的阈值,使得不属于最小生成树的一些边也会加入到图中,使最终的结果看起来更自然。

游戏中的过程生成——binary space partitioning(BSP)生成房间地形
最小生成树生成的连通性

游戏中的过程生成——binary space partitioning(BSP)生成房间地形
加入一点“额外允许”

代码部分。核心的划分规则和参考文中的差不多。只包括bsp生成,没有连通构造。

using UnityEngine;
using System.Collections;
namespace CS.MapGeneration {
    public class Leaf {
        public Leaf[] childLeaf = new Leaf[2];
        public Leaf leftLeaf {
            get {
                return childLeaf[0];
            }
            set {
                childLeaf[0] = value;
            }
        }
        public Leaf rightLeaf {

            get {
                return childLeaf[1];
            }
            set {
                childLeaf[1] = value;
            }
        }
        public int width;
        public int height;
        public int x;
        public int y;
        public Room room;
        public Leaf(int x, int y, int width, int height) {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
        }
        public Leaf() {

        }
    }
    public class Room {
        public int width;
        public int height;
        public int x;
        public int y;
        public Room(int x, int y, int width, int height) {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
        }
        public Room() {

        }
    }

    [System.Serializable]
    public class BspGenrator {
        public MapseedRandom random;
        public Leaf Genrate(int width, int height) {
            Leaf start = new Leaf(0, 0, width, height);
            Split(start);
            return start;
        }
        public int minSplitSize = 5;
        public float stopSplitChance = 0.3f;
        private void Split(Leaf leaf) {
            bool splitH = random.GetNext() > 0.5f;
            if ((float)leaf.width / leaf.height >= 1.25f)
                splitH = false;
            else if ((float)leaf.height / leaf.width >= 1.25f) {
                splitH = true;
            }
            var max = (splitH ? leaf.height : leaf.width) - minSplitSize;
            if (max <= minSplitSize || random.GetNext() < stopSplitChance)
                return;
            var split = (int)(random.GetNext() * (max - minSplitSize + 1) + minSplitSize);

            if (splitH) {
                leaf.leftLeaf = new Leaf(leaf.x, leaf.y, leaf.width, split);
                leaf.rightLeaf = new Leaf(leaf.x, leaf.y + split, leaf.width, leaf.height - split);
            } else {
                leaf.leftLeaf = new Leaf(leaf.x, leaf.y, split, leaf.height);
                leaf.rightLeaf = new Leaf(leaf.x + split, leaf.y, leaf.width - split, leaf.height);
            }
            Split(leaf.leftLeaf);
            Split(leaf.rightLeaf);
        }
    }

    [System.Serializable]
    public class BspRoomGenerator {
        public MapseedRandom random;
        public int minGap = 1;
        public int minRoomSize = 5;
        public void SetRoom(Leaf leaf) {
            bool doSet = true;
            if (leaf.leftLeaf != null) {
                SetRoom(leaf.leftLeaf);
                doSet = false;
            }
            if (leaf.rightLeaf != null) {
                SetRoom(leaf.rightLeaf);
                doSet = false;
            }
            if (!doSet)
                return;
            if (leaf.width < minRoomSize + minGap || leaf.height < minRoomSize + minGap)
                return;
            var width = (leaf.width - minRoomSize - minGap + 1) * random.GetNext() + minRoomSize;
            var height = (leaf.height - minRoomSize - minGap + 1) * random.GetNext() + minRoomSize;
            var x = leaf.x + (leaf.width - minGap - width + 1) * random.GetNext();
            var y = leaf.y + (leaf.height - minGap - height + 1) * random.GetNext();
            leaf.room = new Room((int)x, (int)y, (int)width, (int)height);
        }
    }
    
}