Download demo project HERE.
I am working on a hobby project which is an Unity 3D game. I needed to crate 3D procedural map and stumbled upon this post. I decided to implement my own solution based on that approach. First created some module prefabs with an input and multiple outputs. (Yeah they are just boxes. Replace them with your fancy models.)Here is the script of module prefab:
using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Assets.Scripts { public class Module : MonoBehaviour { System.Random rnd; public GameObject[] Outputs; public List<GameObject> AvailableOutputs { get; set; } public GameObject GetOutput() { //Get an output and seal var op = AvailableOutputs.ElementAt(rnd.Next(AvailableOutputs.Count)); AvailableOutputs.Remove(op); return op; } public void Init(int seed) { rnd = new System.Random(seed); AvailableOutputs = Outputs.ToList(); } } }These are my test modules:
In dungeon generator script I followed this steps:
- Select random module
- Place selected module on some random free output transform position
- Rotate selected module by using output objects local transform vector.
- Check if there is any overlap problem. If current module overlaps destroy it and try an other random module on same output.
Dungeon creator script:
using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Assets.Scripts { public class WorldBuilder : MonoBehaviour { public GameObject[] Modules; public int Iterations; /// <summary> /// Random seed for debugging. /// </summary> public int Seed; System.Random rnd; List<GameObject> createdModules, availableModules; void Start() { rnd = new System.Random(Seed); BuildWorld(); } void BuildWorld() { createdModules = new List<GameObject>(); var initialModule = GameObject.Instantiate(Modules.ElementAt(rnd.Next(Modules.Count()))); initialModule.GetComponent<Module>().Init(Seed); createdModules.Add(initialModule); availableModules = createdModules; for (int i = 0; i < Iterations; i++) { var module = availableModules.ElementAt(rnd.Next(availableModules.Count)); var targetPoint = module.GetComponent<Module>().GetOutput(); //Shuffle and try every blocks to fit var shuffledBlocks = Modules.OrderBy(d => rnd.Next()).ToArray(); foreach (var sBlock in shuffledBlocks) { var candidate = GameObject.Instantiate(sBlock); candidate.GetComponent<Module>().Init(Seed); candidate.gameObject.transform.position = targetPoint.transform.position; candidate.transform.LookAt(targetPoint.transform.position + targetPoint.transform.forward); //Check if there is an any overlapping var bound = candidate.GetComponent<BoxCollider>().bounds; var isSafe = true; foreach (var item in createdModules) { if (bound.Intersects(item.GetComponent<BoxCollider>().bounds)) { //Try another module GameObject.Destroy(candidate); isSafe = false; break; } } if (isSafe) { //Module connected safely createdModules.Add(candidate); break; } } availableModules = createdModules.Where(d => d.GetComponent<Module>().AvailableOutputs.Any()).ToList(); if (!availableModules.Any()) { //No availabel output on any modules. Stop the proccess break; } } foreach (var item in createdModules) { //Disable overlap test colliders item.GetComponent<BoxCollider>().enabled = false; } } } }
Results in different steps:
And here how it works in my game:
Download demo project HERE.