Vector-based cloud system

Vector-based cloud system

During my studies, I was tasked with making a cloud spawning system. It had to be easily modifiable (in case the designer wanted to make changes) and work with a vector to describe the direction of the wind, alongside a value to represent the speed of the wind. By creating a prototype for this, it was determined that the easiest way to illustrate where the clouds would appear, was to restrain them to a bounding box. A random coordinate would then be picked inside of this box, for the cloud to spawn. Once the cloud had been created, its direction and speed would be set by a central weather object, to ensure that all of the clouds would follow the wind. If the cloud left the boundaries of the weather object, it would be deleted.

The world’s most boring terrarium

The benefit of this approach, is how easy it is to understand where the clouds can appear, and at what boundaries they will disappear. By simply modifying the box sizes, the designer would be able to gauge the effect of the changes, without even pressing the play button.

But then comes the requirement of dynamic wind. This would have to affect not only the direction and speed of the clouds, but the location and rotation of the cloud spawning box. You can imagine what would happen if the wind blew towards the left in that picture – the clouds would briefly appear and then disappear, as they’d spawn right next to the edge of the weather system box, going towards it. Plus the rotation of the cloud spawning box meant it was no longer possible to just use the location and size of the box to spawn the clouds.

Theory:

Lo and behold, it was time to bring out the blackboard:

You can almost glimpse the hellhole of our meeting room in the reflection

What is seen here is a birds-eye view of the previous picture. Point A marks the center of the the weather system box. Point B marks the center of the the cloud spawning box. And right under A, there’s a little vector illustrating the normalized wind direction (meaning it’s the wind direction, reduced to a size of 1) called normDir.

With these three components alone, it is possible to alter the location and rotation of the cloud spawning box. How? By multiplying normDir with -1, we get a vector pointing in the opposite direction of normDir. By then, it is just a matter of multiplying this inverted vector with some number representing how far away we want the box to be, and then tell the box to always face Point A (with the fancy Unity command Transform.LookAt(Point A)).

The cool thing about this interaction, is that we know normDir now always runs in parallel with the width of the cloud spawning box. Because vectors are able to run a fancy little operation called the cross product, which gives a new vector running at a 90 degree angle to the original. When working in 2D, this only requires the original vector. We’re working in 3D, but we can just pretend like the X and Z components are the X and Y in 2D:

Behold, my terrible handwriting

Translating this into 3D looks something like:

Vector3 Original = (x, y, z)
Vector3 Crossed = (z, y, -x) 

In a more complex 3D setting, I may have chosen to cross two 3D vectors with each other (eg. Vector3.Up), but since the height of the cloud spawner inside of the weather system is never manipulated, this works just fine. But to put it into perspective, the perpendicular vector now always runs along the length of the cloud spawner box:

The hieroglyps spell “transform.localScale.x”

Now there’s a vector running in parallel with the length, it is the length of 1 (because it is the cross product of a normalized vector), Point B is in the middle of the cloud spawning box, and the length of the box can be found with transform.localScale.x. That means moving from Point B to half of the length of the box, in either direction of the perpendicular vector, will get some value inside the box along that axis.

And voilá, by simply adding the perpendicular vector to Point B, multiplied by some random number between zero and half the length of the cloud spawner box, a random point inside the box along the X-axis is found. To summarize, consider the following illustration:

Perpendicular here is upside down, I am good at maths
  1. Find the normalized vector for the wind direction
  2. Find the vector perpendicular to that one
  3. Pick a random value between zero and half the length of the box
  4. Use the random value as a multiplier for the perpendicular vector
  5. Add the perpendicular vector to the position of the Cloud Spawner object, to finally land within the boundaries

What’s cool about getting this far, is how easy it is to then find a random value for the other dimensions of the box as well, to randomize the height at which they spawn, for example. Additionally, you may randomize the scale and rotation of the clouds, as well as the time between which they spawn. The final result looks like this:

Credit to Emil Larsen for the cloud model!

The exposed parameters in this prototype are as follows:

The wind direction is modified by changing the X and Z parameters of the Wind Dir vector, the speed by Wind Speed and the rest are equally self-explanatory. Which brings us to the actual code.

Code:

