MERTCAN AKARDERE

Code Sample: Relative Object Placement in Unity

For intelligent user interface project, I was implementing level design with speech commands. Upon exploratory testing I noticed the most intuitive way to place objects was to describe position in relative to other objects. Therefore I defined methods like creating an object on top of another one.

The Gist for the can be accessed here.

Here I will explain how I coded this step-by-step:
/**
* Params:
* childName: the name of the object that is being created(ex: vase)
* parentName: the name of the object that is being used to describe the relative destination(ex: table)
* Pre: none
* Post: prefab with name childName is created on top of the parentName.
**/

public void createOnTop(string childName, string parentName)
{
GameObject parent = GetClosest(parentName);
float height = GetObjectHeight(parent);
GameObject child = createPrefab(childName);
child.transform.position = parent.transform.position;
child.transform.localPosition = new Vector3(child.transform.position.x, height, child.transform.position.z);
// can't change only y. have to declare a new vector3

lastObject = child;
moveControls.autoSelect(child);
}
First we need to find the game object referred when the speech command is given. For example, if the user says “place a vase on the table” we need to determine which object is referred by table. For this I decided query every object on the screen, and find the one with the name “table” that is closest to the center of the screen.
public GameObject GetClosest(string prefabName)
{
return GetClosest(prefabName, tag_levelobjects);
}
public GameObject GetClosest(string prefabName, string tagName) // Thanks to bigmisterb from unity forums.
{
GameObject[] objects = GameObject.FindGameObjectsWithTag(tagName);

GameObject closest = null;
float dot = -2.0f;
foreach (GameObject obj in objects) // if prefabName!
{
if (obj.name.ToLower() == prefabName || obj.name.ToLower() == prefabName + "(clone)" || obj.name.ToLower() == prefabName + " (1)")
{
// store the Dot compared to the camera's forward position (or where the object is locally in the camera's space)
// Very important that the point is normalized.
//Vector3 t = Camera.main.transform.position;
Vector3 localPoint = Camera.main.transform.InverseTransformPoint(obj.transform.position).normalized;
Vector3 forward = Vector3.forward;
float test = Vector3.Dot(localPoint, forward);
if (test > dot)
{
dot = test;
closest = obj;
Debug.Log("closest: " + closest.name);
}
}
else { Debug.Log(obj.name + " " + prefabName); }
}
return closest;
}
The code that finds the closest object to the center of the screen is courtesy of bigmisterb from unity forums. I just added the conditions so it only checks the objects with the right tag, and checks the object name with (duplicate) or (1) concatenated in case the object was duplicated or multiples were created.


The next step was finding the parent object’s height. It’s important to note Unity keeps game object’s locations in relative to their parent object, and their sizes can adjusted. So I found the safest way to find the top of the object was to get object position, which is the center by default and add half of the renderer’s height.
max = obj.transform.position.y + (obj.GetComponent<Renderer>().bounds.size.y / 2);
But some 3D assets I used had no renderer, instead it had children with renderers. For example table had table’s top and table’s legs as its children objects. So I added a condition to check for renderer, then run a loop to check for each renderer and find their top.
public float GetObjectSizeOn(GameObject obj, int dim)
{
float max = 0;
{
if (obj.GetComponent<Renderer>() != null)
max = obj.transform.position.y + (obj.GetComponent<Renderer>().bounds.size.y / 2); // center height + object total height/2
}
else
{
float check = 0;
foreach (Renderer renderer in obj.GetComponentsInChildren<Renderer>())
{
check = renderer.transform.position.y + (renderer.bounds.size.y / 2);
if (check > max)
max = check;
}
}
return max;
}
Relative locations help here, as table legs’ renderer is longer, but still return a lower y value than the top of the table top.

Then an object with the childName is created. The createPrefab function looks at namedEntities dictionary that is string to prefab, and creates a game object from that prefab. Then the object is created at the center of the top of the parent object.
public GameObject createPrefab(string prefabName)
{
return createPrefab(prefabName, Vector3.zero);
}
public GameObject createPrefab(string prefabName, Vector3 position)
{
GameObject pre;
Quaternion rotation = new Quaternion(0, 0, 0, 0);
if (namedEntities.TryGetValue(prefabName, out pre)) // get game object from Entity Dictionary
{
pre = Instantiate(pre, position, pre.transform.rotation) as GameObject;
Debug.Log("add");
saver.addCreatedObject(prefabName);
lastObject = pre;
moveControls.autoSelect(pre);
return pre;
}
return null;
}