Where we are coming from

In the previous article we added the possibility to use more prefabs for different floors in a generated building, see here. That is awesome, yet can lead to high effort if you wish (and who does not) to have a bit more visual variance.

What we will be enhancing

random selection of prefabs per floor for visual variance

Therefore, today, we'll enhance the generator to be able to use more than one prefab per floor type in order to add an easy way to create some diversification with little to no effort.

Scene View

Scene View Detail

Game View

As you can see, especially in the Detail View, basically three types of prefabs are being used for the bigger floor type:

Editor View

The sample is quite simple, but I believe you get the effect and what a bit more time in prefab generation can achieve.

Implementation

Differences

There are two main differences as you can imagine that the implementation is quite simple:

  1. Replace the single Prefab in the 'BuildingFloorInfoObject' data class with a List
  2. Reflect those changes in the 'SimpleBuildingGenerator' class
    1. change the position of the error checks
    2. select a prefab randomly

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.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
                                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
                            {
                                //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
                }
            }
        }
    }
    #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

This is the full script for our data class which now includes a List to reflect the different prefabs per floor:

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

[Serializable]
public class BuildingFloorInfoObject
{
    #region Properties

    [Tooltip("The prefabs, randomly selected, for a single floor")]
    public List<GameObject> FloorPrefabs;

    [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

With above changes, we are able to use a randomly selected prefab from a given list of prefabs per floor, which brings huge possibilities:

Visual Tree Detail View

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.

  • 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 *