Positioning child relative to parent, localPosition not working.

I need to start by saying, this is probably a very simple issue I’m just overlooking.

I am instantiating a prefab at runtime, giving it a parent, and trying to set it’s position to (0, -0.5, 0) relative to the parent, so it’s just below the ground. No matter what way I try this, it keeps being created relative to the world coords in the middle of the map.

I’ve been searching this for hours, and all I keep hearing is “Set localPosition rather than position”, and I know that this should be the fix for this, but it’s not working. I must be missing something.

Here’s the guilty method, with the instantiating being right at the top. This method is being called in a script as part of the parent object, incase that’s relevant.

public void CollisionDetected(Collision collision) {
         spikeChild = Instantiate(spikeChildPrefab, transform).transform;
         spikeChild.localPosition = spikesDownPosition;
         print("Spike Parent " + spikeChild.parent);
        
         if (currentState == SpikeState.SpikesDown) {
             currentState = SpikeState.Extending;
             var targetsHealthSystem = null as HealthSystem;
             if ((targetsHealthSystem = collision.gameObject.GetComponent<HealthSystem>()) != null) {
                 targetsHealthSystem.TakeDamageOverTime(5, 3);
             }
         }
     }

The parent is being set fine, I’ve tried setting InstantiateInWorldSpace to false, everything I can think of, but the GODDAMN COORDS ARE LAUGHING IN MY FACE.

Please tell me where I’m being stupid. Thanks.

Looks fine to me. If you check the child’s position in Unity Editor, what coords does it actually get?

Maybe it’s something to do with the parent’s scale/rotation:

(Unity - Scripting API: Transform.localPosition)

I think I used to suffer from this/something like this, so I explicitly used SetParent before doing any translation:

GameObject cloudobj = Instantiate(ChatCloudImagePrefab, gameObject.transform);
cloudobj.transform.SetParent(OurCloudsHolderObject.transform, true);
//set local position etc

In the inspector, if the parent is at position (10, 1, 5), then the child shows up at (-10, -1.5, -5), because it’s being set to 0, -0.5, 0 in the world.
I thought the same thing about the parents scale/rotation, but even setting the scale to (1, 1, 1) and rotation to (0, 0, 0) doesn’t change anything. Same thing with the SetParent method, I’ve tried that, as well as just straight up doing transform.parent to set the parent, doesn’t change anything either.

It’s doing my head in to be honest. If I had to I could just position it in relation to the world using the parents transform, but I feel like whatever it is I’m missing could start to create bigger problems in the future when I’m working with more complex models, so I’d like to find out what it is that’s being weird.

I’ve read that localPosition works the same as position whenever there is no parent object, so I’m thinking there’s something I don’t know about parenting an object maybe? But that’s still being set correctly, so Idk at this point.

All I can think of is that maybe some other script is interfering … worst comes to worst I don’t think anyone’s gonna blame you for using a hacky workaround like child.transform.position = parent.transform.position + new Vector3(0, -0.5, 0). Or you could even try waiting a frame and then setting localPosition. If that still doesn’t work, I would suspect some script is interfering.

Other than that I can only suggest the obvious, like ensuring that they actually do get parented (i.e. one is below the other in the hierachy) and that the parent is actually at 10, 1, 5 and not actually at 0, 0, 0, like you say. I would also double-check that the ‘transform’ you are parenting it to is actually the correct transform.

Might just be one of those weird things. I’ve had similar problems before - I think there’s possibly something wrong with Unity under the hood. Or maybe it’s just not as easy as we think :stuck_out_tongue:

If you Debug.Log($“spikeDownPosition: {spikeDownPosition}”) on line 3 of your snippet, what does it print?

Here’s an image of the inspector the moment after the Log is printed. I’ve printed the value of spikesDownPosition, the actual value of spikeChild.position after it’s localPosition is being set, as well as printing transform.parent, and it’s showing SpikeTrap as the parent, which matches with the list on the left.

Spikes, selected in the inspector, is the child object which was just created, and it’s position in the inspector is different than it’s position in the Debug.Log. The Debug.Log position is where it should be in the world, because the parent is at (10, 1, 5), but it’s not. (Note: The -1.442968 is because it immediately starts moving upwards once the trap is triggered, it’s actually created at -1.5).

I’ve also included the modified method with the Debug’s included, just incase there’s any confusion.

public void CollisionDetected(Collision collision) {
        spikeChild = Instantiate(spikeChildPrefab, transform).transform;
        spikeChild.localPosition = spikesDownPosition;

        Debug.Log($"spikeDownPosition: {spikesDownPosition}");
        Debug.Log($"Actual spike Position: {spikeChild.position}");

        Debug.Break();

        print("Spike Parent " + spikeChild.parent);
      
        if (currentState == SpikeState.SpikesDown) {
            currentState = SpikeState.Extending;

            var targetsHealthSystem = null as HealthSystem;
            if ((targetsHealthSystem = collision.gameObject.GetComponent<HealthSystem>()) != null) {
                targetsHealthSystem.TakeDamageOverTime(5, 3);
            }
        }
    }

1 Like

Hmm… Would you mind sharing the code that handles this movement? I wonder if maybe that code is moving the object back to the origin?

1 Like

It’s done here with a Lerp.

private void MoveSpikes() {
        if (currentState == SpikeState.Extending) {
            spikeChild.position = Vector3.Lerp(spikesDownPosition, spikesUpPosition, currentTimeExtending / extendingTime);

            if (spikeChild.position == spikesUpPosition) {
                currentState = SpikeState.SpikesUp;
                currentTimeExtending = 0;
            }
        } else if (currentState == SpikeState.Retracting) {
            spikeChild.position = Vector3.Lerp(spikesUpPosition, spikesDownPosition, currentTimeRetracting / retractingTime);

            if (spikeChild.position == spikesDownPosition) {
                currentState = SpikeState.SpikesDown;
                currentTimeRetracting = 0;
            }
        }
    }

And the spikesDownPosition and spikesUpPosition are being set in the Awake method.

spikesUpPosition = new Vector3(0, 0, 0);
spikesDownPosition = new Vector3(0, -0.5f, 0);

Yep, I think that’s your problem! You should try replacing all mentions of spikeChild.position in this script with spikeChild.localPosition

private void MoveSpikes() {
        if (currentState == SpikeState.Extending) {
            spikeChild.localPosition = Vector3.Lerp(spikesDownPosition, spikesUpPosition, currentTimeExtending / extendingTime);
            if (spikeChild.localPosition == spikesUpPosition) {
                currentState = SpikeState.SpikesUp;
                currentTimeExtending = 0;
            }
        } else if (currentState == SpikeState.Retracting) {
            spikeChild.localPosition = Vector3.Lerp(spikesUpPosition, spikesDownPosition, currentTimeRetracting / retractingTime);
            if (spikeChild.localPosition == spikesDownPosition) {
                currentState = SpikeState.SpikesDown;
                currentTimeRetracting = 0;
            }
        }
    }
3 Likes

Goddamnit, I knew it was gonna be something simple haha. Yeah, this is the part I was overlooking, thanks for the help both of you.

2 Likes

Thanks this thread and PraetorBlue!
I had the same problem with script interference: one tries to reposition child to new parent and another tries to reposition the same child relative to it’s original position (from original parent).