Why instantiate prefab not show on canvas this way?

When I try to do it this way

copy = Instantiate(gameObject, canvas.transform);

it works but the scale is all messed up.

When I try to set its parent after instantiation

copy = Instantiate(gameObject);
copy.transform.SetParent(canvas.transform);

the image does now show on canvas at all.

Why doesn’t the second method work?

In the first case (Instantiate(gameObject, canvas.transform)), the object is instantiated in the coordinate space of the canvas, which means its transform is interpreted as local position/rotation/scale inside the canvas transform.

In the second case (Instantiate and then SetParent), the object is instantiated in world space, and then SetParent tries to preserve the transform, converting the world space position to a new position in the canvas’ local space.

There’s an option on SetParent called worldPositionStays that you can turn off, to preserve the local transform of the object when changing parents:

copy.transform.SetParent(canvas.transform, worldPositionStays: false);

This should be mostly equivalent to the first case, though the event order is different and this might lead to different results with some scripts (i.e. Awake, OnEnable run in world space, then the parent changes vs them running directly in the canvas’ local space).

Both methods seem to work fine (just tried now).

You mention the scale is messed up but you don’t mention what the canvas nor the image transform values are. But probably either your scale or position are not the ones you expect them to be. You can set them with Transform.position (or Transform.localPosition) and Transform.localScale.

Also, do not expect the result of both to be the same.
For example, if your image is at (0,0,0) inside the canvas, and the canvas is “Screen Space - Overlay”:

  • With the Instantiate-only method, new object will be at (0,0,0) inside the canvas
  • WIth the transform.SetParent method, new object will be at (-screenWidth/2, -screenHeight, 0) inside the canvas.

Hope it helps.

@belwar @Adrian
Thank you both for your answers. I have figured out why the scale is changing.
Its scaling is based on its parent at birth, which is different than the original parent in my case.
What is confusing me and weird about this thing is that it seems like the first instance a prefab is created determines whether or not it can be displayed on the canvas.

copy = Instantiate(gameObject);

according documentation, this method will spawn my prefab with a null parent.
Even though it is inside the canvas or as a child of the canvas. It does not show.
setting its parent after instantiation does not solve it neither. I tried setting it as last child as well, it’s just not part of the canvas despite being inside the canvas in the hierarchy. I must set the parent at the moment of instantiation.
I have accepted this as part of the unity voodoo magic. Just making sure to set the parent as part of the instantiation call would sidestep this problem.

copy = Instantiate(gameObject);

This code instantiates an object in the hierarchy and out of the canvas.
what you have to do here is assign it a parent inside the canvas. for that, you have to write :

public GameObject objectToBeSpawned;
public Vector3 position;
public Quaternion rotation;
public Transform parentObject;

public void Spawn()
{
    GameObject newObject = Instantiate(objectToBeSpawned, position, rotation, parentObject);
    newObject.GetComponent<RectTransform>().anchoredPosition = position;
}

Here I am spawning the gameobject in a parent (inside canvas) and the position of the spawned object will be just different other than that, scale and rotation are fine.

For correcting the position, I have added the next line, in which I am getting the RectTransform component which isn’t directly accessible you to do a GetComponenet for that,
and then in the RectTransform, I have just changed the anchored position to what I want.