03-07-2025 03:33 AM - edited 03-07-2025 05:13 AM
In the colocation multiplayer ar app I am building for Meta Quest 3, I am using Meta XR SDK and Photon Fusion for networking. I have set up a prefab for instantiation across the network. the prefab itself contains multiple children which carry NetworkObject components thmeselves, because these objects are replicas of the NetworkedGrabbableObject building block. Their hierarchy is as follows.
the Pillar_01_Module are the visuals and therefore not important. The GrabInteraction is basically the child that an object gets after we add Grab Interaction, via right click -> Interaction sdk -> add grab interaction. the RotateInteraction child is the same except for the additional component of OneGrabRotateTransformer, which is passed into the One Grab Transformer optinal parameter of the object's Grabbable component. the Inspectors of the BaseGrabbable, GrabInteraction and RotateInteraction with that order are:
The logic i want to achieve is: players can move the BaseGrabbable, being able to take it from each other's hand (works just fine). The BaseGrabbable's trigger BoxCollider collides with the Base's trigger BoxCollider. OnTriggerEnter is called (i have checked that it gets called on all player's successfully, since the two colliders collide in all players, therefore, i am sure no rpc is needed for this logic). OnTriggerEnter snaps the BaseGrabbable object to the position of the Base object, disables itsGrabInteraction child and enables its RotateInteraction child. At this point, all the users should be able to simply rotate the BaseGrabbable, while it remains in a fixed position, while all the other users should be able to see it rotating. However, only the user that was controlling the BaseGrabbable when OnTiggerEnter was called is able to rotate it (the rotation is visible to the other ofcourse but the other cannot interact with it).
I have checked whether the GrabInteraction applies changes to the BaseGrabbable BoxCollider or RigidBody, making it non interactible, until the controlling user leaves it be. I was wondering if these changes persist since the user never releases it, but the GrabInteraction is simply deactivated. But if ound nothing there and I don't think that was the case. I then checked whether i should manually ReleaseStateAuthority of the network object when disabling the GrabInteraction and enaling the RotateInteraction, in order for other players to be able to interact with it, but TransferStateAuthority components allow user's to take state authority from others so that was not the case either.
Here is my PillarPart script, which is carried by the Base object (not the BaseGrabbable)
public enum PillarPartState
{
None,
Positioned,
Rotated,
Smoothed
}
public class PillarPartLogic : MonoBehaviour
{
private GameObject nextPart;
private GameObject currentGrabbable;
public PillarPartState partState;
private BoxCollider triggerCollider;
private int smoothCounter;
private void Awake()
{
triggerCollider = GetComponent<BoxCollider>();
if (this.name == "Base")
{
nextPart = transform.parent.Find("Part1").gameObject;
}
else if (this.name == "Part1")
{
nextPart = transform.parent.Find("Part2").gameObject;
}
else if (this.name == "Part2")
{
nextPart = transform.parent.Find("Part3").gameObject;
}
else if (this.name == "Part3")
{
nextPart = transform.parent.Find("Part4").gameObject;
}
else if (this.name == "Part4")
{
nextPart = transform.parent.Find("Part5").gameObject;
}
else if (this.name == "Part5")
{
nextPart = transform.parent.Find("Part6").gameObject;
}
else if (this.name == "Part6")
{
nextPart = null;
}
currentGrabbable = transform.root.Find(this.name + "Grabbable").gameObject;
// TODO: highlight currentGrabbable
}
private void Start()
{
partState = PillarPartState.None;
smoothCounter = 0;
}
private void OnTriggerEnter(Collider other)
{
if (partState == PillarPartState.None && other.name == this.name + "Grabbable")
{
Debug.Log($"{this.name} collided with {other.name}");
GameObject grabbableGrabInterraction = other.transform.Find("GrabInteraction").gameObject;
GameObject grabbableRotateInterraction = other.transform.Find("RotateInteraction").gameObject;
// disable grab interraction. object is now non interractible
grabbableGrabInterraction.SetActive(false);
// snap at position and rotation
other.transform.position = transform.position + new Vector3(0.0f, 0.04f, 0.0f);
Vector3 eulerAngles = transform.eulerAngles;
other.transform.eulerAngles = new Vector3(eulerAngles.x, other.transform.eulerAngles.y, eulerAngles.z);
// enable rotate interraction. players can now only rotate the object around the y axis
grabbableRotateInterraction.SetActive(true);
partState = PillarPartState.Positioned;
}
else if (partState == PillarPartState.Rotated &&
(other.name == "LeftHandAndControllerTrigger" || other.name == "RightHandAndControllerTrigger"))
{
smoothCounter++;
if (smoothCounter == 3)
{
// activate smoothed visuals, deactivate rough visuals
transform.Find("SmoothVisuals").gameObject.SetActive(true);
transform.Find("RoughVisuals").gameObject.SetActive(false);
partState = PillarPartState.Smoothed;
// deactivate trigger collider
triggerCollider.enabled = false;
// increment completed parts in parent PillarBuilding component
transform.root.GetComponent<PillarBuilding>().partsCompleted++;
}
}
}
private void Update()
{
if (partState != PillarPartState.Positioned || !currentGrabbable)
return;
if (Mathf.Abs(currentGrabbable.transform.eulerAngles.y - transform.eulerAngles.y) < 5)
{
GameObject grabbableRotateInterraction = currentGrabbable.transform.Find("RotateInteraction").gameObject;
// disable rotate inerraction. object is now non interractible
grabbableRotateInterraction.SetActive(false);
// snap at position
currentGrabbable.transform.position = transform.position;
// activate rough visuals
transform.Find("RoughVisuals").gameObject.SetActive(true);
// enable the next part for collision detection
if (nextPart != null)
nextPart.SetActive(true);
// delete controlled object
Destroy(currentGrabbable);
currentGrabbable = null;
// if this was the last part to be placed, enable smoothing logic
if (this.name == "Part6")
{
transform.root.GetComponent<PillarBuilding>().EnablePillarSmoothingLogic();
}
}
}
}
Can anyone provide some insight?