Now that you have a decent idea of the theory behind the prototype, it is time to take a look at the code and how the theory is implemented in practice.

Once you have imported the package file to a Unity project, you can find all of the scripts under Assets > Scripts. Below is a summary of each script, alongside a detailed explanation of each function found within:

  1. WeatherScript: Contains the vector used for wind direction. The weather system holds a list of references to all existing clouds, so that it can update them individually to follow the wind. Additionally, it handles positioning of the cloud spawner, calculated from the wind direction.
  2. SpawnScript: Handles spawning and despawning of the clouds. Positioning of the clouds is done by examining the scales of a bounding box, then generating a vector somewhere within these boundaries. It acts as a middle link passing references to the weather system, as well as deleting them when the clouds are destroyed.
  3. CloudScript: Updates the position of each cloud, as well as tells the SpawnScript when a cloud is destroyed.

1. WeatherScript:

1.0 Fields:

The weather handles the wind direction, as well as passing a number of values onto the SpawnScript class (which are defined in the WeatherScript class to keep value definitions in one place).

Wind parameters:

Parameters which affect the “wind direction” and the “speed” of the wind vector.

public Vector3 windDir

This vector determines the direction of the wind. By design, it’s Y-component must always be zero, which effectively turns it into a 2D-vector on the X- and Z-planes.

Vector3 normDir

This vector is simply the normalized version of the windDir vector, which retains the direction but scales the vector to a size of 1.

public float windSpeed

A multiplier added to normDir, to determine the speed of the wind. The higher the value of this variable, the longer normDir will be stretched.

Cloud-spawning parameters:

These parameters are all passed down to the SpawnScript, but are declared here to make it easier to tweak parameters across the weather system from just one place.

public float spawnDistance

The distance between the center of the Cloud System and the center of the Cloud Spawner object. Increasing this value starts the clouds much further out, but try to keep the Cloud Spawner within the bounds of the Cloud System, or the clouds may never despawn.

public int minSpawnTime

The minimum amount of time (in seconds) which must pass between clouds spawning.

public int maxSpawnTime

The maximum amount of time (in seconds) which can pass between clouds spawning.

public float minCloudSize

The minimum multiplier (onto the model scale) by which the clouds can appear.

public float maxCloudSize

The maximum multiplier (onto the model scale) by which the clouds can appear.

Misc:

Non-categorizable fields, in this case the list of cloud references.

public List<CloudScript> cloudListRef

A list of references to all of the existing cloud object scripts. These references allow the WeatherScript class to dynamically update the direction of all existing clouds, should the direction of the wind be changed.

1.1 void Start():

Finds the Cloud Spawner object (which is a child of the Cloud System object), then establishes a reference to the SpawnScript class attached to it. Via this reference, the WeatherScript passes a reference to itself, onto the SpawnScript – a necessity for allowing the Cloud Spawn object to remove clouds from the cloudListRef list when they are destroyed.

1.2 void Update():

Sets the value of normDir by normalizing the current value of windDir. Additionally, a call to the ChangeDirection, UpdateParameters and MoveCloudSpawner functions is made here, allowing for constant system updates.

1.3 void ChangeDirection():

This function iterates through the cloudListRef list and updates the direction vector of all of the CloudScript objects listed here. The clouds use this parameter to move each update, which means that changing this vector will change the movement of the clouds.

1.4 void UpdateParameters():

This function passes on all of the Cloud-spawning parameters, onto the SpawnScript class. By calling this in the update function, it is ensured that the values are updated constantly.

1.5 void MoveCloudSpawner():

This function moves the location of the Cloud Spawner object, relative to the normDir vector, which is the normalized windDir vector. By multiplying this vector with negative 1, it is possible to get a location that is somewhere “behind” the wind direction, which is optimal for finding a location to spawn clouds, as they should travel in the wind direction across the center of the Cloud System object. The distance at which the Cloud Spawn object is localized, is determined by the spawnDistance multiplied onto the inverted normDir (stored as a new vector3: cloudSpawnLocation). Using this new vector, the location of the Cloud Spawner object is updated, and the rotation is modified so that the Cloud Spawner always faces the center of the cloud system.

2. SpawnScript:

