在平面地图上随机生成块

时间:2016-02-05 15:49:52

标签: c# dictionary random unity3d generator

我正在尝试在平面地图上随机生成块,并使其不会相互重叠。 我制作了一张地图大小(500x500)的矩阵(c#数组),这些块的比例在1到5之间。 代码可以正常工作,但如果生成的块与另一个块重叠,则会被销毁,而不会在其他地方重新生成。

我尝试生成的1000个块中只有大约80个不与另一个块重叠。

这是地图的图片,生成了大约80个块,绿色方块是块

Map

void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
    bool elementFound = false;
    for (int i = 0; i < ratio * generationDefault; i++) {
        GameObject el;
        // Randomly generate block size and position
        int size = Random.Range(minScale, maxScale + 1);
        int x = Random.Range(0, mapSizex + 1 - size);
        int y = Random.Range(0, mapSizey + 1 - size);

        // Check if there is already an element 
        for (int j = x; j < x + size; j++)
            for (int k = y; k < y + size; k++)
                if (map[j][k] != null)
                    elementFound = true;
        if (elementFound)
            continue;
        else {
            el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
            el.transform.localScale *= size;
        }
        // Create element on map array
        for (int j = x; j < x + size; j++)
            for (int k = y; k < y + size; k++)  
                if (map[j][k] == null) {
                    map[j][k] = el.GetComponent<ObjectInterface>();
                }
    }
}

我想到了3个可能的修复

  • 我应该根据它的位置来设置块的大小。
  • 我应该使用另一种随机化算法。
  • 我做得不对。

您认为最好的主意是什么?

更新

我让代码工作得更好。我现在尝试在需要时多次实例化块(当前最多5个)并修复了错误。如果地图上已经有很多元素,它们将不会总是被实例化,这就是我想要的,我只需要找到它尝试实例化块的正确数量。

我尝试在500x500地图上实例化1280个元素。它只需要大约1.5秒,并实例化1278/1280块(99.843%)。

enter image description here

void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
bool elementFound = false;
int cnt = 0;
// Generate every block
for (int i = 0; i < ratio * generationDefault; i++) {
    GameObject el = null;
    // Randomly generate block size and position
    int size, x, y, tryCnt = 0;

    // Try maximum 5 times to generate the block
    do {
        elementFound = false;
        // Randomly set block size and position
        size = Random.Range(minScale, maxScale + 1);
        x = Random.Range(0, mapSizex + 1 - size);
        y = Random.Range(0, mapSizey + 1 - size);

        // Check if there is already an element 
        for (int j = x; j < x + size; j++)
            for (int k = y; k < y + size; k++)
                if (map[j][k] != null)
                    elementFound = true;
        tryCnt++;
    } while (elementFound && tryCnt < 5);
    if (tryCnt >= 5 && elementFound) continue;

    // Instantiate the block
    el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
    el.transform.localScale *= size;
    // Create element on map array
    for (int j = x; j < x + size; j++)
        for (int k = y; k < y + size; k++)  
            if (map[j][k] == null) {
                map[j][k] = el.GetComponent<ObjectInterface>();
            }
    cnt++;
}
print("Instantiated " + cnt + "/" + ratio * generationDefault);

}

3 个答案:

答案 0 :(得分:6)

非常难以做好

这是一个你可能喜欢的快速解决方案......取决于你的场景。

actualWidth = 500 //or whatever. assume here is square
// your blocks are up to 5 size
chunkWidth = actualWidth / 5
// it goes without saying, everything here is an int
kChunks = chunkWidth*chunkWidth
List<int> shuf = Enumerable.Range(1,kChunks).OrderBy(r=>Random.value).ToList();
howManyWanted = 1000
shuf = shuf.Take(howManyWanted)
foreach( i in shuf )
   x = i % actualWidth
   y = i / actualWidth
   make block at x y
   put block in list allBlocks

但是............

......你会发现这看起来很“正常”,所以这样做:

随便扰乱所有街区。请记住,视频游戏编程是关于巧妙的技巧!

