Unity2D: Scale a GameObject without Scaling its Children - c#

I have an enemy that has children in it; the enemy also has a death animation. Within the death animation (using the animator), I have scaled the enemy to an appropriate size. However, the children within the enemy is also being scaled down even though I have a animation on the child where I have sized it, I also added anchor positions on this child. Is there a way I can scale down the enemy but also keep the size of the child, p.s. the child is a UI text object. Thank you!

The easiest way to solve your problem that I see is to introduce another GameObject higher in the hierarchy.
This GameObject would be the parent of your enemy object and your Text object which currently is a child of the enemy.
This way you can scale the enemy independently of the Text.

Maybe (hopefully) there are better solutions but you could use a component on the child objects that always keeps the original scale inverting relative changes in the parents scale
public class FreezeScale : MonoBehaviour
{
private Vector3 originalScale;
private Vector3 parentOriginalScale;
private void Awake()
{
// afaik RectTransform inherits from Transform
// so this should also work for UI objects.
originalScale = transform.localScale;
parentOriginalScale = transform.parent.localScale;
}
private void LateUpdate()
{
var currentParentScale = Transform.parent.localScale;
// Get the relative difference to the original scale
var diffX = currentParentScale.x / parentOriginalScale.x;
var diffY = currentParentScale.y / parentOriginalScale.y;
var diffZ = currentParentScale.z / parentOriginalScale.z;
// This inverts the scale differences
var diffVector = new Vector3 (1/diffX, 1/diffY, 1/diffZ);
// Apply the inverted differences to the original scale
transform.localScale = originalScale * diffVector;
}
}
Not tested since hacked in on my mobile phone but I hope you get the idea ;)

set the child's scale to world space not local space.
local space is the default, but it will go off of the scale of the parent so when the enemy shrinks so will the text.
alternatively you could set both objects to be children of an empty object, then just scale your enemy down and the text should stay the same size since its using the scale of the empty parent, which isn't changing size either.
see here:
public static Vector3 GetWorldScale(Transform transform)
{
Vector3 worldScale = transform.localScale;
Transform parent = transform.parent;
while (parent != null)
{
worldScale = Vector3.Scale(worldScale,parent.localScale);
parent = parent.parent;
}
return worldScale;
}
just a work around though, your meant to use this:
yourtransform.LocalScale=Transform.localToWorldMatrix
but it gives me issues... the above method works well though.
transform.scale=GetWorldScale(transform);
edit: lets be clear, the easiest thing to do would be to unpraent the objext before shrinking the parent. this will separate the scales.

Related

How to "Copy World Placement" in script?

I am using Unity 2020.1.15f1.
First of all if you don't know you can right click on unity editor on a transform or rect transform select "Copy World Placement" at the bottom and get this info.
UnityEditor.TransformWorldPlacementJSON:{"position":{"x":-17.771259307861329,"y":-9.999999046325684,"z":90.0},"rotation":{"x":0.0,"y":0.0,"z":0.0,"w":1.0},"scale":{"x":1.0,"y":1.0,"z":1.0}}
This is exactly what i need in script but could not find a way to get this info from rect transform components. It does not have to be in JSON format all i need is THE SAME x and y values.
PS: transform.position is not what i want. It does not give these values for a rect transform.
Edit:The reason I could not get these values in script from position was the project was taking these values in void Awake() method which changes position values you get if you call it in void Start()
So i guess derHugos first answer is correct for this question.
For me (Unity 2020.2.5f1) this simply returns the transform.position, transform.rotation and transform.localScale regardless of whether it is a RectTransform or just a Transform.
Note though that a RectTransform in a Canvas of type Screenspace - Overlay is in pixel space!
I don't know how it works internally but basically you could do something like
public static class TransformExtensions
{
[Serializable]
private struct TransformData
{
public Vector3 position;
public Quaternion rotation;
public Vector3 scale;
public TransformData(Transform transform)
{
position = transform.position;
rotation = transform.rotation;
scale = transform.localScale;
}
}
public static string CopyWorldPlacementJson(this Transform transform, bool humanReadable = false)
{
var data = new TransformData(transform);
var json = JsonUtility.ToJson(data, humanReadable);
return json;
}
}
With that extension method anywhere in your project you can simply do
var json = someGameObject.transform.CopyWorldPlacementJson();
Debug.Log(json);
In particular to UI stuff you might have to wait for the next re-draw of the rects. You could force this using Canvas.ForceUpdateCanvases
Force all canvases to update their content.
A canvas performs its layout and content generation calculations at the end of a frame, just before rendering, in order to ensure that it's based on all the latest changes that may have happened during that frame. This means that in the Start callback and the first Update callback, the layout and content under the canvas may not be up-to-date.
Code that relies on up-to-date layout or content can call this method to ensure it before executing code that relies on it.
The reason I could not get these values in script from position was the project was taking these values in void Awake() method which changes position values you get if you call it in void Start()
So i guess derHugos first answer is correct for this question.

