2017年2月13日月曜日

【HoloLens開発】ユニティちゃんとHoloLensで戯れる - シェアリング編2 -

前回、2台のHoloLensで同じユニティちゃんを見られるようにしました。今回は予定よりだいぶ遅くなりましたが、2台のHoloLensでそれぞれ生成したユニティちゃんを1台の端末から見られるようにしたいと思います。


連載目次
Part 7: 【HoloLens開発】ユニティちゃんとHoloLensで戯れる - シェアリング編1 -
Part 8: 【HoloLens開発】ユニティちゃんとHoloLensで戯れる - シェアリング編2 - ←本記事
Part 9: 【HoloLens開発】ユニティちゃんとHoloLensで戯れる - シェアリング編3 - (予定)

【利用シーン】
今回のシェアリングの方法では、同じオブジェクトだとあまり意味がありませんが、別々のオブジェクトを共有することで対戦ゲーム等でのキャラクター表示に役立ちます。また、自身の端末の分は表示せず、他端末のものだけを表示しアバターとして用いてコミュニケーションツールのようにもできます。


【アバターコントロール】
今回、生成するユニティちゃんをプレイヤーのアバターとします。
まずはアバターをコントロールするための空のプレハブ「PlayerAvatarStore」をHierarchyに追加します。

続いてAssets > Scripts にAvatarDataContainerとPlayerAvatarManagerの2つのスクリプトを作成します。

AvatarDataContainerはアバターに関連するデータの保存用スクリプトですが、今回はサーバー接続時に割り振られたユーザーIDを保存するために利用します。
PlayerAvatarManagerはアバターデータを他端末とやり取りするために利用します。
AvatarDataContainerはunitychanプレハブ、PlayerAvatarManagerはPlayerAvatarStoreプレハブのコンポーネントとして追加します。


それぞれのソースコードは以下の通りです。
using UnityEngine;
using System.Collections;
using HoloToolkit.Sharing;

public class AvatarDataContainer : MonoBehaviour {

    public long UserId { get; set; }

    public void OnStart()
    {
        if (UserId <= 0)
        {
            UserId = SharingStage.Instance.Manager.GetLocalUser().GetID();
        }
    }
}

using UnityEngine;
using System.Collections.Generic;
using HoloToolkit.Unity;
using HoloToolkit.Sharing;

public class PlayerAvatarManager : Singleton<PlayerAvatarManager> {
    // クローンするオブジェクト
    // UnityのInspectorから指定する
    public GameObject obj;

    // objの親となるオブジェクト
    // UnityのInspectorから指定する
    public GameObject parent;

    // サービス接続時に割り振られた
    private long localUserId { get; set; }

    // 共有されているアバターのリスト
    private Dictionary<long, GameObject> AvatarDic = new Dictionary<long, GameObject>();

