22 Ekim 2016 Cumartesi

3D Procedural Dungeon Generation

 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:
  1. Select random module
  2. Place selected module on some random free output transform position
  3. Rotate selected module by using output objects local transform vector.
  4. 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.

Hiç yorum yok:

Yorum Gönder