i have multiplayer scene using netcode and relay, i want host and other client player can pick and move object using camera/mouse movement, the object using rigidbody physic. try change object owner and using Client Network Transform still doesnt work
my player interaction script :
using Unity.Netcode;
using UnityEngine;
using Unity.Netcode.Components;
public class PlayerInteraction : NetworkBehaviour
{
[SerializeField] private Camera playerCamera;
[SerializeField] private float interactionDistance = 3f;
[SerializeField] private float holdDistance = 2f;
[SerializeField] private LayerMask interactableLayer;
private NetworkVariable<ulong> heldObjectId = new NetworkVariable<ulong>(0);
private void Update()
{
if (!IsOwner) return;
if (Input.GetMouseButtonDown(0) && playerCamera != null)
{
if (heldObjectId.Value == 0)
{
TryPickupObject();
}
else
{
DropObjectServerRpc();
}
}
if (heldObjectId.Value != 0)
{
MoveHeldObject();
}
}
private void TryPickupObject()
{
Ray ray = playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
if (Physics.Raycast(ray, out RaycastHit hit, interactionDistance, interactableLayer))
{
NetworkObject netObj = hit.collider.GetComponent<NetworkObject>();
if (netObj != null)
{
PickupObjectServerRpc(netObj.NetworkObjectId);
}
}
}
[ServerRpc]
private void PickupObjectServerRpc(ulong objectId)
{
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(objectId, out NetworkObject netObj))
{
netObj.ChangeOwnership(OwnerClientId);
heldObjectId.Value = objectId;
}
}
private void MoveHeldObject()
{
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(heldObjectId.Value, out NetworkObject heldObject))
{
Vector3 targetPosition = playerCamera.transform.position + playerCamera.transform.forward * holdDistance;
MoveObjectServerRpc(targetPosition);
}
}
[ServerRpc(RequireOwnership = false)]
private void MoveObjectServerRpc(Vector3 targetPosition)
{
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(heldObjectId.Value, out NetworkObject heldObject))
{
heldObject.transform.position = targetPosition;
}
}
// [ServerRpc(RequireOwnership = false)]
// private void MoveObjectServerRpc(Vector3 targetPosition)
// {
// if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(heldObjectId.Value, out NetworkObject heldObject))
// {
// NetworkRigidbody networkRigidbody = heldObject.GetComponent<NetworkRigidbody>();
// if (networkRigidbody != null)
// {
// // Use MovePosition for rigidbody-based movement
// networkRigidbody.GetComponent<Rigidbody>().MovePosition(targetPosition);
// }
// }
// }
[ServerRpc]
private void DropObjectServerRpc()
{
heldObjectId.Value = 0;
}
}
object setup
spawn object script
public class ObjectSpawner : MonoBehaviour
{
public GameObject objectPrefab;
// Start is called before the first frame update
void Start()
{
if (NetworkManager.Singleton.IsServer)
{
spawnObject();
}
}
void spawnObject(){
GameObject obj = Instantiate(objectPrefab);
obj.GetComponent<NetworkObject>().SpawnWithOwnership(NetworkManager.Singleton.LocalClientId);
}
}
There is a common misconception that picking up an object requires the player to logically own that object, or that the object in the world and the one that is picked up and held must be the same object.
It’s actually easier to reason about this by assuming that the object in the world is just a network object with graphics, and the object held in a hand is not even networked at all - it’s just a graphics that every client is told to show in that player’s hands. Since that object is parented to the player, it’ll follow the player around naturally, requiring no network synchronization at all.
Only when the player drops that object, either the original pickup world object that is networked is positioned where the player drops it, and the graphics of that object is enabled again (alternatively: simply despawn the world object upon pickup, and spawn a new one upon drop).
Use NetworkObjectReference as parameter. This send the id around but you can simply do TryGet on the receiving side to get that object.
2 Likes
so i need like one script manager in server that will receive command from client to hide/despawn certain object and move to new position?
If you are using a distributed network topology, then you can:
- Make the NetworkObject have transferrable permissions.
- Have the client take ownership of the object when a client/player is picking it up
- You can then immediately parent it to a fixed joint (see the social hub example)
- Upon attaching it to a fixed point, you should also lock the object’s permissions so other clients/players cannot pick it up.
If you are using a client-server network topology, then it just takes a bit of pre-planning on how you layout the object to be picked up (like @CodeSmile points out).
The tendency here is to have everything at the root and/or trying to follow a similar pattern as you would in a distributed authority network topology.
Upon picking up you might think of parenting the ObjectToPickup:
- PlayerObject (GameObject)(NetworkObject)(NetworkBehaviours)
- (possibly more nested GameObjects with NetworkBehaviours)
- PickUp Node (GameObject)
- ObjectToPickup (GameObject)(NetworkObject)(NetworkBehaviours)
However, if you approach your ObjectToPickup in a slightly different way:
Then as long as the ObjectToPickup is spawned, you can use normal transform parenting so you end up with something like this:
Where:
- ParentingTargetNetworkBehaviour contains a reference to the PickUp Node GameObject.
- ParentingNetworkBehaviour contains a NetworkVariable property.
Then you would have the pickup logic look like this:
- Player performs pickup action on ObjectToPickup.
- This action invokes a method (PickupObject) on the ParentingNetworkBehaviour passing in the ParentingTargetNetworkBehaviour component.
- ParentingNetworkBehaviour.PickupObject then:
- Gets the PickUp Node GameObject’s transform component from the ParentingTargetNetworkBehaviour.
- Parents the ObjectVisualsAndLogic’s GameObject’s transform under that.
- Sets the NetworkVariable property to the ParentingTargetNetworkBehaviour.
- At this point, on the player picking up the object side, the “object” is parented and will automatically move with the PickUp Node’s GameObject’s transform.
- Other clients will be notified of the change to the NetworkVariable property (you would have non-owners/authority instances subscribe to OnValueChanged) and would automatically perform the same parenting steps of ObjectVisualsAndLogic.
When you wanted to drop the object, you would reverse this process a bit by:
- The player drops the object which would Reparent the ObjectVisualsAndLogic under the original ObjectToPickup:
- You would first want to teleport the ObjectToPickup to the world space position and rotation of the ObjectVisualsAndLogic.
- Then parent the ObjectVisualsAndLogic under the ObjectToPickup.
- Assuring the local space is set back to its normal offset.
- Set the ParentingNetworkBehaviour’s NetworkVariable property to NULL.
- When all non-owner/authority instances see that the NetworkVariable proprty is null, they would perform the same steps.
This effecitvely makes the ObjectToPickup a “container” of the actual object (ObjectVisualsAndLogic) that can be picked up and can provide visual synchronization for that object (i.e. if a player could bump into the object or kick the object etc)] when it is not picked up… but upon being picked up the ObjectToPickup only serves as a way for messages to be routed to the ObjectVisualsAndLogic but the player holding the ObjectVisualsAndLogic handles the visual synchronization portion by animation and/or motion with no cost to bandwidth in terms of synchronizing it.
Let me know if this makes sense and helps you with your project?