Where we are coming from

In the previous article we added some prefab variance options to each floor type, see here. Today, I wanted to share an article about additional layers but I stumbled upon a problem.

Problem Statement

When trying to prepare for the planned article, I wanted to share some images with more realistic samples of generates buildings, as the blog posts before were built around a very simple block prefab.
So I started creating a more realistic floor prefab:

Floor Prefab

As you can see (if not, then believe me ;-)) this prefab consists of

  • a floor
  • an elevator shaft
  • 28 flats (still simple blocks, we'll discuss later)

That is a good starting point for generating a building:

Building Game View

Looking great, at least proportion-wise!

In the Scene View, things look different though:

Building Scene View

You can clearly see that living in the top floor could be a quite wet pleasure - we're missing a roof!

Of course, you could now modify the prefab to include a roof for each building layer / floor, but that makes editing a messy process - a more straight-forward idea is to just add a seperate roof prefab, also one could then place special prefabs on top without much change in the floor types.

Building Scene View with Roof Top

Looks much nicer now - in the implementation, you can select whether a roof top is needed and if yes, which prefab to use - I stuck to a flat plane in the above example.

Let's have a look at the solution:

Implementation

Differences

The main differences are mostly two:

  • expose parameters for determining the roof top preference.
  • instantiate a roof top if needed after instantiating the floors.

Full Script

And here the full script to replace the one from the first article:

SimpleBuildingGenerator.cs

using System;
using System.Collections.Generic;
using UnityEngine;

public class SimpleBuildingGenerator : BaseEditorEnabledBehaviour
{
    #region Properties

    [Header("Floor Information")]
    [Tooltip("The floor templates to be used")]
    public List<BuildingFloorInfoObject> Floors = new List<BuildingFloorInfoObject>();

    [Header("Optional Rooftop")]
    [Tooltip("Shall a Rooftop be added on top of the floors?")]
    public bool DoAddRoofTop;

    [Tooltip("If a roof top should be added, which prefab to use, if 'DoAddRoofTop' is false this will be ignored.")]
    public GameObject RoofTopPrefab;

    [Header("Common Properties")]
    [Tooltip("The number of floors to create")]
    [Range(1, 250)]
    public int NumberOfFloors;

    #endregion Properties

    #region Construction

    #endregion Construction

    #region Methods

    #region Awake: the building generation is being executed here
    /// <summary>
    /// the building generation is being executed here
    /// </summary>
    private void Awake()
    {
        this.Generate(this.Floors, this.NumberOfFloors);
    }
    #endregion Awake

    #region Generate: The building is being created by instantiating the floor Prefab numberOfFloors times
    /// <summary>
    /// The building is being created by instantiating the floor Prefab numberOfFloors times
    /// </summary>
    /// <param name="floorPrefab">the prefab for a floor</param>
    /// <param name="floorHeight">the height of one floor (needs to be greater than zero)</param>
    /// <param name="numberOfFloors">the number of floors to iterate over (needs to be greater than one)</param>
    private void Generate(List<BuildingFloorInfoObject> floors, int numberOfFloors)
    {
        if (floors == null || floors.Count.Equals(0))  //make sure the floors are not null and not empty
        {
            //floor prefabs need to be filled
            //TODO: Log error for the above
        }
        else if (numberOfFloors < 1)  //ensure the number of floors is greater than zero
        {
            // number of floors needs to be greater than one
            //TODO: Log error for the above
        }
        else
        {
            Vector3 currentSpawnPosition = this.transform.position;  //start at the current position
            for (int currentFloor = 0; currentFloor < this.NumberOfFloors; currentFloor++)  //go from 0 to numberOfFloors-1
            {
                int usedPrefabsForThisFloor = 0;  //counter for used prefabs for one floor, to be able to check of anything has been placed
                float greatestHeight = 0f;  //if more than one floor prefabs are set, determine the greatest height, probably subject to change
                foreach (BuildingFloorInfoObject floor in floors)
                {
                    if (floor.FromFloor <= currentFloor && floor.ToFloor > currentFloor)
                    {
                        if (floor.FloorPrefabs == null)
                        {
                            //floor prefabs has to be filled
                            //TODO: Log error for the above
                        }
                        else if (floor.FloorPrefabs.Count.Equals(0))  //NEW
                        {
                            //floor prefabs is an empty list
                            //TODO: Log error for the above
                        }
                        else if (floor.FloorHeight < 0f)
                        {
                            // floor needs to have a height
                            //TODO: Log error for the above
                        }
                        else
                        {
                            int selectedPrefabIndex = UnityEngine.Random.Range(0, floor.FloorPrefabs.Count);
                            GameObject selectedPrefab = floor.FloorPrefabs[selectedPrefabIndex];
                            if (selectedPrefab != null)
                            {
                                GameObject newFloor = GameObject.Instantiate(selectedPrefab, currentSpawnPosition, Quaternion.identity, this.transform);  //instantiate the prefab on the desired position and as child of the current GameObject
                                if (newFloor != null)
                                {
                                    newFloor.name += " - Floor " + currentFloor.ToString("D4");  //add floor name for the visual tree
                                    usedPrefabsForThisFloor++;  //increase counter for instantiated prefabs for this particular floor
                                    if (greatestHeight < floor.FloorHeight)
                                    {
                                        greatestHeight = floor.FloorHeight;  //set the used height to the greatest height of used prefabs, is being called once as a minimum
                                    }
                                }
                                else
                                {
                                    //gameobject could not be instantiated
                                    //TODO: Log error for the above
                                }
                            }
                            else
                            {
                                //selected prefab is null
                                //TODO: Log error for the above
                            }
                        }
                    }
                    else
                    { 
                        //no reaction needed as only the current iterated floor has not been applicable, others could be, we don't know here
                    }
                }

                if (usedPrefabsForThisFloor.Equals(0))  //no floor prefab could be found that can be used for this floor
                {
                    //error that one floor was skipped plus infos
                    //TODO: Log error for the above
                }
                else
                {
                    //only iterate next spawn position if a prefab could be applied
                    currentSpawnPosition += new Vector3(0, greatestHeight, 0);  //adding height to the next spawn position
                }
            }

            if (this.DoAddRoofTop)
            {
                //add rooftop
                GameObject roofTop = GameObject.Instantiate(this.RoofTopPrefab, currentSpawnPosition, Quaternion.identity, this.transform);  //instantiate the prefab on the desired position and as child of the current GameObject
                if (roofTop != null)
                {
                    roofTop.name += " - Rooftop";  //add name for the visual tree
                }
                else
                {
                    //gameobject could not be instantiated
                    //TODO: Log error for the above
                }
            }
        }
    }
    #endregion Generate

    #region EditorValidateCallback: all children are being destroyed and the building is being regenerated
    /// <summary>
    /// all children are being destroyed and the building is being regenerated
    /// </summary>
    protected override void EditorValidateCallback()
    {
        this.gameObject.DestroyAllChildren(true);  //destroy all children to have no artifacts / leftover floors
        this.Generate(this.Floors, this.NumberOfFloors);  //restart creation to get potentially new parameters
    }
    #endregion EditorValidateCallback

    #endregion Methods
}

You can see two new Properties at the top, 'DoAddRoofTop' and 'RoofTopPrefab'.

  • DoAddRoofTop: bool property asking, if the developer wants to add a roof top
  • RoofTopPrefab: the prefab to be used as a roof top, will be ignored when above property is false.

In the 'Generate()' method at the end, we add the part where the bool property is being checked and if true, a roof top will be added. This could be a subject for refactoring, as it doubles some code from the original instantiation.

BuildingFloorInfoObject.cs

No changes needed.

Results

With this feature being added, we now can move on in this series!

Next Steps

Now, if you want to have attachments or decoration details, you need to include them into the given floor prefabs. To achieve more independency, we try to seperate floors from decoration to offer another layer of detail.

  • Meta Information

  • how to use the script to add other elements to the building as well

  • how to remember generated buildings by a simple seeding mechanism

Category
Tags

No responses yet

Leave a Reply

Your email address will not be published. Required fields are marked *