    void Start () {
        // 自身のアバターを生成
        localUserId = SharingStage.Instance.Manager.GetLocalUser().GetID();
        GameObject avatar = Instantiate(obj);
        avatar.transform.parent = parent.transform;
        AddAvatar(localUserId, avatar);
        AvatarDataContainer container = avatar.GetComponent<AvatarDataContainer>();
        if (container != null)
        {
            container.UserId = localUserId;
        }

        // メッセージ受信ハンドラの登録
        CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.StageTransform] = this.OnStageTransfrom;
        CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.Speed] = this.OnSpeed;
        CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.Jump] = this.OnJump;
        CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.Rest] = this.OnRest;
        CustomMessages.Instance.MessageHandlers[CustomMessages.TestMessageID.SpeedAndDirection] = this.OnSpeedAndDirection;
    }

    // 自身のユーザーIDかどうか判定
    public bool IsLocalUserId(long userId)
    {
        return userId == localUserId;
    }

    // 他端末のユーザーIDかどうか判定
    public bool IsAnotherUserId(long userId)
    {
        return userId != localUserId && AvatarDic.ContainsKey(userId);
    }

    // アバターをリストに追加
    public void AddAvatar(long userId, GameObject avatar)
    {
        AvatarDic.Add(userId, avatar);
    }

    // リストからアバターを取得
    public GameObject GetAvatar(long userId)
    {
        if (HasAvatar (userId))
        {
            return AvatarDic[userId];
        } else
        {
            return null;
        }
    }

    // リストから自身のアバターを取得
    public GameObject GetLocalAvatar()
    {
        return GetAvatar(localUserId);
    }

    // すでにアバターが存在するかどうか判定
    public bool HasAvatar(long userId)
    {
        return AvatarDic.ContainsKey(userId);
    }

    // 他端末のアバターの位置情報を処理
    void OnStageTransfrom(NetworkInMessage msg)
    {
        long userId = msg.ReadInt64();
        GameObject avatar;

        if (HasAvatar(userId))
        {
            avatar = GetAvatar(userId);
        }
        else
        {
            // まだリストになければアバターを生成
            avatar = Instantiate(obj);
            avatar.transform.parent = parent.transform;
            // 他端末のアバターは重力の影響を無効にする
            // ※送信元で制御する
            avatar.GetComponent<Rigidbody>().useGravity = false;
            // 他端末のアバターはコントローラーでの操作を無効にする
            // ※送信元で制御する
            AddAvatar(userId, avatar);
            AvatarDataContainer container = avatar.GetComponent<AvatarDataContainer>();
            if (container != null)
            {
                container.UserId = userId;
            }
        }
        avatar.transform.localPosition = CustomMessages.Instance.ReadVector3(msg);
        avatar.transform.localRotation = CustomMessages.Instance.ReadQuaternion(msg);
    }

    // 他端末のアバターのSpeed値を処理
    void OnSpeed(NetworkInMessage msg)
    {
        long userId = msg.ReadInt64();
        float speed = msg.ReadFloat();

        if (HasAvatar(userId))
        {
            Animator anim = GetAvatar(userId).GetComponent();

            anim.SetFloat("Speed", speed);
        }
    }

    // 他端末のアバターのJump値を処理
    void OnJump(NetworkInMessage msg)
    {
        long userId = msg.ReadInt64();
        bool jump = msg.ReadInt32() > 0;

        if (HasAvatar(userId))
        {
            Animator anim = GetAvatar(userId).GetComponent<Animator>();

            anim.SetBool("Jump", jump);
        }
    }

    // 他端末のアバターのRest値を処理
    void OnRest(NetworkInMessage msg)
    {
        long userId = msg.ReadInt64();
        bool rest = msg.ReadInt32() > 0;

        if (HasAvatar(userId))
        {
            Animator anim = GetAvatar(userId).GetComponent<Animator>();

            anim.SetBool("Rest", rest);
        }
    }

    // 他端末のアバターのSpeed値とDirection値を処理
    void OnSpeedAndDirection(NetworkInMessage msg)
    {
        long userId = msg.ReadInt64();
        float speed = msg.ReadFloat();
        float direction = msg.ReadFloat();

        if (HasAvatar(userId))
        {
            Animator anim = GetAvatar(userId).GetComponent<Animator>();

            anim.SetFloat("Speed", speed);
            anim.SetFloat("Direction", direction);
        }
    }
}

PlayerAvatarManagerで行っている処理としては以下の3つです。
  1. Start時に自身のアバターを生成
      ポイントとして、元々用意しているunitychanは非アクティブにしておきます。
  2. 他端末のアバターの位置情報等を処理
      自身のアバターも含めユーザーIDと一緒にDictionaryで管理しておきます。
      他端末からのイベントを受信したら、その端末のアバターに対して処理を実行します。

InspectorからPlayerAvatarManagerのObjとParentの指定を行います。
HierarchyのunitychanをObjに、HologramCollectionをParentにそれぞれ指定します。


また、前回修正したXboxOneControllerスクリプトも修正します。
修正内容としては以下の通りです。
  ・ コントローラーがつながっていた場合のみの操作という条件を削除
  ・ シェアリングサービスからのメッセージ受信ハンドラを削除

これでそれぞれの端末で生成したオブジェクトが表示されるようになります。
今回はユニティちゃんが複数表示されるだけですが、アバターを選択できるようにすると
各人のアバターがどれか分かりやすくなり、ゲーム等に使いやすくなります。


次回はシェアリングサービスからの切断時の処理について公開予定です。
140 180

記載されている会社名、および商品名等は、各社の商標または登録商標です。

0 コメント:

コメントを投稿

Related Posts Plugin for WordPress, Blogger...