Issues with correctly spawning player objects (Paws) in specific hierarchy points in a multiplayer game

Hi everyone,

I’m developing a multiplayer game using Unity Netcode. Each player has a set of “Paws” that need to spawn as children of specific hierarchy points, groundReferenceRight and groundReferenceLeft. However, I’m facing issues where the spawned objects do not retain their correct positions.

Issue Details

Incorrect Paw Positions: After spawning, the Paws are positioned incorrectly. For instance:

  • Original prefab values:
    • Pos X: -0.2900391
    • Pos Y: 1
  • Spawned under groundReferenceRight:
    • Pos X: 1687.844
    • Pos Y: -832.1766
  • Spawned under groundReferenceLeft:
    • Pos X: -759.4812
    • Pos Y: -833.1187The scale, width, and height remain correct:
  • Scale X, Y, Z: 1
  • Width: 217
  • Height: 217.7

Scene Hierarchy:

  • Components involved:
  • groundReferenceRight and groundReferenceLeft have:
    • RectTransform
    • NetworkObject
  • Paws (prefabs) have:
    • RectTransform
    • NetworkObject
    • A behavior script (PawBehavior).
  • Additional Problem: Even though the groundReferenceRight and groundReferenceLeft exist, sometimes the parent or initial RectTransform properties are not respected. I’ve double-checked the hierarchy and prefab setup.
public void InstantiatePawsForPlayer(string playerName, List<PawInstance> pawInstances)
{
    Debug.Log($"Instantiating Paws for player {playerName}.");

    // Determines the groundReference based on the player's name
    Transform groundReference = GetGroundReferenceForPlayer(playerName);

    if (groundReference == null)
    {
        Debug.LogError($"GroundReference for player {playerName} is null. Ignoring instance.");
        return;
    }

    Debug.Log($"GroundReference for {playerName}: {groundReference.name}");

    foreach (var pawInstance in pawInstances)
    {
        // Loads the Paw prefab
        var pawPrefab = Resources.Load<GameObject>($"Prefabs/{pawInstance.PawName}");
        if (pawPrefab == null)
        {
            Debug.LogError($"Prefab for {pawInstance.PawName} not found.");
            continue;
        }

        // Instantiates the Paw
        var pawObject = Instantiate(pawPrefab);

        // Configures the RectTransform if present
        var instanceRectTransform = pawObject.GetComponent<RectTransform>();
        if (instanceRectTransform != null)
        {
            // SetParent without changing local position
            instanceRectTransform.SetParent(groundReference, false);

            // Restores prefab properties
            instanceRectTransform.anchoredPosition = Vector2.zero; // Centers it
            instanceRectTransform.localScale = Vector3.one; // Keeps default scale
            instanceRectTransform.localRotation = Quaternion.identity;
        }
        else
        {
            // For objects without RectTransform
            pawObject.transform.SetParent(groundReference, false);
            pawObject.transform.localPosition = Vector3.zero;
            pawObject.transform.localRotation = Quaternion.identity;
            pawObject.transform.localScale = Vector3.one;
        }

        // Configures the NetworkObject for the Paw
        var networkObject = pawObject.GetComponent<NetworkObject>();
        if (networkObject != null)
        {
            // Spawns the object in the network
            networkObject.Spawn();

            // Reinforces parent in case NetworkManager alters the hierarchy
            if (pawObject.transform.parent != groundReference)
            {
                pawObject.transform.SetParent(groundReference, false);

                if (instanceRectTransform != null)
                {
                    instanceRectTransform.anchoredPosition = Vector2.zero;
                    instanceRectTransform.localScale = Vector3.one;
                }
                else
                {
                    pawObject.transform.localPosition = Vector3.zero;
                    pawObject.transform.localScale = Vector3.one;
                }
            }
        }
        else
        {
            Debug.LogError($"Paw {pawInstance.PawName} does not have a NetworkObject component!");
            Destroy(pawObject);
            continue;
        }

        Debug.Log($"Paw {pawInstance.PawName} configured as a child of {groundReference.name}.");

        // Initializes the behavior of the Paw, if present
        var pawBehavior = pawObject.GetComponent<PawBehavior>();
        if (pawBehavior != null)
        {
            pawBehavior.Initialize(pawInstance);
        }
    }
}

        private Transform GetGroundReferenceForPlayer(string playerName)
        {
            // Associa jogadores a groundReferences, se ainda não mapeado
            if (!groundReferences.ContainsKey(playerName))
            {
                Transform groundReference = groundReferences.Count == 0 ? groundReferenceLeft : groundReferenceRight;
                groundReferences[playerName] = groundReference;
            }

            return groundReferences[playerName];
        }

Avoid offsetting prefab root objects altogether! This is even a requirement for asset publishers and for good reason. Mainly it leads to surprises when you instantiate a prefab at x,y,z but then you notice it’s actually positioned slightly offset at x1,y1,z1.

If you need any content offset from the prefab’s origin, create a child object and offset that. You will find that more often than not, separate child objects need different offsets.

Given the mention of RectTransform it sounds like you’re trying to manually position UGUI elements. Don’t do this either! UGUI (RectTransform) is controlled by the canvas layout system and will not adhere to regular game object positioning behaviour. You will need to use the GUI system to adjust the positions, you cannot directly set RectTransform values and assume they will match what you expect.

Basically whereever you have a RectTransform, consider it forbidden to modify it! It’ll make your life a lot easier. Specifically when it’s within a parent-child hierarchy as in your example.

Since your Paws also have a RectTransform, it gets worse because it means you have networked UGUI prefabs. Avoid doing this too! The GUI should be a secondary citizen to the game logic and should not be controlled or affected directly by networked objects.

If your paws are “cards” in a sense, then you should have a networked PawManager that provides the necessary networked state for each card (on deck, on hand, on table) and a state change starts the card play animation locally (non-networked!). The GUI simply responds to the necessary local-only events from PawManager (eg “card x played”, “card y drawn from deck”) to update its visual state and play the animations.

1 Like

You are absolutely right; since my “game screen” was a UGUI, I was focused on building the game around it. But I spent an entire day trying to spawn the “Paws” as UGUI objects within the Canvas, and I could never achieve the expected behavior.

Then, I switched the scene to a much simpler 2D setup with far fewer objects, and guess what? It took me less than 30 minutes to solve the problem!

Thank you again for your help; it was crucial in clarifying my thoughts and leading me to the solution.

1 Like