How to change ownership on interactables in netcode

Hi
I have a netcode project with an XROrigin and a NetworkPlayer prefab. One player starts the application and automatically opens the server as a host and the second one automatically joins after opening the application. Now I want just a ball that can be picked up and held by both, this ball should be synchronized. The ball gets spawned by pressing SPACE as the host. The host can also take the ball and the 2nd client sees this synchronized. But if the client wants to take the ball it is 1. not snyced with the host (he just doesnt see it) and 2. the ball gets teleported back to the original position after deselecting it. I tried to change the ownership. I copied a method from a tutorial and bound this method to the controllers of the XROrigin when Selected on a XR Direct Interactor. This method is within the NetworkPlayer Script that lies on the NetworkPlayer prefab:

public void OnSelectGrabbable(SelectEnterEventArgs eventArgs)
{
   Debug.Log("Trying to pick up for this OwnerClientId " + OwnerClientId);
   Debug.Log("NetworkId " + NetworkObjectId);
   Debug.Log("IsClient: " + IsClient + " IsOwner: " + IsOwner);

   if (IsClient && IsOwner)
   {
   NetworkObject networkObjectSelected = eventArgs.interactableObject.transform.GetComponent<NetworkObject>();
   Debug.Log("This is the selected object: " + networkObjectSelected);
   if (networkObjectSelected != null)
   {
       RequestGrabbableOwnerShipServerRpc(OwnerClientId, networkObjectSelected);
   }
   }
}

[ServerRpc]
public void RequestGrabbableOwnerShipServerRpc(ulong newOwnerClientId, NetworkObjectReference networkObjectReference)
{
   if (networkObjectReference.TryGet(out NetworkObject networkObject))
   {
       if (networkObject.OwnerClientId == newOwnerClientId)
       {
           return;
       }
       Debug.Log("This is the selected object: " + networkObject);
       Debug.Log("And this the client ID: " + newOwnerClientId);
       networkObject.ChangeOwnership(newOwnerClientId);
   }
   else
   {
       Debug.Log("Unable Changeownership for: " + newOwnerClientId);
   }
}

There are some weird behaviours now. A normal session on a client looks like this:
Host is hosting

  • Spawning this OwnerClientId 0
  • NetworkObjectId 1, IsClient: True, IsOwner: False

Client is joining:

  • Spawning this OwnerClientId 1
  • NetworkObjectId 3, IsClient: True, IsOwner: True

Then the ball is spawned with NetworkObjectId 4 and OwnerClientId 0 (spawned by host) If I now want to take the ball as a client it logs these values: OwnerClientId 0, NetworkObjectId 0 IsClient: False, IsOwner: False And I dont understand that, I didnt even found an object in the hierarchy with the NetworkObjectId 0.
I used a tutorial from valem to setup the project and unity relay. And the interactable code is from dilmer valecilos

Ping … any news on this?

Hi,
I really appreciate your fast support. In particular - since this is a widely discussed challange I am facing e.g.:

I am using:

  • 2022.3.16f1
  • Netcode for GameObjects 1.7.1
  • Relay 1.0.5
  • XR Interaction Toolkit 2.5.2
  • 2 Meta Quest 3

Following the SETUP mentioned here:

GOAL: Each player is able to spawn objects that can be grabbed (XR Grabble) and moved arround by both players.

PROBLEM: If client player grabs, moves or rotates the object, it snaps back to the old position after exiting select. I’m facing the problem even if i requestchange ownership via serverRPC. Reuqesting the ownership slightly changes the behavior - meaning that very small changes of rotations seam to be transmited to the sever somehow.

ASSUMPTION: The missmbehavior ist somehow related to the fact that the grabbed object is reparented for interaction - but I am not sure.

In the attachement you will find a screenshot of the object. In addition I attached the changeOwnership script.

