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:
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:
Looking great, at least proportion-wise!
In the Scene View, things look different though:
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.
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
No responses yet