Where we are coming from

In the previous article here I showed you how to set up a quite simple and straightforward building generator in Unity, which is capable of stacking prefabs in a user-defined amount.

Given that, we are able to enhance our game world with generated boredom, so far so good.

What we will be enhancing

Let's now focus on bringing some more diversity to the generator script to be able to add more kinds of floors to the generated buildings.

Scene View

Game View

As you can see, the enhanced script version in this case features 3 different types of prefabs being used for the floors (please note that due to applied laziness the three floor types are only visualized by two different prefabs).

Editor

The script now allows to define as many floor types as needed to satisfy more complex builds - in this case three. You can set the start and end floor level additionally to the used prefab and floor height. The number of floors is still remaining of the original script.

Let's have a closer look:

Implementation

Differences

The script mainly differs in now using a List of 'BuildingFloorInfoObject' which seperates the floor infos from the generator and enables us to add variety. This structure then needs to be reflected in the updates 'Generate' method, especially in the loop part:

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.FloorPrefab == null)
                        {
                            //floor prefab has to be filled
                            //TODO: Log error for the above
                        }
                        else if (floor.FloorHeight < 0f)
                        {
                            // floor needs to have a height
                            //TODO: Log error for the above
                        }
                        else
                        {
                            GameObject newFloor = GameObject.Instantiate(floor.FloorPrefab, currentSpawnPosition, Quaternion.identity, this.transform);  //instantiate the prefab on the desired position and as child of the current GameObject
                            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
                    { 
                        //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
                }
            }

Set aside some counters and helping variables, we can see that per floor a check, if any of the defined objects in the list can be applied. Then, some sanity checks follow and then, the instantiating part is almost unchanged, although we use the new data class 'BuildingFloorInfoObject' now.

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

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

    [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.FloorPrefab == null)
                        {
                            //floor prefab has to be filled
                            //TODO: Log error for the above
                        }
                        else if (floor.FloorHeight < 0f)
                        {
                            // floor needs to have a height
                            //TODO: Log error for the above
                        }
                        else
                        {
                            GameObject newFloor = GameObject.Instantiate(floor.FloorPrefab, currentSpawnPosition, Quaternion.identity, this.transform);  //instantiate the prefab on the desired position and as child of the current GameObject
                            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
                    { 
                        //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
                }
            }
        }
    }
    #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
}

BuildingFloorInfoObject.cs

Here is the full code for the 'BuildingFloorInfoObject', a classical data holding class, which has been made serializable to be used in Unity:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

[Serializable]
public class BuildingFloorInfoObject
{
    #region Properties

    [Tooltip("The prefab for a single floor")]
    public GameObject FloorPrefab;

    [Tooltip("The Height of one floor")]
    public float FloorHeight = 3f;

    [Tooltip("From which floor should the Prefab be applied?")]
    [Range(1, 250)]
    public int FromFloor;

    [Tooltip("To which floor should the Prefab be applied?")]
    [Range(1, 250)]
    public int ToFloor;

    #endregion Properties

    #region Methods

    #endregion Methods
}

Results

The second prefab introduced is basically a copy of the one in the first article, only the scale set to 12, 3, 12 to be able to distinguish the floor prefabs optically.

You can see some results when playing with the parameters:

Scene view plus editor 01

Scene view plus editor 02

Scene view plus editor 03

As expected, the floor types are only applied within the number of floors range, if other heights are defined, they are simply cut off, respectively not being iterated over.

Next Steps

As you may have noticed, the visual diversity of the generated buildings is rather still lacking a bit. Next parts will focus on

  • how to alternate between different prefabs in the same floor region
  • how to use the script to add other elements to the building as well
Category
Tags

No responses yet

Leave a Reply

Your email address will not be published.