I am attempting to instantiate differently shaped prefabs next to eachother. I have a folder full of differently sized objects that allow for me to string them together randomly to create a 2D endless runner. My issue is that all the answers online about placing objects beside eachother don't work when different sized objects are used. Here is the code I am using to instantiate the sections. It works when it only randomly selects similar sized objects. When the selected prefab is a different length it leaves either a gap between, or overlaps, the previous object.
for(int i = 0; i < activeSectionsAtOnce; i++)
{
int index = Random.Range(0, sectionPrefabs.Length);
GameObject currentOb = sectionPrefabs[index];
Vector2 position = new Vector2();
if(activeSections.Count == 0)
{
//set a (0, 0) position for first object
position = Vector2.zero;
}
else
{
//get most recently placed object from list
GameObject lastOb = activeSections[activeSections.Count - 1];
//my attempt at getting bounds for the last object and for the next prefab that will be used
Bounds lasObbounds = new Bounds(lastOb.transform.position, Vector2.zero);
Bounds currentBounds = new Bounds(currentOb.transform.position, Vector2.zero);
//Include child objects(all have sprite renderers)
foreach (Renderer r in lastOb.GetComponentsInChildren<Renderer>())
{
lasObbounds.Encapsulate(r.bounds);
}
foreach (Renderer rend in currentOb.GetComponentsInChildren<Renderer>())
{
currentBounds.Encapsulate(rend.bounds);
}
//position object will be instantiated at
position.x = lastOb.transform.position.x + (lasObbounds.size.x / 2) + (currentBounds.size.x/2);
}
//instantiate at selected position
activeSections.Add(
Instantiate(currentOb, position, Quaternion.identity)
);
}
This is in the start method. I'm not sure if the problem is how I'm creating the bounds, or how im mathematicaly creating the x coordinate. This is the expected result that happens when I use same sized prefabs. This is what happens with different prefabs
The shorter protrusion from the first object should be cleanly between the other two without a gap.
Here's an image of one prefab
Thanks for any advice
Related
I am making a game, in which I have clouds that when shot are hidden using sprite masks, the sprite masks are then attached to the cloud via parenting, here is the function that attaches and positions the masks.
public void SpawnGasCloudMask(Transform collisionTransform, Vector2 difference)
{
GasCloudMaskController selectedGasCloudMask = null;
for (int i = 0; i < gasCloudMaskList.Count - 1; i++)
{
if (!gasCloudMaskList[i].gameObject.activeSelf)
{
selectedGasCloudMask = gasCloudMaskList[i];
break;
}
}
if(selectedGasCloudMask == null)
{
selectedGasCloudMask = InstantiateGasCloudMaskController();
}
selectedGasCloudMask.transform.SetParent(collisionTransform);
float angle = collisionTransform.rotation.z;
Vector2 straightPosition = new Vector2(difference.x / collisionTransform.localScale.x, difference.y / collisionTransform.localScale.y);
selectedGasCloudMask.transform.localPosition = straightPosition;
selectedGasCloudMask.gameObject.SetActive(true);
}
enter image description here
This is the result when the cloud is not rotated, However if I rotate it, say 90 degrees.enter image description here
I have tried using RotateAroundLocal, but it is said to be deprecated and I cannot see it having an effect. Obviously what I want is for the masks to be properly attached to the parent based on its rotation, I tried doing some geometry, but solving this remains yet out of my reach. I believe I have to change its local parameter position, I did manage to do it with non parented gameObject, however the local parameter does not offer the RotateAround function. I will appreciate any help.TY.
I found my answer:
selectedGasCloudMask.transform.RotateAround(collisionTransform.position,
-collisionTransform.forward, collisionTransform.eulerAngles.z);
Since the position to which the child attaches to the parent, you need to account for the parent's rotation. The deprecate function that did exactly that was replaced by the generic function, which requires you to understand what is going on.
I've searched around and can't seem to find a write up on the calculations I might need for placing an object onto another one without them placing halfway inside of eachother.
private void MovePlaceableObject()
{
Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward);
Debug.DrawRay(ray.origin, ray.direction * 20f);
RaycastHit hit;
if(Physics.Raycast(ray, out hit, 15f))
{
Vector3 newPosition = hit.point;
currentPlaceableObject.transform.position = newPosition; //move object to where we have the mouse
currentPlaceableObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, hit.normal); //rotation equal to the current rotation of our hitinfo, then up
}
}
private void HandeNewObjectHotkey()
{
if (Input.GetKeyDown(newObjectHotkey))
{
if(currentPlaceableObject == null)
{
currentPlaceableObject = Instantiate(placeableObjectPrefab);
//currentPlaceableObject.GetComponent<BoxCollider>().enabled = false;
}
else
{
Destroy(currentPlaceableObject); //pressing the button again will destroy current object
}
}
}
I've tried storing the object I'm looking at's size to floats for x,y,z and a vector3 for it's position, and tried fiddling around with adding those to the newPosition with the hit.point to no avail.
I've wracked my brains for about a day now, i'm terrible at math, however I know that you must need to get the current object your looking ats position or size, then factor that into your placement right?
Get the size of the object that you are looking at by accessing its collider component. You can use the bounds property of the collider to get its size.
Bounds objectBounds = hit.collider.bounds;
Calculate the center point of the object by adding half of its size to its position.
Vector3 objectCenter = hit.collider.transform.position + objectBounds.extents;
Calculate the position of the new object by adding the size of the new object to the center point of the object that you are looking at. This will place the new object on top of the other object.
Bounds newObjectBounds = currentPlaceableObject.GetComponent<Collider>().bounds;
Vector3 newObjectPosition = objectCenter + new Vector3(0, newObjectBounds.extents.y, 0);
Set the position of the new object to the calculated position.
currentPlaceableObject.transform.position = newObjectPosition;
You can also set the rotation of the new object to match the normal of the surface that it is being placed on, as you are already doing in your code.
currentPlaceableObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, hit.normal);
I have following problem with my Unity3D project. I will try to describe it on images.
So I want to merge to objects into 1. When object 1 and 2 collide, then they merge (object 2 became child of object 1). In practice I made it when bolt in object 1 (this blue "something" is bolt) collide with object 2, then they should merge. And I want to position object 2 on top of object 1 (saying top I mean where the red lines are, right image in second picture). So I decide to set localPosition of second object to be equal to bolt's localPosition (bolt is child of object 1 too). But that was wrong (second image, left side). So I get idea that I should add half of second object's height to one of his axis. But still it isn't perfect. And also I don't know to which axis I should add it. And should I use localPosition or normal position when I'm adding this half of height?
My code:
void OnTriggerEnter(Collider c) {
if(transform.IsChildOf(mainObject.transform)) {
childObject.transform.SetParent (mainObject.transform);
childObject.transform.localPosition = boltObject.transform.localPosition;
childObject.transform.position = new Vector3 (childObject.transform.position.x, childObject.transform.position.y, (float)(childObject.transform.position.z + childObject.GetComponent<Renderer>().bounds.size.z));
}
}
I have to add that objects can have different size, so I can't just add a number, it must be flexible. I will be very grateful for any help.
EDIT:
This my whole code:
using UnityEngine;
using System.Collections;
public class MergeWithAnother : MonoBehaviour {
public GameObject mainObject;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnTriggerEnter(Collider c) {
if(transform.IsChildOf(mainObject.transform)) {
if (c.gameObject.name == "holeForBolt" && c.gameObject.transform.parent.gameObject != mainObject) {
Destroy (c.gameObject.transform.parent.gameObject.GetComponent("MouseDrag"));
Destroy (c.gameObject.transform.parent.gameObject.GetComponent("RotateObject"));
c.gameObject.transform.parent.gameObject.transform.SetParent (mainObject.transform);
c.gameObject.transform.parent.gameObject.transform.localPosition = gameObject.transform.localPosition;
c.gameObject.transform.parent.gameObject.transform.position = new Vector3 (c.gameObject.transform.parent.gameObject.transform.position.x, c.gameObject.transform.parent.gameObject.transform.position.y, (float)(c.gameObject.transform.parent.gameObject.transform.position.z + c.gameObject.transform.parent.gameObject.GetComponent<Renderer>().bounds.size.z));
c.gameObject.transform.parent.gameObject.transform.localRotation = gameObject.transform.localRotation;
c.gameObject.transform.parent.gameObject.transform.localRotation = new Quaternion (360 + c.gameObject.transform.parent.gameObject.transform.localRotation.x, c.gameObject.transform.parent.gameObject.transform.localRotation.y, c.gameObject.transform.parent.gameObject.transform.localRotation.z, c.gameObject.transform.parent.gameObject.transform.localRotation.w);
Destroy (c.gameObject.transform.parent.gameObject.GetComponent<CapsuleCollider>());
Destroy (gameObject);
Destroy (c.gameObject);
CapsuleCollider cc = mainObject.GetComponent<CapsuleCollider>();
cc.height *= 2;
cc.center = new Vector3(0, 0, 1);
}
}
}
}
I will explain what that means:
MainObject is the 1st object in picture.
c.gameObject.transform.parent.gameObject is the 2nd object from
picture
gameObject is bolt (blue something in 1 object)
script is attached in bolt (blue something on picture)
just use the position of your first object and bounds.size:
vector3 posOfSecObject = new vector3(0,0,0);
posOfSecObject.y +=
FIRSTOBJECT.GetComponent().Renderer.bounds.size.y;
I used the Y axis, I don't know which one you need, just try ;) I used this code to build a house composed of floors
I am trying to make a Scrabble word game using a fixed camera, but I have a simple issue.
I add some boxes as game objects and the number of these boxes is the length of the word so if the word is "Fish" we will add 4 boxes dynamically. I did that successfully, but I can't center those boxes in the screen. I tried to add those game objects as children under another game object, then center the parent, but with no effect.
This is my code:
void Start () {
for (int i=0; i<word.Length; i++) {
GameObject LetterSpaceObj = Instantiate(Resources.Load ("LetterSpace", typeof(GameObject))) as GameObject;
LetterSpaceObj.transform.parent = gameObject.transform;
LetterSpaceObj.transform.localPosition = new Vector2(i*1.5f,0.0f);
LetterSpaceObj.name = "LetterSpace-"+count.ToString();
count++;
}
gameObject.transform.position = new Vector2 (0.0f, 0.0f);
}
This image shows you the idea:
I believe your code is working, but the problem is that your first letter is located at your parent object, and then every letter from then on is added to the right. That means that when you center the parent object what you are doing is putting the first letter in the center of the screen.
If you run the game and use the scene view to look at where the parent object is this can confirm this. What you can do instead is that rather than placing the parent at the center of the screen, offset it by an amount equal to the length of the word.
gameObject.transform.position = new Vector2 (-(word.Length / 2.0f) * 1.5f, 0.0f);
You might also want to consider changing some of those constants, such as the 1.5f into variables with names like LetterSize or basing it off the actual prefab so that way any future changes will work automatically.
This is the last solution after some edits to fix this issue.
GameObject LetterSpaceObjRow;
int count = 1;
string word = "Father";
float ObjectXPos;
float LocalScaleX;
void Start () {
for (int i=0; i<word.Length; i++) {
GameObject LetterSpaceObj = Instantiate(Resources.Load ("LetterSpace", typeof(GameObject))) as GameObject;
LocalScaleX = LetterSpaceObj.transform.localScale.x;
ObjectXPos = i*(LocalScaleX+(LocalScaleX/2));
LetterSpaceObj.transform.parent = gameObject.transform;
LetterSpaceObj.transform.localPosition = new Vector2(ObjectXPos,0.0f);
LetterSpaceObj.name = "LetterSpace-"+count.ToString();
count++;
}
gameObject.transform.position = new Vector2 (-(ObjectXPos/2.0f) , 0.0f);
}
How can I get the actual position of an object in a grid layout? In my current iteration symbolPosition keeps returning 0,0,0.
public void OnPointerClick (PointerEventData eventData)
{
// Instantiate an object on click and parent to grid
symbolCharacter = Instantiate(Resources.Load ("Prefabs/Symbols/SymbolImage1")) as GameObject;
symbolCharacter.transform.SetParent(GameObject.FindGameObjectWithTag("MessagePanel").transform);
// Set scale of all objects added
symbolCharacter.transform.localScale = symbolScale;
// Find position of objects in grid
symbolPosition = symbolCharacter.transform.position;
Debug.Log(symbolPosition);
}
The position value wont be updated until the next frame. In order to allow you to make a lot of changes without each one of them causing an entire recalculation of those elements it stores the items that need to be calculated and then updates them at the beginning of the next frame.
so an option is to use a Coroutine to wait a frame and then get the position
public void OnPointerClick (PointerEventData eventData)
{
// Instantiate an object on click and parent to grid
symbolCharacter = Instantiate(Resources.Load ("Prefabs/Symbols/SymbolImage1")) as GameObject;
symbolCharacter.transform.SetParent(GameObject.FindGameObjectWithTag("MessagePanel").transform);
// Set scale of all objects added
symbolCharacter.transform.localScale = symbolScale;
StartCoroutine(CoWaitForPosition());
}
IEnumerator CoWaitForPosition()
{
yield return new WaitForEndOfFrame();
// Find position of objects in grid
symbolPosition = symbolCharacter.transform.position;
Debug.Log(symbolPosition);
}
This may not have been in the API when this question was asked/answered several years ago, but this appears to work to force the GridLayoutGroup to place child objects on the frame in which child objects are added, thus removing then need for a coroutine.
for(int i = 0; i < 25; i++){
GameObject gridCell = Instantiate(_gridCellPrefab);
gridCell.transform.SetParent(_gridLayoutTransform,false);
}
_gridLayoutGroup.CalculateLayoutInputHorizontal();
_gridLayoutGroup.CalculateLayoutInputVertical();
_gridLayoutGroup.SetLayoutHorizontal();
_gridLayoutGroup.SetLayoutVertical();
From here, you can get the positions of the 'gridCells' same frame.
There is another function, which seems like it should fulfill this purpose, but it isn't working for me. The API is pretty quiet on what exactly is going on internally:
LayoutRebuilder.ForceRebuildLayoutImmediate(_gridLayoutTransform);