27681


[マルチポストです。](https://teratail.com/questions/41184)  
**UnityのNetworkに関して，操作オブジェクトを切替る方法が知りたいです。**  
例えばゲーム画面上に２つのオブジェクト
Player1，Player2があるとして，自分がPlayer1を方向キーで操作できるとします。その際，スペースボタンを押せばPlayer2の操作に切替る，そしてもう一度スペースを押せばPlayer１の操作に切替る方法が知りたいです。

（やってみたこと）  
[Unity本家サイト](http://docs.unity3d.com/ja/current/Manual/UNetSetup.html)の「マルチプレイヤーの動きをテストする」まで同じことを行いました。そして[プレイヤーオブジェクト](http://docs.unity3d.com/ja/current/Manual/UNetPlayers.html)のプレイヤーの切替を参考にPlayerのScriptを以下のように書き換えました。

[code]

    using UnityEngine;
    using UnityEngine.Networking;
    
    public class PlayerMove : NetworkBehaviour {
    
        string player1;
        string player2;
    
        void Start(){
            player1 = "Player1(Clone)";
            player2 = "Player2";
        }
    
        // Update is called once per frame
        void Update () {
            if(!isLocalPlayer){
                return;
            }
    
    
            float x = Input.GetAxis("Horizontal")*0.1f;
            float z = Input.GetAxis("Vertical")*0.1f;
    
            transform.Translate(x, 0, z);
    
            if(Input.GetKeyDown(KeyCode.Space)){
                Debug.Log("切替");
                PlayerChange(this.gameObject);
            }
        }
    
      public void PlayerChange(GameObject oldPlayer)
      {
          var conn = oldPlayer.GetComponent<NetworkIdentity>().connectionToClient;
    
          GameObject newPlayer;
          if(this.gameObject.name == player1){
              newPlayer = GameObject.Find(player2);
              NetworkServer.ReplacePlayerForConnection(conn, newPlayer, 0);
          }
          else{
              newPlayer = GameObject.Find(player1); 
              NetworkServer.ReplacePlayerForConnection(conn, newPlayer, 0);
          }  
      }
    
    }
    
[/code]

またPlayer1（Prefab）をHierarchyにドラッグしてPlayer2というオブジェクトを作っています。

（エラー）  
上のコードで再生したところ，Player1からPlayer2への切替はうまくいったのですが，Player2からPlayer1に切り替えようとすると以下のエラーが出ました。

[code]

    SetClientOwner m_ClientAuthorityOwner already set!
    UnityEngine.Networking.NetworkServer:ReplacePlayerForConnection(NetworkConnection, GameObject, Int16)
    PlayerMove:PlayerChange(GameObject) (at Assets/PlayerMove.cs:43)
    PlayerMove:Update() (at Assets/PlayerMove.cs:28)
    
[/code]


CC BY-SA 3.0
2016-07-15T02:49:22.857
27639
2016-07-20T21:00:01.883
2016-07-17T08:34:54.987
3783
8100
question
c# unity3d
Network対戦での操作オブジェクトの切替方法


厳密な原因はわかりませんが、サーバーがSpawnしていないGameObjectをサーバーが扱うのは問題が起きそうな気がします。

そこで、Player2の生成をサーバーに行わせることで問題を回避できないかと試したらできました。

まずはUnityのマニュアルにあるPlayerMoveクラスに手を加えたものです:

[code]

    // PlayerMove.cs
    using UnityEngine;
    using UnityEngine.Networking;
    
    public class PlayerMove : NetworkBehaviour
    {
        public GameObject playerPrefab;
        static bool player2Spawned;
        static bool playerSwitched;
    
        void Update ()
        {
            if (!isLocalPlayer || playerSwitched)
            {
                playerSwitched = false;
                return;
            }
            var x = Input.GetAxis("Horizontal") * 0.1f;
            var z = Input.GetAxis("Vertical") * 0.1f;
            transform.Translate(x, 0, z);
    
            if (Input.GetKeyDown(KeyCode.Space))
            {
                GameManager.singleton.SwitchPlayer(this);
                playerSwitched = true;
            }
        }
    
        public override void OnStartLocalPlayer()
        {
            if (name == "Player 2")
            {
                GetComponent<MeshRenderer>().material.color = Color.blue;
            }
            else
            { 
                GetComponent<MeshRenderer>().material.color = Color.red;
            }
    
            if (!player2Spawned)
            {
                player2Spawned = true;
                GameManager.singleton.SpawnPlayer2();
            }
        }
    }
    
[/code]

次にプレイヤーのSpawnを担当するGame Managerのスクリプトです:

[code]

    // GameManager.cs
    using UnityEngine;
    using UnityEngine.Networking;
    
    public class GameManager : MonoBehaviour
    {
        public static GameManager singleton;
        public GameObject playerPrefab;
    
        void Awake()
        {
            singleton = this;
        }
    
        public void SpawnPlayer2()
        {
            var player2 = Instantiate<GameObject>(playerPrefab);
            player2.name = "Player2";
            NetworkServer.Spawn(player2);
        }
    
        public void SwitchPlayer(PlayerMove oldPlayer)
        {
            Debug.Log("Switch Player");
            var conn = oldPlayer.connectionToClient;
            GameObject newPlayer;
            if (oldPlayer.name == "Player2")
            {
                newPlayer = GameObject.Find("Player1(Clone)");
            }
            else
            {
                newPlayer = GameObject.Find("Player2");
            }
            NetworkServer.ReplacePlayerForConnection(conn, newPlayer, 0);
        }
    }
    
[/code]

念のためこれらのスクリプトの動作を確認する手順を記します(質問された状況からの追加手順です):

  1. シーンのPlayerにアタッチ済みのPlayerMoveの内容を上記PlayerMove.csの内容に差し替えてます。
  2. シーン上にGame Managerという名前のGameObjectを追加して選択します。
  3. Game Managerのインスペクターが表示されたら、上記GameManager.csをドラッグアンドドロップします。
  4. Unity EditorのPlayボタンを押して、Game画面の「LAN Host」ボタンを押します。
  5. Game画面に赤いCubeが表示され、前後左右に操作できます。同時にデフォルト色のCubeも表示されていますが、こちらは動きません。
  6. 次にスペースキーを押すと先ほどのデフォルト色のCubeが青くなって、かつ前後左右に操作できるようになります。同時に赤いCubeは動かなくなります。

冒頭にも書きましたが、シーンに直接Player2を置かず、Player1と同様にPlayer2もサーバーにSpawnさせるところが肝だと思います。

Player2の生成をNetworkBeahvoiur.OnStartLocalPlayer()で行っている、Playerの切り替え時に同じフレームで複数のPlayerに対して操作が行えてしまうことがあるためフラグを使って対処している、などいろいろと筋の悪さはありますが、そのあたりは実際の実装ではうまいことやっていただければと思います。

参考:

  * [Switching between several player objects | Unity Community](http://forum.unity3d.com/threads/switching-between-several-player-objects.333649/)

2016-07-21追記:

ホストとクライアントの両方で、二つのCubeをスペースキーで切り替えながら操作できるようにしました。

まずはPlayerMoveから:

[code]

    // PlayerMove.cs
    using UnityEngine;
    using UnityEngine.Networking;
    using System.Linq;
    
    public class PlayerMove : NetworkBehaviour
    {
        [SyncVar]
        public string playerName;
    
        bool _switched;
    
        void Update()
        {
            if (!isLocalPlayer)
            {
                return;
            }
            if (_switched)
            {
                Debug.Log("Switched...");
                _switched = false;
                return;
            }
    
            var x = Input.GetAxis("Horizontal") * 0.1f;
            var z = Input.GetAxis("Vertical") * 0.1f;
            transform.Translate(x, 0, z);
    
            if (Input.GetKeyDown(KeyCode.Space))
            {
                var sc = isServer ? "s" : "c";
                string newPlayerName;
                if (playerName == "Player1" + sc)
                {
                    newPlayerName = "Player2" + sc;
                }
                else
                {
                    newPlayerName = "Player1" + sc;
                }
                CmdSwitchPlayer(newPlayerName);
                _switched = true;
            }
        }
    
        public override void OnStartLocalPlayer()
        {
            Debug.Log("OnStartLocalPlayer: " + gameObject.name);
            var sc = isServer ? "s" : "c";
            var newName = "Player1" + sc;
            var players = FindObjectsOfType<PlayerMove>();
            if (!players.Any(x => x.playerName == newName))
            {
                CmdSetPlayerName(newName);
                CmdSpawnPlayer("Player2" + sc);
            }
            else
            {
                CmdSetPlayerName(newName);
            }
            _switched = true;
        }
    
        [Command]
        void CmdSpawnPlayer(string name)
        {
            GameManager.singleton.SpawnPlayer(name);
        }
    
        [Command]
        void CmdSwitchPlayer(string name)
        {
            GameManager.singleton.SwitchPlayer(this, name, isServer);
        }
    
        [Command]
        void CmdSetPlayerName(string name)
        {
            if (string.IsNullOrEmpty(playerName))
            {
                playerName = name;
            }
        }
    }
    
[/code]

次にGameManager:

[code]

    // GameManager.cs
    using UnityEngine;
    using UnityEngine.Networking;
    using System.Linq;
    
    public class GameManager : NetworkBehaviour
    {
        public static GameManager singleton;
    
        public GameObject playerPrefab;
    
        void Awake()
        {
            singleton = this;
        }
    
        public void SpawnPlayer(string name)
        {
            Debug.Log("Spawn Player: " + name);
            var newPlayer = Instantiate<GameObject>(playerPrefab);
            var pm = newPlayer.GetComponent<PlayerMove>();
            pm.playerName = name;
            NetworkServer.Spawn(newPlayer);
        }
    
        public void SwitchPlayer(PlayerMove oldPlayer, string newPlayerName, bool isServer)
        {
            Debug.Log("Switch Player: " + oldPlayer.playerName + " -> " + newPlayerName);
            var conn = oldPlayer.connectionToClient;
            var pms = FindObjectsOfType<PlayerMove>().ToList();
            var pm = pms.FirstOrDefault(x => x.playerName == newPlayerName);
            NetworkServer.ReplacePlayerForConnection(conn, pm.gameObject, 0);
        }
    }
    
[/code]

今回のコードの要点は次の通りです:

  1. Player2の生成をサーバーで行うようにしました。前回はspawnをクライアントが直接行うことができたため、Command経由で生成するようにしました。
  2. サーバーでspawnされたGameObjectのnameはクライアントには反映されないようでしたので、別途名前比較用の変数(PlayerMove.playerName)を追加しました。
  3. ホストが扱うべきGameObjectとクライアントが扱うべきGameObjectを明確に区別しました。  
前回のGameManager.SwitchPlayer()では見つけたGameObjectがホストのものかクライアントのものか区別がつけられませんでしたので、今回は「Player1s(サーバーのPlayer1)」「Player2c(クライアントのPlayer2)」のようにわかりやすい名前をつけました。  
ただし、単純にplayerNameに名前を代入してもクライアントだけに反映されてホストでは空のままということがありましたので、名前の代入もCommandで行っています。

  4. Playerの切り替えもサーバーで行います。NetworkBehaviour.connectionToClientはサーバー用の変数のためかクライアントではnullでした。それが原因でクライアントではNetworkServer.ReplacePlayerForConnection()を使えません。前回行えたのはホストがサーバーとクライアントを兼ねていたためでした。
  5. NetworkBehaviour.OnStartLocalPlayer()はNetworkServer.ReplacePlayerForConnection()で切り替える度に呼び出されるようです。  
そのままではplayerNameへ不適切な名前が代入される恐れがあるため、名前の代入を複数回行われないように対処しています。

クライアントが複数になった場合には未対応など手付かずなところはありますが、こんな感じでいかがでしょうか。


CC BY-SA 3.0
2016-07-17T06:50:18.603
27681
2016-07-20T21:00:01.883
2016-07-20T21:00:01.883
3783
3783
27639
answer