Community
cancel
Showing results for 
Search instead for 
Did you mean: 
MartyG
Honored Contributor III
909 Views

Unity Tip: Controlling Multiple Objects With One TrackingAction

Hi everyone,

If you have a project where you are using multiple TrackingAction scripts to control different objects, you will be familiar with the principle that the more TrackingActions you introduce, the greater the movement instability that you potentially introduce.  This is because even if objects are linked together, each TrackingAction acts independently and so two objects with the same TrackingAction settings may behave differently - for example, one may jump its position more than the other when the hand or face is detected.

However, I have designed a mechanism that enables an object without a TrackingAction to take its movement inputs from another object with a TrackingAction in.  The logic behind this is that if you have two objects that travel in the same direction - such as the lifting of an upper arm and lower arm - then why not just have the lower arm follow the inputs given to the TrackingAction in the upper arm instead of both of them using independent hand inputs and "doing their own thing".

In this guide, I will be introducing the concept of giving an object a "tag" name and using that tag as a reference in a non-TrackingAction object to borrow information from that tagged object in real-time.  

STEP ONE

Highlight the object that contains the TrackingAction that will provide the control inputs for your other objects.  At the top of the Unity 'Inspector' panel is a 'Tag' option, with a menu beside it that is set to 'Untagged' by default.

1.jpg

STEP TWO

We need to create a tag name for our TrackingAction-powered object so that whenever we quote the name in a script, Unity will find that object's settings using the tag to locate it.

Tags can also be used to group together multiple objects under a single tag-name so that you can change all of the objects using that tag with a single line of code instead of communicating with them individually.  For example, if you had an avatar hand with several TrackingAction-powered rotation joints on each finger, you could have a TrackingAction in the finger's base joint, tag that joint and then tell all the other joints in that finger to take their rotation from the tagged joint.  So one TrackingAction could bend an entire finger.  

Whilst it is possible to do this with a TrackingAction in every individual joint, it is not guaranteed that every joint will have the exact same rotation behavior.  By copying the TrackingAction's rotation behavior to all the other joints via a tag, you ensure that all joints in that finger will rotate uniformly.

Anyway, for now though we will focus on using a tag to control the movement of just one other object.

Left-click on the 'Untagged' menu option to drop the menu down and select the 'Create Tag' option at the base of the menu to bring up the 'Tags & Layers' window inside the Inspector, where you can define a new tag.

Click on the 'Tag 0' text box and type in the name that you want to tag your TrackingAction-controlled object with.. In my own project, I chose to give the right-side shoulder joint of my avatar - which guides the position of the lower arm attached to it - the tag name -Right Arm'.

2.jpg

3.jpg

STEP THREE

Now that our TrackingAction object is tagged, we need to write a C# script for our non-TrackingAction rotation joints so that they can take their rotation information from the TrackingActioned joint.

In my part guide articles, I used an instruction called GameObject.Find to communicate between scripts in different objects.  I have learned though that developers commonly avoid this instruction, because it is implemented in Unity in a way that can cause lag   This is because if it is used in an Update() type function, it makes Unity constantly check the status of the object that is the target of the Find instruction.  

It is considered to be better to instead use a related instruction called GameObject.FindWithTag, as tags do not get checked and indexed repeatedly by Unity in the same way that it does with its slower GmeObject.Find cousin.  This is the reason why we are using tags in this guide, and in subsequent guides in the future wherever possible.

Anyway ... highlight the object that you want to be able to be rotated without a TrackingAction inside it and create a C# script file inside the object, then open that script in the Unity editor.  You will see the usual default block of code that Unity places in every new C# script, in the same way that it places default code in a new JavaScript file.

I chose to call my own script 'RotationFrom_RightShoulder', to describe the function of the script (taking rotation information from the rotation joint of the avatar's right shoulder).

using UnityEngine;
using System.Collections;

public class RotationFrom_RightShoulder : MonoBehaviour {

	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
	
	}
}

STEP FOUR

We need to inform Unity that we will be wanting to find an object using a tag-name.  To do this, we must define a variable of the 'GameObject' type in the header of the script (outside of the Start() and Update() functions) and give the variable a reference name that is ideally different from the tag name.  I gave my variable the reference name 'RightArmMotion'.