Does it make sense?
Dd you need any additional information? … upload screen recordings?
Is there any sample showing how to have a xr grabbable object being moved arround by two players with netcode for GameObjects and Relay? I means this seamas to me a very obvious XR use case.

Your help is very appreciated! Hope to hear from you soon!

BR,
Andreas

9608420–1362947–NetworkGrabbableObject.cs (1.69 KB)

1 Like

I’m facing the same problem.

This is happening on client:

Is there someone who can help me with a simple example of how to do grabbing over netcode using xr grab interactable?

Hi guys, I’m facing the same problem, but I found some light that may help. I have not try it yet, so you may try and tell if it worked or not. Anyway, let’s go:
He’ll explain in the video, but basically what he did was that the server moves the cube to follow the client’s hand.

:thinking: Hello everyone. I am new to unity and is my first time writing something here. I managed somehow to be abble to grab objects and all clients and host are able to interact with the object or see them. So the only difference about you the other one that commented before my I see you people are you trying to achieve that on VR. Before starting using unity I did multiplayer on GODOT VR and it was a lot easyer. But this is not the point. So I will share my script for grabing the object but the only thing is that the script is not for VR. But I think is easyer to use this script and just add the VR button.

Also for my script instead of parenting the object to the hand. I made a script that follow the hand. And is really smooth for the player. If I don’t answher because I am not sure how I can check my notification you can send a message to my YT chanell NightKing96 :smiley: https://www.youtube.com/@nightking96

I will also somewhere on the near future try to make it on VR with my oculus quest 2 and if I have some result I will try to share the result on VR too. But for now I need to finish my game LOCKDOWN: Exchange Protocol

using UnityEngine;
using Unity.Netcode;

public class SimpleGrabSystem : NetworkBehaviour
{
    private PlayerRole currentPlayerRole;
    [SerializeField] private Camera characterCamera;
    [SerializeField] private Transform handSlot; // Reference to the hand slot
    private PickableItem pickedItem;

    public override void OnNetworkSpawn()
    {
        if (!IsOwner) return;

        // Determine the player's role based on the tag
        currentPlayerRole = GetPlayerRoleFromTag(gameObject.tag);
    }

