Minor changes, major consequences

Sometimes in life, you have to learn the hard way.

While working late on the enemy AI one night, I decided that I wanted more structure in the hierarchy in the Unity scene I was working in. To structure up the scene I added a number of empty objects and named them after the objects that they would represent in the hierarchy. These empty objects act similarly to ordinary folders when objects are placed as children under them. I was content with the result of my new structure in the hierarchy and decided to stop working for the evening.

asset_enemyfish

Image: Enemy from Umibozu developed by Vampire. Artist: Josefine Ringstad

The next day when I opened up Unity to keep working I discovered, to my dismay, that the enemy AI was broken and that all enemies would move towards the bottom of the screen and get stuck against a wall.

Why was this happening when it had worked fine the night before?

After a few hours of trying to fix the issue, I managed to track down the cause. The culprit turned out to be the improved structure in the hierarchy, more presicely the empty objects used in the structure.

Why was this an issue? It’s all because of how the enemy AI was designed.

There was a function within the AI that did not work with this new structure. The AI was designed in such a way that it would use an empty object’s location as a destination when roaming the game world. When the distance between the enemy and the destination would be less than 0.2 world units, a new location would be set for the destination using a random x and y coordinate within a defined range.


//function used for roaming
void PatrolArea()
{
   //Move towards the current destination
   rb.velocity = transform.up * movementSpeed;

   //Evaluate if the enemy is within the desired distance
   if (Vector2.Distance(transform.position, destination.position < 0.2f)
   {
      //Randomize a new destination for the enemy
      //This code was originally used
      destination.position = new Vector2(Random.Range(minX, maxX),
      Random.Range(minY, maxY));

      //This is the fixed code
      destination.localPosition = new Vector2(Random.Range(minX, maxX),
      Random.Range(minY, maxY));
   }
}

The fix is subtle, but it is something important to keep in mind. The reason the AI broke is found in the difference between transform.position och transform.localPosition.

transform.position
The position of an objects transform in world space.

transform.localPosition
The position of an objects transform in relation to the objects parent.

The reason the AI broke was because it changed the position in world space of the destination instead of the position relative to the destinations parent object. Since the destination objects did not have a parent when the AI was coded, it made sense to change the position in world space. When the re-structuring was done and the destinations for the enemies all now shared a parent, that was no longer the case.

A rule of thumb for the future will be to use .localPosition when handling children of other objects and .position when handling the parent objects.

Knowing when to use global and when to use local positions in Unity is important to avoid undefined behaviour and make sure that the scripts work as intended. Something else that is very important to keep in mind is that the slightest changes in the engine could result in a lot of time spent on bug fixing afterwards.

 

/Jesper H. Backman

Leave a comment