using UnityEngine;
using System.Collections;

public class RotationFrom_RightShoulder : MonoBehaviour {

public GameObject RightArmMotion;

void Start () {
	
	}
	
void Update () {
	
	}
}

STEP FIVE

In the Start() function of the script - which runs only once and then stops when the script is run - we need to tell Unity what to do with the tag-variable that we have just created.  What we will do is store inside the variable the instruction that will enable Unity to find the TrackingActioned action via the tag name.

The basic format is <tag variable name> = <"instruction to be stored in the variable, placed in speech marks">;

using UnityEngine;
using System.Collections;

public class RotationFrom_RightShoulder : MonoBehaviour {

	public GameObject RightArmMotion;

	void Start () {

		RightArmMotion = GameObject.FindWithTag ("Right Arm");

	}

	void Update () {

	
	}
}

It is possible to communicate with a tagged object without using a variable.  However, we are purposely writing the script this way to reduce the amount of lag that it generates in the project whilst it is running.  

Every time that a script runs an instruction, it creates a small lag spike.  If this only happens once then it would be barely noticeable.  However, if the instruction is in an Update() type function then Unity will check it and re-check it every single second.  This multiple checking causes the project to be slowed down.

However, if you store an instruction as a variable in the run-once-only Start() function of the script then the instruction is "cached" in the variable so that Unity only needs to read it once when the script first runs.

STEP FIVE

Our script can now connect to the tagged object with the TrackingAction in.  However, it cannot yet do anything once it has made that connection.  So we need to program the script to do something useful with the link - namely, copy the rotation information from an axis of the TrackingAction and substitute it into the appropriate rotation axis in our non-TrackingAction object.

To tell Unity that we want the rotation of the object to be able to be affected, we need to define a 'Transform' type variable in the header of the script, and - like we did before with the tag's variable, give it a custom name.  I chose to give my rotation variable the name 'rotator', again choosing a title that informs at a glance what that variable's purpose is.

using UnityEngine;
using System.Collections;

public class RotationFrom_RightShoulder : MonoBehaviour {

	public GameObject RightArmMotion;

	public Transform rotator;

	void Start () {

		RightArmMotion = GameObject.FindWithTag ("Right Arm");

	}

	void Update () {
	
	}
}

STEP SIX

Like we did with the tag variable, we need to tell Unity in the Start() section of the script what instruction will be stored inside our rotation variable.  Once again, it is possible to write a script that does not require a variable to control the rotation.  But like with the tag variable, by using one we are caching the instruction so that Unity does not have to keep re-loading it, and so ensuring that potential lag is minimized.

In Unity, objects are commonly rotated with the instruction 'transform.Rotate'.  We do not want to make a direct call to the transform in the Update() function though because we want to minimize lag.  So what we do instead is set up our rotation variable in the Start() function to store the 'transform' term inside our 'rotator' variable, with the instruction 'rotator - transform'.

using UnityEngine;
using System.Collections;

public class RotationFrom_RightShoulder : MonoBehaviour {

	public GameObject RightArmMotion;
	public Transform rotator;

	void Start () {

		RightArmMotion = GameObject.FindWithTag ("Right Arm");

		rotator = transform;

	}

	void Update () {
	
	}
}

STEP SEVEN

The final step is to place a rotation instruction in the infinitely-looping Update() section of the script.  This will enable the script to repeatedly check the rotation angle of an angle of a particular axis of the TrackingActioned object in real-time and substitute that rotation angle into our non-TrackingAction object so that the object rotates at the same time as the TrackingActioned object rotates.

If we were not using a tag, such an instruction to read the angle of another object and copy it would look something like this, using the dreaded GameObject.Find command:

transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler (transform.eulerAngles.x, GameObject.Find("Chest Section New").transform.eulerAngles.y, transform.eulerAngles.z), Time.deltaTime * 1.5f);

The above line would tell the script to do the following:

(a) Set the angles of the X and Z axes of the object to whatever the current angle of those axes is.  So if X and Z = 0 degrees, make those axes have an angle of 0 degrees.  It is basically telling the script to "do nothing" with those axes.

