Azure Spatial Anchors and PUN

I am trying to create a simple game that allows users to manipulate 3D , holographic cubes collaboratively on two HoloLens 2s. I have been able to get PUN working, and can distribute the cubes in relative (local) space. To create shared, absolute space, I am using Azure Spatial Anchors. The MasterClient can create an anchor, and can public the unique identifier to the regular client using RPCs. All that works. Problem is, that only the MasterClient actually sees the holograms in their correct, absolute locations. Below I have attached the PlayerManager script (attached to the player prefab) that calls for the anchor creation and sends the RPC. Also, I have attached the script that uses the anchored "AnchorParent" object as a parent. The PhotonViews attached to the cubes are set to transfer local space.

public class PlayerManager : MonoBehaviourPunCallbacks, IPunObservable

{


  [Tooltip("The local player instance. Use this to know if the local player is represented in the Scene")]

  public static GameObject LocalPlayerInstance;

  public GameObject clientPrefabImages;

  public GameObject clientPrefabDepth;

  string identifier = null;


  // Start is called before the first frame update

  #region MonoBehaviour CallBacks


  /// <summary>

  /// MonoBehaviour method called on GameObject by Unity during early initialization phase.

  /// </summary>

  void Awake()

  {

    DontDestroyOnLoad(this.gameObject);

    // #Important

    // used in GameManager.cs: we keep track of the localPlayer instance to prevent instanciation when levels are synchronized

    if (photonView.IsMine)

    {

      LocalPlayerInstance = gameObject;

    }



  }


  async void Start()

  {


    if (PhotonNetwork.IsMasterClient && photonView.IsMine)

    {

      identifier = await GameObject.Find("AzureSpatialAnchors").GetComponent<AzureSpatialAnchorsScript>().createAnchor();

      while (identifier == null)

      {

        identifier = await GameObject.Find("AzureSpatialAnchors").GetComponent<AzureSpatialAnchorsScript>().createAnchor();

      }

      photonView.RPC("SendIdentifier", RpcTarget.OthersBuffered, identifier);


      Debug.Log("Anchor Created");

    }

  }


  #endregion


  // Update is called once per frame

  void Update()

  {

    if (photonView.IsMine)

    {

      this.gameObject.transform.position = Camera.main.transform.position;


    }


  }


  #region IPunObservable implementation


  public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)

  {


  }


  #endregion

}



using UnityEngine;

using Photon.Pun;

using Photon.Realtime;


public class Launcher : MonoBehaviourPunCallbacks

{

  #region Private Serializable Fields

  /// <summary>

  /// The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created.

  /// </summary>

  [Tooltip("The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created")]

  [SerializeField]

  private byte maxPlayersPerRoom = 4;

  /// <summary>

  /// Keep track of the current process. Since connection is asynchronous and is based on several callbacks from Photon,

  /// we need to keep track of this to properly adjust the behavior when we receive call back by Photon.

  /// Typically this is used for the OnConnectedToMaster() callback.

  /// </summary>

  bool isConnecting;



  #endregion



  #region Private Fields


  /// <summary>

  /// This client's version number. Users are separated from each other by gameVersion (which allows you to make breaking changes).

  /// </summary>

  string gameVersion = "1";



    #endregion



    #region MonoBehaviour CallBacks



    /// <summary>

    /// MonoBehaviour method called on GameObject by Unity during early initialization phase.

    /// </summary>

    void Awake()

    {

      // #Critical

      // this makes sure we can use PhotonNetwork.LoadLevel() on the master client and all clients in the same room sync their level automatically

      PhotonNetwork.AutomaticallySyncScene = true;

    //DontDestroyOnLoad(this.gameObject);

  }



    /// <summary>

    /// MonoBehaviour method called on GameObject by Unity during initialization phase.

    /// </summary>

    void Start()

    {

    Connect();


    }


  void Update()

  {


  }



    #endregion



    #region Public Methods



    /// <summary>

    /// Start the connection process.

    /// - If already connected, we attempt joining a random room

    /// - if not yet connected, Connect this application instance to Photon Cloud Network

    /// </summary>

    public void Connect()

    {

      // we check if we are connected or not, we join if we are , else we initiate the connection to the server.

      if (PhotonNetwork.IsConnected)

      {

      // #Critical we need at this point to attempt joining a Random Room. If it fails, we'll get notified in OnJoinRandomFailed() and we'll create one.

      //PhotonNetwork.JoinRandomRoom();

      isConnecting = PhotonNetwork.ConnectUsingSettings();

    }

      else

      {

      // #Critical, we must first and foremost connect to Photon Online Server.

      isConnecting = PhotonNetwork.ConnectUsingSettings();

      PhotonNetwork.GameVersion = gameVersion;

      }

    }



  #endregion


  #region MonoBehaviourPunCallbacks Callbacks



  public override void OnConnectedToMaster()

  {

    Debug.Log("PUN Basics Tutorial/Launcher: OnConnectedToMaster() was called by PUN");

    if (isConnecting)

    {

      // #Critical: The first we try to do is to join a potential existing room. If there is, good, else, we'll be called back with OnJoinRandomFailed()

      //PhotonNetwork.JoinRandomRoom();

      PhotonNetwork.JoinOrCreateRoom("HardCodedRoom", null, null);

      isConnecting = false;

    }

  }



  public override void OnDisconnected(DisconnectCause cause)

  {

    Debug.LogWarningFormat("PUN Basics Tutorial/Launcher: OnDisconnected() was called by PUN with reason {0}", cause);

    isConnecting = false;

  }


  public override void OnJoinRandomFailed(short returnCode, string message)

  {

    Debug.Log("PUN Basics Tutorial/Launcher:OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom");


    // #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.

    PhotonNetwork.CreateRoom(null, new RoomOptions { MaxPlayers = maxPlayersPerRoom });

  }


  public override void OnJoinedRoom()

  {

    Debug.Log("PUN Basics Tutorial/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room.");

    // #Critical

    // Load the Room Level.

    var player = PhotonNetwork.Instantiate("Player", new Vector3(0f, 0f, 0f), Quaternion.identity, 0);

    player.transform.SetParent(GameObject.Find("AnchorParent").transform, false);


  }


  public void spawnCube()

  {

    GameObject newCube = PhotonNetwork.Instantiate("Cube", new Vector3(0f, 0f, .5f), Quaternion.identity);

    if (PhotonNetwork.IsMasterClient)

    {

      newCube.GetComponent<MeshRenderer>().material.color = Color.green;

      newCube.transform.SetParent(GameObject.Find("AnchorParent").transform, false);

    }

    else

    {

      newCube.GetComponent<MeshRenderer>().material.color = Color.red;

      newCube.transform.SetParent(GameObject.Find("AnchorParent").transform, false);

    }

  }


  #endregion

Answers

  • Its fixed, but I don't know why. I added the SetParent call to the Start() function of the player and cube prefab. Works now. Can anyone elaborate on why?

  • We don't read a lot of code and much less, if it's not formatted. When you input code, select it and use the formatting options left of the input box (looks like a paragraph icon)...

    When you PN.Instantiate an object, you can access it right away (it's returned). But only "you" can do this. Others instantiate the same object due to messages arriving from the network (it's not this code that instantiates the obj).