Forum Discussion

🚨 This forum is archived and read-only. To submit a forum post, please visit our new Developer Forum. 🚨
fredoculuss's avatar
fredoculuss
Honored Guest
4 years ago

Meta avatar 2 multiplayer

Hello everyone. 

I'm trying to use the new meta avatars in multiplayer but I struggle with implementation. I tried with Photon Pun 2 and the sample scene "network loopback" but I'm unable to achieve proper networking. 

Has anyone achieved multiplayer with the new avatar sdk or know a documentation or tutorial I can use to make it work?

Thanks a lot.

42 Replies

Replies have been turned off for this discussion
  • I have the same problem.
    Here is my code

    using Oculus.Avatar2;
    using Photon.Pun;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unity.Collections;
    using UnityEngine;
    using static Oculus.Avatar2.OvrAvatarEntity;
    
    public class MirrorTest : MonoBehaviourPun, IPunObservable
    {
        [SerializeField] private OvrAvatarEntity avatarEntity = null;
        private readonly float[] _streamLodSnapshotElapsedTime = new float[OvrAvatarEntity.StreamLODCount];
        private static readonly float[] StreamLodSnapshotIntervalSeconds = new float[OvrAvatarEntity.StreamLODCount] { 1f / 72, 2f / 72, 3f / 72, 4f / 72 };
        
        private const int MAX_PACKETS_PER_FRAME = 3;
        
    
        private void Awake()
        {
            PhotonNetwork.AddCallbackTarget(this);
    
            avatarEntity.SetNewActiveView(photonView.IsMine?CAPI.ovrAvatar2EntityViewFlags.FirstPerson
                                                            : CAPI.ovrAvatar2EntityViewFlags.ThirdPerson);
            DataHolder test = FindObjectOfType<DataHolder >();
            avatarEntity.SetIsLocal(photonView.IsMine);
    
            avatarEntity.SetBodyTracking(photonView.IsMine? test.ovrAvatarManager : null);
            avatarEntity.SetLipSync(photonView.IsMine ? test.lipSyncBehavior : null);
        }
    
        private void OnDisable()
        {
            PhotonNetwork.RemoveCallbackTarget(this);
        }
    
        List<byte[]> GetData()
        {
            //if (!_localAvatar.HasJoints) { return; }
            List<byte[]> fullData = new List<byte[]>();
            for (int streamLod = (int)StreamLOD.High; streamLod <= (int)StreamLOD.Low; ++streamLod)
            {
                _streamLodSnapshotElapsedTime[streamLod] += Time.unscaledDeltaTime;
    
                fullData.Add(avatarEntity.RecordStreamData((StreamLOD)streamLod));
            }
    
            return fullData;
        }
    
        private void ApplyData(List<byte[]> data)
        {
            for (int i = 0; i < data.Count; i++)
            {
                avatarEntity.ApplyStreamData(data[i]);
            }
        }
    
        public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
        {
            if (stream.IsReading)
            {
                if (avatarEntity.HasJoints)
                {
                    stream.SendNext(GetData());
                }
            }
            else if (stream.IsWriting)
            {
                ApplyData((List<byte[]>) stream.ReceiveNext());
            }
        }
    }

    I test it locally and it works fine, can someone help me?

    • olaysia's avatar
      olaysia
      Expert Protege

      I got it to work! Here's the key to the problem:

      1. ApplyStreamData() will only work if the avatar has been configured as IsLocal = false and if the Creation Info has been set to preset_remote.
      2. However you've got to apply these configurations BEFORE CreateEntity() is called which happens in Awake() otherwise it ain't gonna work.

      My problem was that I had to configure these programatically and not in the editor because I had to instantiate a generic prefab using Photon. As anyone would with Photon. Here's how the relevant parts of my code look now:

      protected override void Awake()
          {
              ConfigureAvatarEntity();
              base.Awake();
          }
      
      
      void ConfigureAvatarEntity()
          {
              m_photonView = GetComponent<PhotonView>();
              if (m_photonView.IsMine)
              {
                  SetIsLocal(true);
                  _creationInfo.features = Oculus.Avatar2.CAPI.ovrAvatar2EntityFeatures.Preset_Default;
                  SampleInputManager sampleInputManager = OvrAvatarManager.Instance.gameObject.GetComponent<SampleInputManager>();
                  SetBodyTracking(sampleInputManager);
                  gameObject.name = "MyAvatar";
              }
              else
              {
                  SetIsLocal(false);
                  _creationInfo.features = Oculus.Avatar2.CAPI.ovrAvatar2EntityFeatures.Preset_Remote;
                  gameObject.name = "OtherAvatar";
              }
          }
      • j0schm03's avatar
        j0schm03
        Explorer

        You just saved me! I've spent so many hours scratching my head trying to get this to work even with a local avatar. I finally go it working but I have a bool on the AvatarEntity script right now instead of looking at whether the photonview IsMine bool. 

         

        Where did you place your script you referenced? I was going to add this to my AvatarEntity script and add the configure function right before create Entity there. BUT I cannot add using Photon.PUN in this script because it cannot find the namespace due to file structure. I just cannot figure out how to fix this issue.

         

        So where did you add this code to ensure the configure gets called before the AvatarEntity Awake? Thanks!!

         

        One question, 

  • olaysia's avatar
    olaysia
    Expert Protege

    I've got the same problem, let me know if you find a solution

  • Hi!
    I followed all the above steps. In editor with quest2 it works as it should be, but in quest2 standalone build nothing appears from character. No hands no avatar. Edit: Even the example files are not showing Avatar in builds. I made the test people admin roles.
    Please help.

    Other question: If I would like to put collisions on the index finger, how would I do that?
    Thank you in advance 🙂

    • cazforshort's avatar
      cazforshort
      Expert Protege

      Hi Muszti, Did you ever figure out how to add colliders/rigidbodies to the hands of meta avatarts?

      • Muszti's avatar
        Muszti
        Protege

        Hi cazforshort!
        I just instantiate my custom hands prefabs. Those has collisions. But I'm experiencing a significant FPS loss.

    • Gabo.Andres's avatar
      Gabo.Andres
      Explorer

      For your first question, check that you are using unity 2020 LTS version, the avatar sdk wont work with unity 2021 or 2022.

      and for the second question it depends, why are you trying to add collisions to the finger? if is for interact with objects or buttons I use the interaction SDK that comes with the oculus integration and it works fine! no fps loss on quest 2 standalone

      • cazforshort's avatar
        cazforshort
        Expert Protege

        Slap objects, so the interaction won’t work super well.  Also how do you use SetActiveView()? I need to hide the local avatar once I have a remote avatar connected, but localPlayer.SetActiveView(“None”) is Definitely not right. 

  • Here is the $1,000,000 follow-up question. How do you use the new avatars in Fusion. I feel like there has to be a super optimized built in method I'm not seeing to handle the byte[] recordings / snapshots.

    • owain_rich's avatar
      owain_rich
      Honored Guest

      Hi Olaysia - thanks for sharing this super useful code. I was trying to reply to Evans_Taylor_Digital as I'm having the same problem - using your code like-for-like but no game objects instantiating inside the remote avatar.  Everything looks like it's happening as it should - ConfigureAvatarEntity() is setting the isLocal flag and the creation info is being properly set to the remote preset before Awake in the base class runs, but nothing is created inside the OtherAvatar game object.  I had partial success using ForceEnableFeatures() to set UseDefaultAnimHierarchy - doing that fixed the spawning of the children inside the OtherAvatar game object and I could see them moving around over the network, but no meshes were visible. I'll continue trying things and add this to the thread if I get it working. It sounds like most people got this working with the code you posted so it's obviously something I'm doing wrong.

       

       

  • olaysia's avatar
    olaysia
    Expert Protege

    Oh, my bad. I thought this was a reply to one of my earlier posts.

    It sounds like your other avatar prefab is instantiating but its failing to load its meta avatar. How are you instantiating the other avatar? Are you passing it a user id to load? Because if it doesn't have a user id it won't be able to load its avatar.

    • owain_rich's avatar
      owain_rich
      Honored Guest

      I think you're right. I am using your code:

      Int64 userId = Convert.ToInt64(m_userId);
              object[] objects = new object[1] { userId };
              GameObject myAvatar = PhotonNetwork.Instantiate("NetworkAvatar", spawnPos, Quaternion.identity, 0, objects);

       

      But I think m_userId is null because I'm not getting the value properly from the other script.  It's really helpful to know that if I'm not passing the value it won't spawn any mesh. Kind of obvious now you pointed it out - thank you!

      • olaysia's avatar
        olaysia
        Expert Protege

        Yes! You're going to need to fetch a user id before you photon instantiate that avatar: Here's how I did it:

        public class LogInManager : MonoBehaviourPunCallbacks
        {
            const string ROOM_NAME = "Meta Avatar Sdk Test Room";
            const float SPAWN_POS_Z = 1f;
            const string AVATAR_PREFAB_NAME = "GGMetaAvatarEntity";
        
            [SerializeField] Text m_screenText;
            [SerializeField] Camera m_camera;
            [SerializeField] float m_minSpawnPos_x = -5f;
            [SerializeField] float m_maxSpawnPos_x = 5f;
            [SerializeField] float m_minSpawnPos_z = -5f;
            [SerializeField] float m_maxSpawnPos_z = 5f;
            [SerializeField] ulong m_userId;
        
            //Singleton implementation
            private static LogInManager m_instance;
            public static LogInManager Instance
            {
                get
                {
                    return m_instance;
                }
            }
            private void Awake()
            {
                if (m_instance == null)
                {
                    m_instance = this;
                }
                else
                {
                    Destroy(this.gameObject);
                }
            }
        
            void Start()
            {
                StartCoroutine(SetUserIdFromLoggedInUser());
                StartCoroutine(ConnectToPhotonRoomOnceUserIdIsFound());
                StartCoroutine(InstantiateNetworkedAvatarOnceInRoom());
            }
        
            IEnumerator SetUserIdFromLoggedInUser()
            {
                if (OvrPlatformInit.status == OvrPlatformInitStatus.NotStarted)
                {
                    OvrPlatformInit.InitializeOvrPlatform();
                }
        
                while (OvrPlatformInit.status != OvrPlatformInitStatus.Succeeded)
                {
                    if (OvrPlatformInit.status == OvrPlatformInitStatus.Failed)
                    {
                        Debug.LogError("OVR Platform failed to initialise");
                        m_screenText.text = "OVR Platform failed to initialise";
                        yield break;
                    }
                    yield return null;
                }
        
                Users.GetLoggedInUser().OnComplete(message =>
                {
                    if (message.IsError)
                    {
                        Debug.LogError("Getting Logged in user error " + message.GetError());
                    }
                    else
                    {
                        m_userId = message.Data.ID;
                    }
                });
            }
        
            IEnumerator ConnectToPhotonRoomOnceUserIdIsFound()
            {
                while (m_userId == 0)
                {
                    Debug.Log("Waiting for User id to be set before connecting to room");  
                    yield return null;
                }
                ConnectToPhotonRoom();
            }
        
            void ConnectToPhotonRoom()
            {
                PhotonNetwork.ConnectUsingSettings();
                m_screenText.text = "Connecting to Server";
            }
        
            public override void OnConnectedToMaster()
            {
                PhotonNetwork.JoinLobby();
                m_screenText.text = "Connecting to Lobby";
            }
        
            public override void OnJoinedLobby()
            {
                m_screenText.text = "Creating Room";
                PhotonNetwork.JoinOrCreateRoom(ROOM_NAME, null, null);
            }
        
            public override void OnJoinedRoom()
            {
                string roomName = PhotonNetwork.CurrentRoom.Name;
                m_screenText.text = "Joined room with name " + roomName;
            }
        
            IEnumerator InstantiateNetworkedAvatarOnceInRoom()
            {
                while (PhotonNetwork.InRoom == false)
                {
                    Debug.Log("Waiting to be in room before intantiating avatar");
                    yield return null;
                }
                InstantiateNetworkedAvatar();
            }
        
            void InstantiateNetworkedAvatar()
            {
                float rand_x = UnityEngine.Random.Range(m_minSpawnPos_x, m_maxSpawnPos_x);
                float rand_z = UnityEngine.Random.Range(m_minSpawnPos_z, m_maxSpawnPos_z);
                Vector3 spawnPos = new Vector3(rand_x, SPAWN_POS_Z, rand_z);
                Int64 userId = Convert.ToInt64(m_userId);
                object[] objects = new object[1] { userId };
                GameObject myAvatar = PhotonNetwork.Instantiate(AVATAR_PREFAB_NAME, spawnPos, Quaternion.identity, 0, objects);
                m_camera.transform.SetParent(myAvatar.transform);
                m_camera.transform.localPosition = Vector3.zero;
                m_camera.transform.localRotation = Quaternion.identity;
            }
        
        }
  • Thanks Olaysia - this worked perfectly - remote and local avatars showing up with the correct users. By looking through the code I now understand how this is working too. This whole thread is fantastic and will help a lot of people figure this out!

  • It's the local variable that holds the result of the message requested.

    m_userId = message.Data.ID;
  • I am trying to use Meta Avatars to create a multi-player experience, but I am well over my head. Where are the official tutorial videos? If they don't exist, where should I begin in order to understand PUN / Meta Avatar SDK functionality? Those links to 'documentation' just don't do it.