gameObject transform position doesn't align with parent's transform position

I am creating an inventory system, in which you can drag and drop items into slots. My problem is that when I drop my item into the slot, it doesn't align correctly.
I've tried using transform.localposition to set it correctly, but it changes absolutely nothing, they are still misaligned.
public void OnDrop(PointerEventData eventData)
{
//If there's nothing already in this slot
if(!item)
{
DragNDropController.itemBeingDragged.transform.SetParent(transform);
DragNDropController.itemBeingDragged.transform.position = this.transform.position;
}
}
Here is the result : https://i.imgur.com/iosqVAT.png
Note how the X/Y positions are -12.5/12.5. If I change it to 0/0 in the Rect Transform, then everything is aligned perfectly, but I can't seem to do it using code.
You are probably trying to set the localposition of the tranform, instead you should get the RectTransform and set its localposition.
Code:
RectTransform myRectTransform = GetComponent<RectTransform>();
myRectTransform.localPosition += Vector3.zero;
myRectTransform.anchoredPosition += Vector3.zero;
When you set a transform as a child of another.
The child position no longer representing its own position,Let's just say it is the sum of all its parent positions + its local position.
So you made it a child of a transform with position = 5,5,5.
but then you sat its position to be same as its parent which is 5,5,5.
Its total position is now 10,10,10.
You need to set its localPosition to 0,0,0, so its position is its parent position and wherever the slot goes, goes the item.
So to position it correctly at the center of the slot your code should be:
DragNDropController.itemBeingDragged.transform.SetParent(transform);
DragNDropController.itemBeingDragged.transform.localPosition = Vector3.zero;

Unity - How to get location of center of parent gameobject

I've a question about how to get the transform created by the addition of child objects to a parent. It's easier to see what I mean by looking at the picture that follows.
Tranform
. I understand that the world position of the parent is effectively at 0 but how do I get the displayed one?
Best Regards
Some info, first. That location is where the Center of that GameObject is. The alternative to having the handles there is at the Pivot, which will put the handles at the same location as Transform.position. You can toggle between those two handle display modes in the Unity Editor.
So, you want to find the location of the Center handles. That depends on if the parent has a Renderer or not.
In your situation, where the parent object has no Renderer, you have to go into the children of the gameObject and average their transform positions:
Vector3 sumVector = new Vector3(0f,0f,0f);
foreach (Transform child in parentObject.transform)
{
sumVector += child.position;
}
Vector3 groupCenter = sumVector / parentObject.transform.childCount;
If the situation were a little different and the parent gameObject had a Renderer, then it is much easier:
Vector3 groupCenter = parentObject.getComponent<Renderer>().bounds.center;
I met the same problem. Thanks for the answer of #Ruzihm.
I want to make the center, but don't know whether the object has 'Renderer', so I write recursive function like this to get a center pos of the object.
Vector3 getCenter(Transform obj)
{
Vector3 center = new Vector3();
if (obj.GetComponent<Renderer>() != null)
{
center = obj.GetComponent<Renderer>().bounds.center;
}
else
{
foreach (Transform subObj in obj)
{
center += getCenter(subObj);
}
center /= obj.childCount;
}
return center;
}