理想情况下,你必须从中间开始并逐步解决;无论如何,你不能只是排成一行。洗牌是可以的。所以,这样做..

   harmonic = 3  //for example. TRY DIFFERENT VALUES

   function rh = Random.Range(1,harmonic) (that's 1 not 0)

   function rhPosNeg
       n = rh
       n = either +n or -n
       return n

   function onePerturbation
   {
   allBlocks = allBlocks.OrderBy(r => Random.value) //essential
   foreach b in allBlocks
      newPotentialPosition = Vector2(rhPosNeg,rhPosNeg)
      possible = your function to check if it is possible
           to have a block at newPotentialPosition,
           however be careful not to check "yourself"
      if possible, move block to newPotentialPosition
   }

最简单的方法就是运行onePerturbation,比如三次。每次运行之间都要看一下。同时尝试harmonic调整因子的不同值。

有很多方法可以扰乱不同大小的块的字段,上面是一个KISS解决方案,希望看起来很适合你的情况。

编码说明......

如何获取一组唯一的随机数。

只是解释这行代码......

List<int> shuf = Enumerable.Range(1,kChunks).OrderBy(r=>Random.value).ToList();

如果你不熟悉编码:说你想这样做:“获得一百个随机数,从1到百万,但没有重复”。

幸运的是,这是一个众所周知的问题,其中包含非常简单的解决方案

您获得没有重复的数字的方式只是 随机播放所有数字 ,然后从最顶层获取您想要的数量。

例如,假设您需要一对1-10的随机数字,但没有重复。

所以,这里的数字1-10改组:3,8,6,1,2,7,10,9,4,5

简单地从前面拿出你需要的东西:所以,3,8,6等等。

所以举一个例子让我们说你需要十二个数字,没有重复,从1到75.所以第一个问题是,你想要一个列表,所有数字最多为75,但是洗牌。事实上,你这样做..

List<int> shuf = Enumerable.Range(1,75).OrderBy(r=>Random.value).ToList();

所以该列表长达75个项目。您可以通过说foreach(int r in shuf) Debug.Log(r);来查看它。在示例中,您只需要12个这样的数字。幸运的是,List调用了这个:

shuf = shuf.Take(12)

所以,就是这样 - 你现在有12个数字,没有重复,在1到75之间都是随机的。你可以再次查看foreach(int r in shuf) Debug.Log(r);

简而言之,当你想要“n”个数字,没有重复,介于1和Max之间时,所有你需要的就是:

List<int> shuf = Enumerable.Range(1,Max).OrderBy(r=>Random.value).ToList();
shuf = shuf.Take(n);

etvoilà,您可以使用foreach(int r in shuf) Debug.Log(r);

查看结果

我只是详细解释这个问题,因为经常会问“如何获得独特的随机数”。这是一个“古老的”编程技巧,答案很简单,就是 shuffle 所有相关整数的数组。

有趣的是,如果你谷歌这个问题(“如何获得独特的随机数字”),这是 google没有多少帮助的极少数情况之一,因为:每当提出这个问题时,你会得到大量敏锐的新程序员(他们没有听过简单的技巧来正确地做到这一点!!)写出了巨大而复杂的想法,导致进一步的混乱和复杂化。

这就是你如何制作没有重复的随机数,幸运的是它是微不足道的。

答案 1 :(得分:1)

if (elementFound) continue;将跳过此当前循环迭代。你需要包裹int x=Random..; int y=Random()..;部分在while循环中,条件为while(/* position x/y already occupued*/) { /* generate new valid point */},例如:

void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
    for (int i = 0; i < ratio * generationDefault; i++) {
        GameObject el;
        // Randomly generate block size and position

        bool elementFound = false;
        int size, x, y;
        do
        {
            elementFound = false;
            size = Random.Range(minScale, maxScale + 1);
            x = Random.Range(0, mapSizex + 1 - size);
            y = Random.Range(0, mapSizey + 1 - size);

            // Check if there is already an element 
            for (int j = x; j < x + size; j++)
                for (int k = y; k < y + size; k++)
                    if (map[j][k] != null)
                        elementFound = true;
        } while(elementFound);

        el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
        el.transform.localScale *= size;

        // Create element on map array
        for (int j = x; j < x + size; j++)
            for (int k = y; k < y + size; k++)  
                if (map[j][k] == null) {
                    map[j][k] = el.GetComponent<ObjectInterface>();
                }
    }
}

答案 2 :(得分:1)

你不应该得到那么多碰撞。

假设您的积木全部为5个单位宽,并且您正试图将它们装入500,500的网格中,那么它们至少会有100 * 100个空间,这样就可以容纳10,000个空间,可以容纳1,000个块。

尝试使用此代码:

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            var result = PlaceNonOverlappingBlocks(1000, 5, 500, 500);
        }

        static List<Block> PlaceNonOverlappingBlocks(int count, int maxBlockSize, int mapX, int mapY)
        {
            var map    = new bool[mapY, mapX];
            var rng    = new Random();
            var result = new List<Block>(count);
            int collisions = 0; 

            while (count > 0)
            {
                int size = rng.Next(1, maxBlockSize + 1);
                int x = rng.Next(0, mapX - size);
                int y = rng.Next(0, mapY - size);

                if (fits(map, x, y, size))
                {
                    result.Add(new Block(x, y, size));
                    addToMap(map, x, y, size);
                    --count;
                }
                else
                {
                    if (++collisions> 100000)
                        throw new InvalidOperationException("Hell has frozen over");
                }
            }

            // This is just for diagnostics, and can be removed.
            Console.WriteLine($"There were {collisions} collisions.");

            return result;
        }

        static void addToMap(bool[,] map, int px, int py, int size)
        {
            for (int x = px; x < px+size; ++x)
                for (int y = py; y < py + size; ++y)
                    map[y, x] = true;
        }

        static bool fits(bool[,] map, int px, int py, int size)
        {
            for (int x = px; x < px + size; ++x)
                for (int y = py; y < py + size; ++y)
                    if (map[y, x])
                        return false;

            return true;
        }

        internal class Block
        {
            public int X    { get; }
            public int Y    { get; }
            public int Size { get; }

            public Block(int x, int y, int size)
            {
                X = x;
                Y = y;
                Size = size;
            }
        }
    }
}