(b) Look at the Y axis of a separate object called 'Chest Section New', read the current rotation angle of that object's Y axis and substitute that angle into the Y rotation axis of the object that the script is housed within (so that as the angle of the Chest changes, the rotation of the scripted object matches that rotation path).

There are two things that are important about the above equation:

1. The letter on the end of the 'transform.eulerangles' statement (x, y or z) tells Unity which rotational axis of an object it should be looking at.  So if we say 'GameObject.Find("Chest Section New").transform.eulerAngles.y',  what we are saying is look at the Chest Section New object, and then look at what the current Y rotation axis value of that object is.  If we wanted that object's X or Z axis to be read, we would put .x or .z on the end of the statement instead of .y.

2.  Within the bracket of the equation, where you place an instruction determines which axis of your non-TrackingAction object will be rotated, as the bracket uses the format (X, Y, Z) in that order.  

So if you look at the transform.rotation instruction above, you will see that the GameObject.Find instruction was placed in the middle of the bracket, telling Unity to affect the rotation angle of the Y axis of the object that the script is housed in.  If you wanted to change the X axis then you would place the GameObject.Find statement at the start of th ebracket, and it Z was to be affected then you would place it at the end of the bracket.

E.g for the X axis:

transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler (GameObject.Find("Chest Section New").transform.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z), Time.deltaTime * 1.5f);

And for the Z axis:

transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler (transform.eulerAngles.x, transform.eulerAngles.y, GameObject.Find("Chest Section New").transform.eulerAngles.z), Time.deltaTime * 1.5f);

The reason why we went to the effort of explaining how the formatting of rotation equation brackets work is so that the structure of the Tag version of our rotation instruction will make more sense to you!

As we said earlier, we are not actually going to use the inefficient GameObject.Find instruction.  Instead, we are going to place in the looping Update() section of our script a rotation equation that substitutes our cached tag and rotation variables into it.  Because the update() function's contents are checked every second, it will ensure that the script repeatedly compares the angle of the TrackingAction-powered object to the non-TrackingActioned one and updates the angle of the latter to match the angles produced by the camera control inputs.

Finally, here is the complete listing of our script.

using UnityEngine;
using System.Collections;

public class RotationFrom_RightShoulder : MonoBehaviour {

	public GameObject RightArmMotion;
	public Transform rotator;

	void Start () {

		RightArmMotion = GameObject.FindWithTag ("Right Arm");

		rotator = transform;

	}

	void Update () {

rotator.rotation = Quaternion.Euler (rotator.eulerAngles.x, rotator.eulerAngles.y, RightArmMotion.transform.eulerAngles.x);

	
	}
}

In the Update section, we have replaced the usual 'transform' term with the name of our 'rotator' variable that has the 'transform' term cached inside it.  Inside the bracket, the 'RightArmMotion' tag variable is placed in the script in place of the term 'GameObject.FindwithTag("Right Arm").  When the script runs, it substitutes the stored instructions into the equation in place of their short-hand variable names, ensuring that the script does the same as it would if the equation had been written out in full, but does it in a way that considerably reduces lag generated by the script when it is running.

If you look at the above equation, you will notice something odd though.  It is saying "keep the X and Y angles the same, and give the Z angle the value of the TrackingActioned object's X angle'.  Why would we put an X axis value into the Z axis of our object?  If you remember, we stated that an equation has two parts - the axis of the TrackingAction-equipped object that you are looking at, and the axis that we want to affect in the non-TrackingActioned object containing the script.

In my avatar, although both the shoulder and lower arm move up and down when the user's hand is moved up and down, the orientation of the joint objects means that they actually use different axes to move in these directions.  The shoulder moves up and down along the Z axis, whilst the lower arm moves up and down along the X axis.

Therefore, in our rotation equation we have set it up to tell Unity "Hey, take a look at what the Z axis of the TrackingActioned object is doing.  Then paste the current value of that axis into the X rotation axis of our non-TrackingActioned object".  By doing this, the lower arm will move up and down when the shoulder moves up and down, even though they are using different axes.

CONCLUSION

Once the script was complete and there were no errors present, I did a test run.  When I moved the shoulder up and down, the lower arm also flexed up and down, following the up-down movement axis of the shoulder.  Success!

If you have any questions about this guide, please feel free to ask them in the comments below!

0 Kudos
0 Replies
Reply