Open GUI on Certain HTC VIVE Controller Angle/Turn

I am Trying to make a GUI for HTC VIVE but having trouble in opening it on certain controller angle.
I have done some work and achieved a bit sketchy one because my object is a child which make it hard for me to track its rotation or position, as i wanted it to open only when controller is at certain angle (as a guy looking at his watch)
Here is some visual Example:
This is my controller rotation without GUI:
As i rotate the controller the GUI should show something like this:
Here is some code I have managed
void RayCastFromHead() // is just a name for Method i am raycasting from a dummy which contains left Grip button
{
if (Physics.Raycast(dummy.position, dummy.up, out hitInfo, 30))
{
transform.rotation.ToAngleAxis(out tempAngle, out tempAxis);
if (hitInfo.collider.name.Contains("Camera (eye)"))
{
if (dummy.gameObject.GetComponent<MeshRenderer>().enabled)
{
if ((transform.localEulerAngles.z > 270.0f && transform.localEulerAngles.z < 315.0f)&&
(transform.position.y > 0.9f && transform.position.y < 2f))
{
staticRotaion = transform.localRotation;
canvasOnHead.GetComponent<TweenScale>().PlayForward();
}
}
}
}
}
I do not know that it is a right method to do this kind of task? In Simple manner i want to show GUI on certain controller rotation.
This is My hierarchy what i am talking about
This is the same i wanna do with my GUI it should open when my hand angle is something like this image
There is a simple solution to handle UI rotation.
I suppose you have a canvas for your GUI. This canvas can be child of any object. If you add the canvas (root of this menu) as a child of the left hand it should move and rotate with the left hand.
Note that the render mode of the canvas must be World Space.
This is the parent (left hand):
set the values of canvas's rect transform correctly (most important part is pos.z, I changed the scale of the canvas instead of changing the z. I could change width and height of canvas but it would have adverse effects)
it will behave as you described when rotating the parent object (left hand):
Edit
Add this script to your camera
public class LookAtWatchController : MonoBehaviour
{
public GameObject menuGUI;
public GameObject hand;
void Update(){
if(transform.eulerAngles.x > 10)
{
menuGUI.transform.eulerAngles = new Vector3(transform.eulerAngles.x, 0, 0);
menuGUI.SetActive(true);
menuGUI.transform.position = hand.transform.position;
}
else{
menuGUI.SetActive(false);
}
}
}
assign the gui menu to menuGUI
assign the left hand to hand
you can also include the rotation elements of the hand in the menuGUI rotation:
menuGUI.transform.eulerAngles = new Vector3(transform.eulerAngles.x, hand.transform.eulerAngles.y, hand.transform.eulerAngles.z);
I haven't tested this yet but menuGUI.transform.eulerAngles = new Vector3(transform.eulerAngles.x, 0, 0); should also work fine.
As you see below the rotation.eulerAngles.x of camera and canvas are the same when canvas is seen right in front of camera

Make GameObject “attach” properly?

