LoginSignup
4
6

More than 3 years have passed since last update.

【Unity(C#),PUN2】ルームの入退室に対応したプレイヤー生成位置、色の振り分け

Last updated at Posted at 2020-07-05

やりたかったこと

まず前提として、ネットワーク経由で同期し、
ルームに好きなタイミングで出入りできるアプリケーション

作成を行っていました。

その中で、プレーヤーごとの生成位置及び色をどうきめるか
という問題に直面しました。

位置も色も被ることなくプレイヤーに反映したい! 
というのが今回の記事の本筋です。

試行錯誤

最初に思いついたのは
ルームの人数に応じたプレイヤーの生成位置、色の振り分けです。

ルーム入室時の人数が
0人なら1番目の位置、色
1人なら2番目の位置、色
2人なら3番目の位置、色
3人なら4番目の位置、色 といった具合です。

ただし、このやり方だと再入室時に位置被り、色被りが発生してしまいます。


既に部屋に3人いる場合

部屋には3人.png


4人目の場所と色を割り当てる

4人目の場所と色.png


3人目のプレイヤーが退出

抜けます.png


再入室(もしくは別プレイヤーの入室)

再入室部屋には3人.png


部屋の人数は4人なので既に存在する4番目の位置、色に割り当てられたプレイヤーと被る

4人目の場所と色?.png

そこで別のやり方を考えました。

プレイヤーごとに番号を割り当てる

入室時にプレイヤーごとに番号を割り当てるという方法を思いつきました。
まずはルーム内で使われていない番号をチェックします。

使われていない番号が
1なら番号1をプレイヤーに割り当て
2なら番号2をプレイヤーに割り当て
3なら番号3をプレイヤーに割り当て
4なら番号4をプレイヤーに割り当て といった具合です。

あとは各々のクライアントが各自のローカルで生成された
同期オブジェクトの所有権を持つプレイヤーを取得し、
番号をチェックして処理を行います。

プレイヤー番号振り.png

番号ごとに各々のクライアントが自分のローカル環境で処理を行うので、
位置情報や色を送る必要がなくなります。

つまり、この方法を使えば通信に必要な情報は
プレイヤーごとに割り振られた番号のみ
で済みます。

カスタムプロパティ

入室時にプレイヤーごとに番号を割り当てるという実装を行う上で便利そうな
カスタムプロパティというPUN2の機能に関する記事を発見しました。

【参考リンク】:PUN2で始めるオンラインゲーム開発入門【その5】

今回はカスタムプロパティを利用して、
ルームの入退室に対応したプレイヤー生成位置、色の振り分けを
実装していきます。

カスタムプロパティはプレイヤー単位で設定できて、
必要な時に通信して呼び出す
形で利用できるので
今回の目的との親和性は高いと思います。

拡張メソッド

カスタムプロパティを使う上で拡張メソッドは必須かなと個人的には思ってます。

理由としては、カスタムプロパティの定義には文字列のキーが必要だからです。

とある文字列Xに対して、値を紐づけていくようなイメージです。

コードに落とし込むとこんな感じになります。

using Photon.Realtime;
using UnityEngine;
using Hashtable = ExitGames.Client.Photon.Hashtable;

public static class PlayerPropertyExtensions
{
    private const string PLAYER_ASSIGN_NUMBER = "n"; 

    private static Hashtable _hashtable = new Hashtable();

    //=========================================
    // プレイヤーの番号
    //=========================================

    //Hashtableにプレイヤーに割り振られた番号があれば取得する
    private static bool TryAndGetPlayerNum(this Hashtable hashtable, out int playerAssignNumber)
    {
        if (hashtable[PLAYER_ASSIGN_NUMBER] is int value)
        {
            playerAssignNumber = value;
            return true;
        }

        playerAssignNumber = 0;
        return false;
    }

    //プレイヤー番号を取得する
    public static int GetPlayerNum(this Player player)
    {
        player.CustomProperties.TryAndGetPlayerNum(out int playerNum);
        return playerNum;
    }

    //プレイヤーの割り当て番号のカスタムプロパティを更新する
    public static void UpdatePlayerNum(this Player player, int assignNum)
    {
        _hashtable[PLAYER_ASSIGN_NUMBER] = assignNum;
        player.SetCustomProperties(_hashtable);
        _hashtable.Clear();
    }
}

任意の文字列キーのカスタムプロパティを取得、更新する拡張メソッドを用意することで、
利用側は文字列キーの誤りを気にする必要性がなくなります。

使用済みの番号を特定し、未使用の番号でプレイヤーの番号を更新

拡張メソッドで利用側はかなり楽になったので、
プレイヤーごとの番号割り当ての実装に移ります。

順番としては見出し通り下記です。
①使用済みの番号を特定
②未使用の番号でプレイヤーの番号を更新

コードは以下です。

private const int _PLAYER_UPPER_LIMIT = 4;

/// <summary>
/// プレイヤーに番号を与える
/// </summary>
private void SetMyCustomProperties()
{ 
    //自分のクライアントの同期オブジェクトにのみ
    if (photonView.IsMine)
    {
        List<int> playerSetableCountList = new List<int>();

        //制限人数までの数字のリストを作成
        //例) 制限人数 = 4 の場合、{0,1,2,3}
        int count = 0;
        for (int i = 0; i < _PLAYER_UPPER_LIMIT; i++)
        {
            playerSetableCountList.Add(count);
            count++;
        }

        //他の全プレイヤー取得
        Player[] otherPlayers = PhotonNetwork.PlayerListOthers;

        //他のプレイヤーがいなければカスタムプロパティの値を"0"に設定
        if (otherPlayers.Length <= 0)
        {
            //ローカルのプレイヤーのカスタムプロパティを設定
            int playerAssignNum = otherPlayers.Length;
            PhotonNetwork.LocalPlayer.UpdatePlayerNum(playerAssignNum);
            return;
        }

        //他のプレイヤーのカスタムプロパティー取得してリスト作成
        List<int> playerAssignNums = new List<int>();
        for (int i = 0; i < otherPlayers.Length; i++)
        {
            playerAssignNums.Add(otherPlayers[i].GetPlayerNum());
        }

        //リスト同士を比較し、未使用の数字のリストを作成
        //例) 0,1にプレーヤーが存在する場合、返すリストは2,3
        playerSetableCountList.RemoveAll(playerAssignNums.Contains);

        //ローカルのプレイヤーのカスタムプロパティを設定
        //空いている場所のうち、一番若い数字の箇所を利用
        PhotonNetwork.LocalPlayer.UpdatePlayerNum(playerSetableCountList[0]);
    }
} 

コメントの通りの処理を行っています。

番号に応じた生成位置と色を割り当て

未使用の番号でプレイヤーの番号を更新 することができたので、
割り当てられた番号を利用した処理を追加していきます。

PhotonViewがアタッチされたアバターオブジェクトにアタッチ
using System.Collections.Generic;
using ExitGames.Client.Photon;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;

/// <summary>
/// プレーヤー生成時に行う初期設定処理
/// </summary>
public class PlayerInitializeSetting : MonoBehaviourPunCallbacks
{
    [SerializeField] private MeshRenderer _avatarObjectMeshRenderer;
    [SerializeField] private Material[] _playerMaterials;
    [SerializeField] private Transfrom[] _playerInitTransform;

    private void Start()
    {
        //プレーヤーのカスタムプロパティ更新
        SetMyCustomProperties();
    }

    /// <summary>
    /// カスタムプロパティ更新時のコールバック
    /// </summary>
    /// <param name="target">更新されたカスタムプロパティを持つプレーヤー</param>
    /// <param name="changedProps">更新されたカスタムプロパティ</param>
    public override void OnPlayerPropertiesUpdate(Player target, Hashtable changedProps)
    {
        //自分のクライアントの同期オブジェクトの設定
        if (photonView.IsMine)
        {
            this.gameObject.transform.rotation = _playerInitTransform[PhotonNetwork.LocalPlayer.GetPlayerNum()].rotation;
            this.gameObject.transform.position = _playerInitTransform[PhotonNetwork.LocalPlayer.GetPlayerNum()].position;
            _avatarObjectMeshRenderer.sharedMaterial = _playerMaterials[PhotonNetwork.LocalPlayer.GetPlayerNum()];
        }
        //他のクライアントの同期オブジェクトの設定
        else
        {
            this.gameObject.transform.rotation = _playerInitTransform[photonView.Owner.GetPlayerNum()].rotation;
            this.gameObject.transform.position = _playerInitTransform[photonView.Owner.GetPlayerNum()].position;
            _avatarObjectMeshRenderer.sharedMaterial = _playerMaterials[photonView.Owner.GetPlayerNum()];
        }
    }
    }
}

OnPlayerPropertiesUpdateという
通信完了後、カスタムプロパティが更新し終えたことを
通知するコールバック
 内でフラグを立てています。

このフラグを利用し、Start関数内でasync/awaitして完了待ちを実装しています。

デモ

手話VRSS2.PNG

実際に今回メモした実装方法でプレイヤーの生成位置、色の設定を
入退室に対応させたアプリのデモムービーです。

【参考リンク】:Sign Language in VR

オンライン上でVR空間内での手話トレーニングを実施し、
動画やビデオ通話以上のトレーニング効果を期待したVRアプリです。

最後に

カスタムプロパティで初期設定を行う方法を使用しましたが、
完全に同じタイミングでカスタムプロパティを
更新した場合のエラーハンドリングなどが未実装です。

私が把握している問題は現状これだけですが、
まだまだ対策しないといけないことが多そうです。

引き続き深みにはまっていこうと思います。。。

参考リンク

PUN2で始めるオンラインゲーム開発入門【その4】

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6