Uma Avatar System Network Example?

Networked games and character customization tend to go hand in hand.

As a learning hobbyist I have played around with both the UMA plugins and the Photon plugins and can work with them individually, but still cant figure out how to use them together.

Since UMA seems to be the up and coming standard for avatars, it would be nice to have a networking tutorial/example here for working with them and Photon.

The problem I have is that the avatars are created dynamically, not instantiated as prefabs, and I am not sure how to work with that difference.
It could be quite obvious as I am still learning lots, but a tutorial would help not just myself but I'm sure a multitude of others struggling to 'learn' faster than they should.

thanks,

narfi

(any tips on how to approach the problem without a tutorial/example would be welcome as well)

Comments

  • Thanks for the heads up. Doing such a tutorial sounds like a good idea. It's a different angle from making tutorials for specific game types, etc.

    You are able to manually assign a PhotonView and network viewID to your avatar, when you are not able to simply instantiate it. When that's done, you need a script that gathers the parameters with which your avatar can be re-created on another machine. Those must be sent to the others. You could "save" them as player properties.

    Can you somehow access the creation values of an UMA avatar?
  • The UMA system is free in the asset store (addons are paid)
    From what I have learned so far I can 'create' an avatar (even spawn one from a prefab) and once it exists then change its 'recepie' to the appearance I want.

    The avatar/prefab is a pretty deep tree of objects, where the actual moving character is 2 or 3 branches down.
    You are saying that if I put a photonview and networkview ID on the moving character it should work? (I think this would produce identical characters to the one that is mine whenever someone else joined)

    Since I can spawn a prefab avatar (just not one customized yet) I can add and observe a script to the top level of the prefab that contains the recepie of the avatar and makes the changes once it appears, as well as the Transform of the lower branch that is the actual moving character?

    When spawned it looks like this,
    avaPrefab(Clone) - the prefab containing the details and recepie to make it unique
    - UMA_Human_Female(Clone) - empty game object, Im not sure what it does
    -- Female_Unified - The actual moving character, all bones etc.. below this
  • I have that mostly working by modifying the NetworkCharacter.cs script found in the macro polo Example.
    (The networked characters move and change appearance on all clients) (I don't have animation sync figured out yet)


    [code2=csharp]using UnityEngine;

    public class NetworkCharacterUMA : Photon.MonoBehaviour
    {
    public GameObject ava;
    public string testRecipe;
    private Vector3 correctPlayerPos = Vector3.zero; // We lerp towards this
    private Quaternion correctPlayerRot = Quaternion.identity; // We lerp towards this
    // Update is called once per frame

    void Start()
    {
    ava = this.transform.GetChild (0).transform.GetChild (0).gameObject;
    if (photonView.isMine) {
    Camera.main.transform.parent = ava.transform;
    Camera.main.transform.position = new Vector3(0f,1.5f,-1.5f);

    ava.GetComponent<Locomotion>().enabled = true;
    }
    }
    void Update()
    {
    if (!photonView.isMine)
    {
    ava.transform.position = Vector3.Lerp(ava.transform.position, this.correctPlayerPos, Time.deltaTime * 5);
    ava.transform.rotation = Quaternion.Lerp(ava.transform.rotation, this.correctPlayerRot, Time.deltaTime * 5);
    }
    }

    void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
    var avatar = gameObject.GetComponent<UMAAvatarBase>();
    var asset = ScriptableObject.CreateInstance<UMATextRecipe>();
    asset.Save(avatar.umaData.umaRecipe, avatar.context);


    if (stream.isWriting)
    {
    // We own this player: send the others our data
    stream.SendNext(ava.transform.position);
    stream.SendNext(ava.transform.rotation);

    asset.Save(avatar.umaData.umaRecipe, avatar.context);
    stream.SendNext (asset.recipeString);







    // myThirdPersonController myC = GetComponent<myThirdPersonController>();
    // stream.SendNext((int)myC._characterState);
    }
    else
    {
    // Network player, receive data
    this.correctPlayerPos = (Vector3)stream.ReceiveNext();
    this.correctPlayerRot = (Quaternion)stream.ReceiveNext();

    asset.recipeString = (string)stream.ReceiveNext();
    if (asset.recipeString != testRecipe){
    avatar.Load(asset);
    }
    testRecipe = asset.recipeString;






    // myThirdPersonController myC = GetComponent<myThirdPersonController>();
    // myC._characterState = (CharacterState)stream.ReceiveNext();
    }
    }
    }[/code2]

    hopefully figuring out the animation syncing isn't too complex.
  • Seems that code only works if your avatar changes are within the same gender, if the gender changes then it will destroy the camera when it spawns the new gender.
  • I think I got it working properly now, I'm sure the code could be cleaner but I tried to comment what I did so someone with more knowledge than me could make a proper example from it, or others learning can hopefully piece together what I did.

    [code2=csharp]using UnityEngine;

    public class NetworkCharacterUMA : Photon.MonoBehaviour
    {
    public GameObject ava;
    public string myRecipe;
    public string testRecipe;
    private Vector3 correctPlayerPos = Vector3.zero; // We lerp towards this
    private Quaternion correctPlayerRot = Quaternion.identity; // We lerp towards this
    // Update is called once per frame

    public Animator animator;

    void Start()
    {
    //The actual moving avatar is the child of the child of the prefab
    ava = this.transform.GetChild (0).transform.GetChild (0).gameObject;
    //set the camera for your own avatar and turn on Locomotion
    if (photonView.isMine) {
    Camera.main.transform.parent = ava.transform;
    Camera.main.transform.position = new Vector3(0f,1.5f,-1.5f);

    ava.GetComponent<Locomotion>().enabled = true;
    }

    //Get the testRecipe from your prefab avatar
    var avatar = gameObject.GetComponent<UMAAvatarBase>();
    var asset = ScriptableObject.CreateInstance<UMATextRecipe>();
    asset.Save(avatar.umaData.umaRecipe, avatar.context);
    //If no recipe is specified, set it as the prefab one, if it is then load it.
    if (myRecipe == "") {
    myRecipe = asset.recipeString;
    } else {
    avatar.Load (asset);
    }



    }
    void Update()
    {
    //incase a new recipe was used, get the avatar and turn on its animator
    ava = this.transform.GetChild (0).transform.GetChild (0).gameObject;
    animator = ava.GetComponent<Animator>();



    //if the avatar is not mine, then smooth its movement and make sure that locmotion is off
    if (!photonView.isMine) {
    ava.transform.position = Vector3.Lerp (ava.transform.position, this.correctPlayerPos, Time.deltaTime * 5);
    ava.transform.rotation = Quaternion.Lerp (ava.transform.rotation, this.correctPlayerRot, Time.deltaTime * 5);
    ava.GetComponent<Locomotion>().enabled = false;
    } else {

    // if it is mine, then make sure that the camera and locomotion are set properly
    Camera.main.transform.parent = ava.transform;
    ava.GetComponent<Locomotion>().enabled = true;

    //get the currently used recipe
    var avatar = gameObject.GetComponent<UMAAvatarBase> ();
    var asset = ScriptableObject.CreateInstance<UMATextRecipe> ();
    asset.Save (avatar.umaData.umaRecipe, avatar.context);

    //if the recipe has changed, then move the camera to safety
    //and load the new avatar using the new recipe
    if (asset.recipeString != myRecipe) {
    Camera.main.transform.parent = GameObject.Find("Code").transform;
    asset.recipeString = myRecipe;
    avatar.Load (asset);

    }
    }
    }


    void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
    //get the current recipe
    var avatar = gameObject.GetComponent<UMAAvatarBase>();
    var asset = ScriptableObject.CreateInstance<UMATextRecipe>();
    asset.Save(avatar.umaData.umaRecipe, avatar.context);



    // We own this player: send the others our data
    if (stream.isWriting)
    {
    // avatar position and rotation
    stream.SendNext(ava.transform.position);
    stream.SendNext(ava.transform.rotation);

    //avatar Recipe
    // asset.Save(avatar.umaData.umaRecipe, avatar.context);
    stream.SendNext (asset.recipeString);
    //avatar speed used by the Animator for knowing what animation to use
    stream.SendNext (animator.GetFloat("Speed"));


    }
    else
    {
    // Network player, receive data
    //receive the avatar position and rotation
    this.correctPlayerPos = (Vector3)stream.ReceiveNext();
    this.correctPlayerRot = (Quaternion)stream.ReceiveNext();

    //receive avatar recipe
    asset.recipeString = (string)stream.ReceiveNext();

    //if the recipe has changed, then load the new recipe to change the avatars apperance
    if (asset.recipeString != testRecipe){
    avatar.Load(asset);

    //store the new recipe to compare if it has changed next time
    testRecipe = asset.recipeString;
    }

    //receive avatar speed for use by the animator
    animator.SetFloat("Speed", (float)stream.ReceiveNext());
    }
    }
    }[/code2]
  • Ok, I am not sure if this is related or not, but I am having an issue with spawning in a new scene.

    I have a startup scene (not networked) that calls the main game scene using Application.Load("Scene Name");
    (Only the main game scene connects to the photon network so I did not use PhotonNetwork.LoadLevel.... is this wrong?)
    If i load the main game scene everything works fine and I can instantiate the avatar (done with a script in the scene once loaded)
    but if I load the scene from another scene, then I get this error,

    [code2=plain]OnDestroy for PhotonView View (0)1001 on avaPrefab2(Clone) but GO is still in instantiatedObjects. instantiationId: 1001. Use PhotonNetwork.Destroy(). Loading new scene caused this. Identical with this: True PN.Destroyed called for this PV: True
    UnityEngine.Debug:LogWarning(Object)
    PhotonView:OnDestroy() (at Assets/Photon Unity Networking/Plugins/PhotonNetwork/PhotonView.cs:217)[/code2]

    any ideas what I am doing wrong?
    Thanks for the help,

    narfi
  • nevermind... I was loading the the scene in a persistant script and forgot to toggle it off once loaded, so it was just a loop loading the scene
  • Glad you found it. This would have been hard to find from here.
  • Hello every body
    this is mohamed fayed from egypt
    nice to meet you
    i have problem about connect uma avator with photon network

    i use this code but its not working
    can you help me please ??

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
    if (stream.IsWriting)
    {




    stream.SendNext(Avatar.GetCurrentRecipe());







    }
    else
    {
    Avatar.LoadFromRecipeString((string)stream.ReceiveNext());




    }
    }
    thaink you very mucb