This script handles the instantiation of the cloud objects, their listing on the cloudListRef list and subsequent removal when they are destroyed. Additionally, it holds some elements of randomization for the rotation and scale of each cloud as their are instantiated, to provide a varied look despite using the same model for all of the clouds.

2.0 Fields:

Most of the fields in this class are initialized by the WeatherScript, and are described within the WeatherScript documentation as well.

public int minTime

The minimum amount of time (in seconds) which must pass between clouds spawning.

public int maxTime

The maximum amount of time (in seconds) which can pass between clouds spawning.

public float minCloudSize

The minimum multiplier (onto the model scale) by which the clouds can appear.

public float maxCloudSize

The maximum multiplier (onto the model scale) by which the clouds can appear.

public WeatherScript weatherRef

A reference to the WeatherScript class attached to the Cloud System object. This reference is established by the WeatherScript itself, as it finds the Cloud Spawner object by looking through its child objects.

CloudScript cloudRef

A reference to a CloudScript class. This is necessary for facilitating the removal of Cloud objects from the cloudListRef list, when a Cloud object is destroyed.

2.1 void Start():

All the Start function does, it call the SpawnClouds coroutine once.

2.2 IEnumerator SpawnClouds():

This coroutine is designed as an infinite loop with a timer pausing the thread for a random amount of time, between the minTime and maxTime parameter values. When the timer is over, the logic of the coroutine executes.

First, object data is loaded from the Resources folder in Unity, which is where the Cloud Object prefab can be found (under “GameObjects/Cloud_01”). This data is then used to instantiate a new Cloud Object. Once instantiation has finished, a reference to the CloudScript class attached to the Cloud Object is established, and a reference to the SpawnScript is passed onto it. This reference is necessary for the cloud, so that it may tell the Cloud Spawner object when it has been destroyed, and pass itself up to be cleared from the cloudListRef list in the WeatherScript class, via the SpawnScript class.

An assortment of vector calculations are carried out, in order to find a location where the cloud can be initialized, within the Cloud Spawner object boundaries. For more specific details on how this operation is performed, see the “Vector Displacement calculations.”

Once the Cloud object has been moved to its correct position, its scale is modified with a call to the CloudSize function, and its rotation is modified by a call to the CloudRotation function. Finally, the coroutine calls itself, repeating the process.

2.3 public float CloudSize():

Returns a random float between the value of minCloudSize and maxCloudSize.

2.4 public Quarternion CloudRotation():

Returns a quarternion with a random rotation between 0 and 360 degrees, around the Y-axis.

2.5 void DespawnCloud(CloudScript _cScript):

This function takes the passed CloudScript reference parameter, then removes it from the cloudListRef list in the WeatherScript class. Calls to this function are made via reference by the clouds themselves, as they are destroyed.

3. CloudScript:

A fairly simple script for a fairly simple object, the CloudScript handles the moving of the Cloud objects, checking when the clouds are moving beyond the Cloud System object boundaries, destroying them when they are, then calling the DespawnCloud-function in the SpawnScript class upon destruction.

3.0 Fields:

public Vector3 direction

The vector used to update the position of the Cloud objects. The value of this vector is updated by the WeatherScript class via its cloudListRef list.

public float chanceOfSound

A float used to indicate the likelihood of a sound playing when the Cloud object is initialized. 0 being never and 1 being always, with percentages ranging in between.

public SpawnScript spawnRef

A reference to the SpawnScript class on the Cloud Spawner object. This reference is used to call the DespawnCloud function when the Cloud object is destroyed.

3.1 void Start():

Determines if a sound should be played, randomly pitching the sound up or down as well.

3.2 void Update():

Constantly add the direction vector to the position of the Cloud object, thus making it move in the given direction.

3.3 void OnTriggerExit(Collider col):

Upon exiting a trigger, check to see if this trigger is the bounding box of the Cloud System, by looking for the “Despawner” tag. If this matches, the Cloud object has left the Cloud System and will be destroyed.

3.4 private void OnDestroy():

This function is automatically called when the Cloud object is destroyed. It calls the DespawnCloud function in the SpawnScript class via the spawnRef reference, passing a reference to itself as a parameter, for removal from the cloudListRef list in the WeatherScript class.

Leave a Reply

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