This script makes a cube "stick" to whatever it collided with. The problem is that when it's going at relatively high or medium speeds (or when the device itself is slow), the cube tends to "get a bit inside" what it collided with and then stick to it. What changes do I have to make to fix this?
In order for this script to work, one GameObject must have bool _sticksToObjects = true; and the other bool _sticksToObjects = false;
I have tried turning the Rigidbody's Collision Detection mode to either Continuous or Continuous Dynamic
I think my script depends on frame rate. That may be where the problem lies.
Normal "Attach":
Abnormal "Attach":
Rigidbody _rigidBody;
Transform _meshTransform;
bool _sticksToObjects = true;
public Transform _stuckTo = null;
protected Vector3 _offset = Vector3.zero;
void Awake()
{
GameObject CubeMesh = GameObject.FindWithTag ("CubeMesh");
GameObject Cube = GameObject.FindWithTag ("Cube");
_rigidBody = Cube.GetComponent<Rigidbody> ();
_meshTransform = CubeMesh.GetComponent<Transform> ();
}
void Update()
{
if (_stuckTo != null)
{
transform.position = _stuckTo.position - _offset;
}
}
void OnCollisionEnter(Collision collision)
{
if (!_sticksToObjects) {
return;
}
_rigidBody.isKinematic = true;
// Get the approximate collision point and normal, as there
// may be multipled collision points
Vector3 contactPoint = Vector3.zero;
Vector3 contactNormal = Vector3.zero;
for (int i = 0; i < collision.contacts.Length; i++) {
contactPoint += collision.contacts [i].point;
contactNormal += collision.contacts [i].normal;
}
// Get the final, approximate, point and normal of collision
contactPoint /= collision.contacts.Length;
contactNormal /= collision.contacts.Length;
// Move object to the collision point
// This acts as setting the pivot point of the cube mesh to the collision point
transform.position = contactPoint;
// Adjust the local position of the cube so it is flush with the pivot point
Vector3 meshLocalPosition = Vector3.zero;
// Move the child so the side is at the collision point.
// A x local position of 0 means the child is centered on the parent,
// a value of 0.5 means it's to the right, and a value of -0.5 means it to the left
meshLocalPosition.x = (0.5f * contactNormal.x);
_meshTransform.localPosition = meshLocalPosition;
if (_stuckTo == null || _stuckTo != collision.gameObject.transform) {
_offset = collision.gameObject.transform.position - transform.position;
}
_stuckTo = collision.gameObject.transform;
}
Here are some screenshots of the Unity editor:
This is a well-known category of problem in game engineering and you'll be pleased to know the solution is relatively simple. You'll be pleased to hear there are similar, but much more complicated, problems that are actually solved in the same way. I'll try to explain.
Now here's the thing. It's quite often that the following question comes up...
So I'm working on GTA. I have a humanoid, H, running around. She approaches vehicle V. She opens the door and gets in and drives off. After that everything goes to hell in Mecanim and all the code stops working. What to do?
Surprisingly, the way that is done in games is:
Surprisingly: you actually swap to totally different models at that point!!!!!
You have H and V in the game. But then you have an animation (say) for H climbing in to V. But then, you literally destroy the game objects of H and V, and you Instantiate (or just awake) a new, totally different, game object, which is D ("a car being driven around by a lady").
(If you think about it, you can see that when you do this, you carefully adjust all the stuff in D, so that it matches what was "just then happening" in the frame, in relation to both H and V. So for example, literally, you copy the transform, twist etc of the car V, to the new car-inside-D, if lady H has the SmearedMakeupEffect, you put the same SmearedMakeupEffect on the lady-within-D, you position all the bones identically, and so on.)
Another simple example of this is, you often get people asking, "my character C gets killed and I want it to become a ragdoll, how to?" In fact you just swap to a totally new game object you have all set up for that passage of the game. Indeed, if you have a character A ("Arnie") in a game, it's normal that you have 4 or 5 "different As" sitting offside the stage, so, there's "ragdoll A", "A who can dance" "A with weapon". And indeed many of these are combos, you know "A on the horse" "A in the car" and so on.
So interestingly, the "real" solution here is,
once they become a new connected thing, destroy them both and swap to a new game object altogether!
if you have made games "until you are blue in the face" from making games, this is just what you would do as a matter of course. Even though its' a simple situation, it's just easier in the long run. After all, consider all the stuff you have to do when this happens:
make hitting object child of the other
turn off physics on the child
change the way your physics works for the whole thing
turn off or change the collider on the hitting object, perhaps making it part of the overall object
you'll likely have some sort of new "separation" physics where it can be knocked-off - you'd have to turn all that on
likely change minor issues like sound effects, colors etc
As you can see it's a huge chore doing all this stuff, and indeed it's one of those things it's just "easier to do properly" and change to a new model.
All that being said, I know you want a Quick Script Solution you can Paste In :) Here it is...
Step 0, You'll create "YourScript" which goes on the "main" cube. it will "catch" another cube moving around.
YourScript will look basically like this ...
[System.NonSerialized] public bool isConnectedNow;
void OnCollisionEnter(Collision collision)
GameObject theThingWeCaught = collision.gameObject
Debug.Log("We caught this thing .. " + theThingWeCaught.name)
// make it a child of us......
theThingWeCaught.transform.parent = transform
theThingWeCaught ... set kinematic
theThingWeCaught ... probably disable the rigidbody
theThingWeCaught ... probably disable the collider
isConnectedNow = true;
That's really all you have to do.
Step 1, YOUR script must have a public bool like this
[System.NonSerialized] public bool isConnectedNow;
Step 2, Here's MyScript which goes on the hitting cube, first we'll unit-test that your isConnectedNow bool is working
public Class MyScript:MonoBehaviour // attach to the "child" cube
{
public float correctXDistance;
public float correctYDistance;
public Transform bigCube;
public YourScript yourScript;
void Update()
{
string message = yourScript.isConnectedNow ? "free" : "stuck";
Debug.Log("I am " + message);
}
}
attach, debug, and run. Make the little cube stick and unstick from the big cube .. Watch the console. it works? So add this to MyScript
private void DistanceCorrectionX()
{
float xDistance = bigCube.position.x - transform.position.x;
float xSign = Mathf.Sign(xDistance);
float xDelta = Mathf.Abs(xDistance);
float closenessPercentage = (xDelta/correctXDistance)*100f;
if ( closenessPercentage<90f || closenessPercentage>110f)
{
// they are not close enough to quantize on this axis
// this comes in to play when you have multiple axes
return; // do nothing.
}
float xShouldBe = bigCube.position.x + xSign * correctXDistance;
Vector3 p = transform;
p.x = xShouldBe; // be careful it's .y, .z etc for other axes
transform.position = p;
}
for now call that in Update() in MyScript like this
void Update()
{
Debug.Log("I am " yourScript.isConnectedNow ? "free" : "stuck");
if (yourScript.isConnectedNow) DistanceCorrectionX();
}
Now actually Play and make it stick. Now, since it's running in Update simply while Play look at the Inspector for MyScript and adjust the value of correctXDistance to get the exact look you want. When yo have decided on a value, unPlay and put that in as the final value you wish.
Next, in DistanceCorrectionX simply duplicate all the code and do it again for the Y axis DistanceCorrectionX. If you also do Z, do that.
Finally. Note you will have a lot of messy code, like this...
void Update()
{
// handle all the DistanceCorrectionX etc as seen above.
if (yourScript.isConnectedNow)
{
.. turn off the collider on me
}
else
{
.. turn on the collider on me
}
}
and so on, there's "many little things" you'll need to do.
Don't forget also, overwhelmingly you may want to make the hitting object a child of the big object, depending on your situation. (Then of course they would move around together as a unit.)
Note that in the positioning code above I just showed it as position, not local position, for pedagogic clarity. If you want to do them flinging around, and spinning and so on, you'd make the hitting object a child of the other and you would use localPosition in the same way. Enjoy.
One possible way that comes to my mind is:
Inside of the "collision enter" check the distance between these objects and move the one that should stick to the other one a bit away.
As you see in the picture the distance between A and B should be equal to the sum of the widths divided by 2 (with a small threshold of course).
If the distance is less than the sum of the widths / 2 then you have an abnormal "attach" and you have to move one of the objects away. Its not really difficult to accomplish that.

Categories