Skip to content

Unity best practices

AgostinoSturaro edited this page Jun 21, 2014 · 1 revision

Unity best practices

Properly setting the height of a prefab

Some of the components we designed instantiate prefabs. In order to know how high the transform of the prefab should be in order to place the prefab just above the ground, a setup is needed. The prefab can be queried to know how tall it is, but how to position it requires knowledge of the height of its origin. For example, if an object is tall 2 units, and its center is at height 1, then the position.y of the prefab should be set to 1.

Components, GameObject and Prefabs

Components in Unity are things that are attached to a GameObject to give it additional properties or to make it behave and react in different ways. Components cannot be used if they are not attached to a GameObject. All script in Unity are Components, precisely they are MonoBehaviours. Other examples are the Transform, Collider and RigidBody components.

Some Components interact with each other, for example, the Transform component, that represents the position and rotation of a GameObject can be influenced by the RigidBody component, which allows the GameObject to react to physics forces. So, if a forward force is applied to a RigidBody, it will make the Transform change its position. More than 2 components can interact, continuing the same example, forward movement can be stopped if the Collider component detects a collision.

GameObject is a class that can get instantiated in the game world. Its properties and behaviors depend on what Components are attached to it. GameObjects can be nested to allow them to move together or to communicate with each other more easily.

Prefabs are Objects, not GameObjects. A single prefab can represent a whole hierarchy of GameObject that have not been instantiated yet. Prefabs can exist without being instantiated inside the game world. When they are instantiated, a copy of the hierarchy of GameObjects they represent is brought to the game world. The original prefab will remain untouched and available for more instantiations.

To sum this up, GameObjects are a bag of Components, Prefabs are a bag of GameObjects.

Tips on parenting of GameObjects

In Unity GameObjects can be nested using the inspector to create a hierarchy. When an object is nested inside another one, then its Transform component becomes a child of the Transform component of the GameObject a step higher in the hierarchy. That is, childGameObject.transform.parent = parentGameObject.transform

Nesting GameObjects has another less obvious purpose, that is letting the parent object query Components of its children. In order to do this, parentGameObject.GetComponentsInChildren () will return an array of Components of the requested type. An option can be used inside the parenthesis to also return disabled Components.

This is especially useful to get Components of a prefabs, which are all inactive until the prefab is instantiated. However, since a prefab is an Object and not a GameObject, a cast is needed before applying this operation ((GameObject)prefabObject).GetComponentsInChildren (true).

Another possible use of nesting is to make them communicate through messages. When a GameObject calls SendMessage("FunctionName"), all Components attached to it or to one of its descendant GameObjects will receive that message.

Conversely, when a GameObject calls SendMessageUpwards("FunctionName"), all Components attached to it or to one of its ancestor GameObjects will receive that message.

Moving a GameObject

There are many ways of moving a GameObject, but the appropriate one depends on the Components attached to that GameObject. Ignoring these guidelines can result in unexpected behaviors.

Transform only

Simple GameObjects that just have a Transform can be moved changing their transform directly, changing transform.position, transform.rotation, and this equates to teleporting the object to the new position, without having it "walk" form the start to end positions. That means it will not affect the objects between the 2 positions. That is, transform.position is useful to relocate objects, but not to move a bullet across the air, as it could teleport from a position in front of object to a position behind it, without hitting its target.

If you want to simulate a movement of the object from where it is to another place, and have it cross the space in between, then use the methods Transform.translate and Transform.Rotate. Other objects on the line of movement will be informed and react accordingly (e.g. have the bullet pass through themselves).

Transform + Collider

GameObjects with a Collider component should not be moved or rotated unless they also have a RigidBody component. This is because if they do not have such components, unity assumes they are stationary and caches their positions. So, if an object needs to be moved often, such assumption will not hold and caching will degrade performances.

Transform + Collider + RigidBody

GameObject with a RigidBody component are objects affected by physics, and should be moved only using functions provided by the physics engine. Here are the available options:

  • moving to a position using rigidbody.MovePosition, this is similar to using Transform.translate, but applies physics to "push aside" objects between the 2 positions the object is moving

  • changing the velocity, applying a force or applying an acceleration, using rigidbody.AddForce and setting the ForceMode respectively to VelocityChange, Force and Acceleration

  • applying a torque force on a point, to rotate the object on a pivot, using the function rigidbody.AddForceAtPosition

Remember to set the drag and angularDrag properties of the GameObject to a value >0, or it won't stop moving and rotating.

Using the physics system is a just little more complex but has great advantages. If consistently used, it will take care of all the collision checks, free you from the need to check if a position is free every time you need to move an object, and have objects smoothly slide amongst each other instead of getting stuck into corners. If you are using transform.position to move an object, and checking if the position is free every time you move, you are probably doing it wrong.

If a moving RigidBody meets a Collider that is not a trigger, it will not penetrate it. The physics engine will take care of making the object bounce away or stop, depending on the materials the 2 objects are made of. Such materials are set in the Collider component (Collider.material).

CharacterController

A middle road that allows to move GameObjects giving absolute values or movement deltas like we would do changing the Transform and avoid entering Colliders at the same time is possible. Just implement the CharacterController, which replaces the Collider and RigidBody components of an object.

There are however some non-obvious restrictions, the Collider used internally by the CharacterController is a capsule aligned with the Y axis, that means it can't be flipped.

GameObject with a CharacterController should be moved only using its SimpleMove and Move methods, and never directly changing its Transform, unless maybe in the extreme case we are teleporting it. The methods it provides for movement can be a little confusing:

  • SimpleMove works with a speed vector, applies gravity (no way to disable this) and does not let you jump.

  • Move works with absolute movement deltas, it does not apply gravity (no way to enable it) and lets you "jump".