    private void Update()
    {
        if (!IsOwner) return;

        if (Input.GetButtonDown("Fire1"))
        {
            if (pickedItem)
            {
                DropItem(pickedItem);
            }
            else
            {
                var ray = characterCamera.ViewportPointToRay(Vector3.one * 0.5f);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, 1.5f))
                {
                    var pickable = hit.transform.GetComponent<PickableItem>();
                    if (pickable && CanInteractWith(pickable))
                    {
                        PickItem(pickable);
                    }
                }
            }
        }

        if (Input.GetButtonDown("Fire2") && pickedItem)
        {
            ThrowItem(pickedItem);
        }
    }

    private void LateUpdate()
    {
        if (pickedItem && IsOwner)
        {
            UpdateHeldItemPosition();
        }
    }

    private void PickItem(PickableItem item)
    {
        if (!IsOwner)
        {
            // Request authority if not owned
            item.RequestChangeOwnership(NetworkManager.Singleton.LocalClientId);
        }

        pickedItem = item;
        item.Rb.isKinematic = true;
        item.Rb.velocity = Vector3.zero;
        item.Rb.angularVelocity = Vector3.zero;

        // Notify the server to handle reparenting and position updates
        PickItemServerRpc(item.GetComponent<NetworkObject>().NetworkObjectId, handSlot.position, handSlot.rotation);
    }

    [ServerRpc]
    private void PickItemServerRpc(ulong itemId, Vector3 handSlotPosition, Quaternion handSlotRotation)
    {
        if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(itemId, out var netObj))
        {
            var item = netObj.GetComponent<PickableItem>();
            if (item != null)
            {
                item.Rb.isKinematic = true;
                item.Rb.detectCollisions = false;

                // Handle reparenting on the server
                item.transform.SetParent(handSlot);
                item.transform.localPosition = Vector3.zero;
                item.transform.localRotation = Quaternion.identity;

                // Update item position and rotation on all clients
                UpdateItemPositionServerRpc(itemId, handSlot.position, handSlot.rotation);
            }
            else
            {
                Debug.LogWarning($"Item with ID {itemId} not found.");
            }
        }
        else
        {
            Debug.LogWarning($"NetworkObject with ID {itemId} not found.");
        }
    }

    [ServerRpc]
    private void UpdateItemPositionServerRpc(ulong itemId, Vector3 position, Quaternion rotation)
    {
        if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(itemId, out var netObj))
        {
            var item = netObj.GetComponent<PickableItem>();
            if (item != null)
            {
                // Ensure the item's position and rotation are updated on all clients
                item.transform.position = position;
                item.transform.rotation = rotation;
            }
            else
            {
                Debug.LogWarning($"Item with ID {itemId} not found.");
            }
        }
        else
        {
            Debug.LogWarning($"NetworkObject with ID {itemId} not found.");
        }
    }

    private void DropItem(PickableItem item)
    {
        pickedItem = null;

        // Notify the server to handle dropping the item
        DropItemServerRpc(item.GetComponent<NetworkObject>().NetworkObjectId);
    }

    [ServerRpc]
    private void DropItemServerRpc(ulong itemId)
    {
        if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(itemId, out var netObj))
        {
            var item = netObj.GetComponent<PickableItem>();
            if (item != null)
            {
                item.transform.SetParent(null); // Unparent the item
                item.Rb.isKinematic = false;
                item.Rb.detectCollisions = true;

                // Optionally, apply force to the item
                item.Rb.AddForce(characterCamera.transform.forward * 2, ForceMode.VelocityChange);
            }
            else
            {
                Debug.LogWarning($"Item with ID {itemId} not found.");
            }
        }
        else
        {
            Debug.LogWarning($"NetworkObject with ID {itemId} not found.");
        }
    }

    private void ThrowItem(PickableItem item)
    {
        pickedItem = null;

        // Notify the server to handle throwing the item
        ThrowItemServerRpc(item.GetComponent<NetworkObject>().NetworkObjectId);
    }

    [ServerRpc]
    private void ThrowItemServerRpc(ulong itemId)
    {
        if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(itemId, out var netObj))
        {
            var item = netObj.GetComponent<PickableItem>();
            if (item != null)
            {
                item.transform.SetParent(null); // Unparent the item
                item.Rb.isKinematic = false;
                item.Rb.detectCollisions = true;

                // Apply force to the item
                item.Rb.AddForce(characterCamera.transform.forward * 10, ForceMode.VelocityChange);
            }
            else
            {
                Debug.LogWarning($"Item with ID {itemId} not found.");
            }
        }
        else
        {
            Debug.LogWarning($"NetworkObject with ID {itemId} not found.");
        }
    }

    private void UpdateHeldItemPosition()
    {
        if (pickedItem)
        {
            // Smoothly update position and rotation to avoid jitter
            pickedItem.transform.position = handSlot.position;
            pickedItem.transform.rotation = handSlot.rotation;

            // Update the server with the new position and rotation
            UpdateItemPositionServerRpc(pickedItem.GetComponent<NetworkObject>().NetworkObjectId, handSlot.position, handSlot.rotation);
        }
    }

    private bool CanInteractWith(PickableItem item)
    {
        // Compare player's role with the item's role
        return currentPlayerRole == item.ItemRole;
    }

    private PlayerRole GetPlayerRoleFromTag(string tag)
    {
        switch (tag)
        {
            case "PatrolObject":
                return PlayerRole.Patrol;
            case "CleanerObject":
                return PlayerRole.Cleaner;
            default:
                Debug.LogError($"Unexpected tag: {tag}. Please ensure that the player objects have the correct tag.");
                throw new System.ArgumentException($"Unexpected tag: {tag}");
        }
    }
}