今回はユーザーがサーバーに接続したら操作キャラクターをゲームの世界に登場させます。
前回でPhoton Cloudのサーバーに接続する事が出来るようになりました。
![](https://gametukurikata.com/wp-content/uploads/2017/03/eyecatchrealtime3.png)
登場させるキャラクターは
![](https://gametukurikata.com/wp-content/uploads/2017/03/eyecatchrealtime1.png)
で作成したEthanになります。
EthanにPhotonViewを取りつけプレハブにする
マルチプレイ1の記事で作成したEthanはあらかじめヒエラルキー上に配置していました。
しかしネットワーク対応する場合は自分のゲーム世界ではなくネットワーク上にキャラクターを登場させなくてはいけません。
ネットワーク上にキャラクターを登場させる為にはキャラクターにPhotonViewコンポーネントを取りつけます。
Add Component→Photon Networking→PhotonViewを選択します。
PhotonViewを取りつけたらEthanをProjectタブのAssetsフォルダにPrefabsというフォルダを作成し、さらにその中にResourcesという特殊フォルダを作成しその中にドラッグ&ドロップしてプレハブにします。
敵キャラクターもPhotonViewを取りつけ同じようにPrefabs/Resourcesフォルダの中にドラッグ&ドロップしプレハブ化しておきます。
Resourcesフォルダに入れる理由はネットワーク上にキャラクターをインスタンス化する時にResourcesフォルダにプレハブを入れる必要があるからです。
プレハブにしたEthanをネットワーク上でインスタンス化
プレハブにしたEthanをネットワークに登場させましょう。
前回ネットワークに接続する事が出来ましたが、部屋に入室したと同時にネットワーク上にキャラクターをインスタンス化します。
NetworkManagerスクリプトに処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // プレイヤーのインスタンス private GameObject player; // 部屋に入室した時に呼ばれるメソッド public override void OnJoinedRoom() { loginUI.SetActive(false); logoutButton.SetActive(true); Debug.Log("入室"); // InputFieldに入力した名前を設定 PhotonNetwork.player.NickName = playerName.text; // プレイヤーキャラクターを登場させる StartCoroutine("SetPlayer", 0f); } // プレイヤーをゲームの世界に出現させる IEnumerator SetPlayer(float time) { yield return new WaitForSeconds(time); // ネットワークごしにEthanをインスタンス化する player = PhotonNetwork.Instantiate("Ethan", Vector3.up, Quaternion.identity, 0); } |
OnJoinedRoom内でコルーチンをスタートさせます。
SetPlayerメソッドではPhotonNetwork.Instantiateを使ってネットワーク上にキャラクターをインスタンス化します。
PhotonNetwork.Instantiateで指定するプレハブは文字列で指定し、EthanはResourcesフォルダの直下に入れておく必要があります。
これでプレイヤーが入室した時にゲームの世界に操作キャラクターが登場する事になります。
ネットワーク上にキャラクターが登場するか確認
それでは確認してみましょう。
↑のようにアプリケーションを2つ起動した後、ログインするとEthanが登場しました。
ログインした方のキャラクターをキーボードの移動キーで動かし移動させた後、もう一方の方も同じ部屋にログインします。
最初にログインしたアプリケーションの方に別のキャラクターが登場しました!
これでマルチプレイが出来る!と単純にいけばいいんですが、最初のアプリケーションの方でキャラクターを動かすと後から登場した別のプレイヤーキャラクターも一緒に動いてしまいます。
これは自身のアプリケーションに他のキャラクターは登場したんですが、このキャラクターも自身のキャラクターと同じように移動させるCharaスクリプト等が設定されている為です。
また最初のアプリケーションでキャラを動かした後に後で起動したアプリケーションの方を見るとキャラクターが動いていません。
これは他のキャラクターのデータが自身のアプリケーションにいる他のキャラクターにシンクロしてデータを渡していないからです。
自分のキャラクター以外は動かさないようにする
自身のキャラクターと同様、他のプレイヤーのキャラクターもCharaスクリプトを持っているので一緒に動いてしまいます。
そこで自身のキャラクター以外は操作出来ないようにキャラクター操作スクリプト等を無効化させます。
EthanにNetworkPlayerCheckというスクリプトを作成し取りつけます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | using UnityEngine; using System.Collections; using ExitGames.Client.Photon; public class NetworkPlayerCheck : Photon.PunBehaviour { // Use this for initialization void Start () { // 自分で操作する以外のキャラクターの不要な機能は使えないようにしておく if (!photonView.isMine) { GetComponent<Chara> ().enabled = false; GetComponent<ProcessAttack>().enabled = false; GetComponentInChildren<Attack>().enabled = false; } } } |
NetworkPlayerCheckはPhoton.PunBehaviourを継承して作成します。
photonView.isMineでPhotonViewが自身のものかどうかを判定し、違う場合はCharaスクリプト、ProcessAttack、Attackなど自分のキャラクター以外の操作はいらないので無効化しておきます。
これでキャラクターを操作した時に他のキャラクターが一緒に動く事はなくなります。
他のプレイヤーのデータをシンクロさせる
他のプレイヤーがキャラクターを動かした時に自分のアプリケーション内にいる他のキャラクターが動くようにします。
これをするにはキャラクターに取り付けたPhotonViewのObserved Componentsにシンクロ(同期)させるコンポーネントを設定します。
とりあえず移動値だけを反映させてみましょう。
EthanのPhotonViewのObserved Componentsに自身のTransformをドラッグ&ドロップします。
↑のように設定したらEthanのインスペクタのApplyを押しプレハブに反映し、Ethanを非表示にしておきます。
他キャラクターの移動値がシンクロするか確認する
それでは2つアプリケーションを起動しお互いのキャラクターを操作した時に相手方にいる自分のキャラクターが動くかどうか確認しましょう。
自身のキャラクターを動かすと相手方のアプリケーションでもキャラクターが移動しているのが確認出来ます。
しかし今回Observed Componentsに設定したのはTransformコンポーネントだけなので同期するのは移動値だけです。
その為、攻撃したり歩くアニメーションになったりということは同期されていません。
他キャラクターの移動値やアニメーションをスクリプトで同期させる
Observed Componentsに指定出来るのはコンポーネントなのでスクリプトも同期させる事が出来ます。
そこでキャラクターの移動値やアニメーションのフラグ等をスクリプトで同期させるようにします。
NetworkPlayerCheckスクリプトに追記していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class NetworkPlayerCheck : Photon.PunBehaviour { // 同期するデータ private Vector3 position; private Quaternion rotation; private float hp; private Animator animator; private float speed; private bool attack; // スムーズ度合い [SerializeField] private float smooth = 10f; // Use this for initialization void Start() { animator = GetComponent<Animator>(); // 自分で操作する以外のキャラクターの不要な機能は使えないようにしておく if (!photonView.isMine) { GetComponent<Chara>().enabled = false; GetComponent<ProcessAttack>().enabled = false; GetComponentInChildren<Attack>().enabled = false; // 初期値を設定 position = transform.position; rotation = transform.rotation; hp = GetComponent<Status>().GetHp(); speed = animator.GetFloat("Speed"); attack = animator.GetBool("Attack"); // 自分のキャラクター以外のデータを同期 StartCoroutine("UpdateMove"); } } // 自分のキャラクター以外のデータの同期 IEnumerator UpdateMove() { while (true) { // スムーズに補間させる transform.position = Vector3.Lerp(transform.position, position, Time.deltaTime * smooth); transform.rotation = Quaternion.Lerp(transform.rotation, rotation, Time.deltaTime * smooth); animator.SetFloat("Speed", speed); animator.SetBool("Attack", attack); yield return null; } } // Observed Componentsに設定したスクリプトで呼ばれるメソッド void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { // データの読み込み if (stream.isReading) { position = (Vector3)stream.ReceiveNext(); rotation = (Quaternion)stream.ReceiveNext(); hp = (float)stream.ReceiveNext(); speed = (float)stream.ReceiveNext(); attack = (bool)stream.ReceiveNext(); // データの書き込み } else { stream.SendNext(transform.position); stream.SendNext(transform.rotation); stream.SendNext(hp); stream.SendNext(animator.GetFloat("Speed")); stream.SendNext(animator.GetBool("Attack")); } } } |
Startメソッド内で自身のキャラクターでなかったら(他のプレイヤーキャラを自身のアプリ上に登場させたキャラ)コルーチンを使ってUpdateMoveメソッドを呼びます。
キャラクターの位置や回転を現在の値からデータとして受け取った値にLerpを使って徐々に変更します。
Lerpを使う事で通信の度に断続的なデータで更新してカクカク動かず滑らかに動いているように見えます。
またアニメーションの遷移条件も受け取りアニメーション遷移もさせるようにします。
Observed Componentに設定したスクリプトではOnPhotonSerializeViewメソッドが呼ばれます。
データが読み込みであればそのstream.ReceiveNextでデータを受け取りデータのキャストをした後に変数に入れています。
データの書き込みであれば現在の位置や回転、アニメーションの操作値をSendNextを使ってデータとして送ります。
読み込みと書き込みのデータの順番は統一しておく必要があります。
これでデータの同期も出来ました。
PhotonViewのObserved Componentsには自信(Ethan)のTransformを設定していましたが、それを削除し、今作成したNetworkPlayerCheckスクリプトを設定します。
他のキャラクターが同期されるか確認する
アプリケーションを2つ実行し、同じ部屋に入室させてください。
片方のキャラクターを移動させたり攻撃ボタンを押すともう一方のアプリケーションで他のキャラクターが同期して動いているのが確認出来ると思います。
あ・・・すでにキャラクター名が表示されてしまってますが、これは次回やる予定なので無視してください。
終わりに
いやぁとうとう他のキャラクターの同期まで出来ましたね!
こんなに簡単にマルチプレイが実現出来るようになってるんですねぇ、すごいです。
と言ってもやっぱり難しいですが・・・・(^_^;)
慣れれば当たり前になっていくんでしょうね・・・・・・(-_-)
次回はプレイヤー毎に名前を表示したり、敵を攻撃して他のキャラクターと敵の情報を共有させていきます。