UnityNetworkでオンライン対応3-キャラクターの同期と自分用カメラの設定-

今回はUnityNetworkで自身のキャラクターのみ動かし、他のプレイヤーキャラクターを動かさないようにする処理と、キャラクターの位置やアニメーションの同期を行ってみたいと思います。

前回はオンライン上にキャラクターを登場させるところまでをやりました。

UnityNetworkでプレイヤーキャラクターをオンライン上に登場させるところまでを作成していきます。
スポンサーリンク

自身のキャラクターのみ動かし、他のキャラクターを動かさない

前回、オンラインに接続した時に自身のキャラクターを登場させる事が出来ましたが、自身のアプリケーションに存在する他のプレイヤーのキャラクターも一緒に動いてしまいました。

そこで、今回は自身のキャラクターのみ動かす事が出来るようにします。

キャラクターが自分のものかどうかを判別する為に、MonoBehaviourクラスを継承して作成したUCharaスクリプトをNetworkBehaviourクラスに変更し、

NetworkBehaviourのisLocalPlayerプロパティを参照し、自身のキャラクターかどうかを判別します。

↑のスクリプトは変更部分を記載しています。

NetworkBehaviourを使用する為、using UnityEngine.Networkingディレクティブを追記します。

Updateメソッド内では移動処理や攻撃処理が記述してありますので、自身のキャラクター以外だった時に実行しないようにすれば移動や攻撃処理を行いません。

そこでUpdateメソッドの初めにisLocalPlayerで自身のキャラクターではない時にreturnを使って処理を実行しないようにします。

試しにUnityEditor上で実行し、ホストで接続して作成したキャラクターと、スタンドアロンで実行しクライアントで接続し生成したキャラの違いを見てみます。

自身のキャラと他のキャラの違い

左がUnityEditorで実行し生成したキャラで、右がスタンドアロンで実行しクライアントで接続した時に作成したキャラで、UnityEditor上に登場したプレハブになります。

左側は自身のキャラなのでHas AuthorityとIs Local Playerがyesになっているのに対して、右側は他のプレイヤーキャラなのでnoとなっています。

キャラクター位置とアニメーションの同期

自身のキャラクターのみを操作する事が出来るようになりましたが、他のプレイヤーが操作しているアプリケーション上の自分のキャラクターは動いたり攻撃したりをしません。

これは自分の動かしたキャラクターの位置やアニメーションを他のプレイヤーのアプリケーションと同期していない為です。

そこで、位置やアニメーションをオンライン上で同期し、他のプレイヤーのアプリでも自身のキャラクターが動くようにしましょう。

キャラクター位置の同期

キャラクターの位置を同期するにはキャラクターにNetworkTransformコンポーネントを取り付けます。

Add Component→Network→NetworkTransformを選択し、取り付けます。

キャラクターにNetworkTransformを取り付ける

Transform Sync ModeをCharacter Controllerに変更します。

これで位置の同期が可能となりました。

NetworkTransformで位置を同期したサンプル

↑のようにキャラクターの位置が他のアプリケーションでも同期されるようになりました。

キャラクターアニメーションの同期

キャラクターの位置を同期する事は出来ましたが、スーッとお化けのように動いていますね。

これは位置はNetworkTransformで同期しているけれど、アニメーションの同期を行っていないからです。

そこでキャラクターのアニメーションも同期するようにしていきましょう。

アニメーションの同期にはNetworkAnimatorを使用します。

Add Component→Network→NetworkAnimatorを選択し取り付けます。

NetworkAnimatorを取り付けたら、同期するAnimatorをNetworkAnimatorにドラッグ&ドロップします。

NetworkAnimatorを取り付ける

すると↑のように同期するアニメーションパラメータが表示されるのでSpeedとAttackの両方のチェックを入れておきます。

このチェックボックスは同期を行うかどうかではなく、自動でアニメーションパラメータのデータを送るかどうかみたいです。

ネットワークオブジェクトの Macanim によるアニメーション状態を同期するコンポーネント

↑のSetParameterAutoSendを使った処理でtrueを指定したのと同じですね。

NetworkAnimatorのチェックをする事で同期するかしないかだと勝手に思ってましたが、違うみたいですね・・・(^_^;)

ここで、試しにビルドし二つのアプリケーションを起動し実行してみてください。

すると歩行アニメーションは再生されるけど、攻撃アニメーションが再生されないと思います。

NetworkAnimatorではTrigger型のアニメーションパラメータを反映するにはAnimatorではなくNetworkAnimatorのSetTriggerを使ってトリガーする必要があるみたいです。

そこでUCharaスクリプトのAnimator部分を変更します。

Animator型のanimatorとしていた箇所をNetworkAnimatorのnetAnimと宣言を変更します。

アニメーションパラメータを操作している箇所で、Trigger以外を操作している箇所をNetworkAnimatorを介したAnimatorの操作に変更し、Triggerを操作している部分はNetworkAnimatorのSetTriggerを使った処理に変更します。

これでアニメーションの同期が行われますが、実は一つだけ問題が残ります。

ホストになったらアニメーションが2回再生される

Triggerを使う場合はNetworkAnimatorのSetTriggerを使用すればアニメーションの同期はされますが、実はホストであるプレイヤーキャラクターは攻撃アニメーションが2回再生されてしまいます。

クライアントで接続した場合はちゃんと1回だけの再生なんですが・・・(^_^;)

ここら辺の問題はUnityのフォーラムにも記載がありました。

I'm a bit confused about the functionality and purpose between the Animator component and the NetworkAnimator. From my understanding since the debut of...

そこでサーバーとして稼働していた場合はResetTriggerメソッドを使用してトリガーをキャンセルするようにします。

最初にNetworkAnimatorのSetTriggerでAttackをトリガーして、NetworkBehaviourのisServerプロパティでサーバーであればNetworkAnimatorを介してAnimatorのAttackトリガーをリセットします。

こうすることで、ホスト(サーバー兼クライアント)でも攻撃アニメーションの再生が1回になりました。

どういう挙動で2回アニメーションが再生されるのかはわかりませんが・・・・、とりあえず問題がなければこのままいきます。

自分用カメラの設定

現状ではゲームの世界を写すカメラは固定カメラのみがそれぞれのアプリケーションに存在しています。

ですが、キャラクターが移動したら自分専用のカメラが自分のキャラクターを追いかけてくるようにしたいところです。

そこで自分のキャラクターがオンラインに接続した時にカメラが自分を追いかけるようにします。

まずは元々存在するMainCameraのインスペクタのAdd ComponentからScripts→UnityStandardAssets.Utility→Follow Targetを選択し取り付けます。

Follow Targetには追いかける対象を設定しないといけませんが、自身のキャラクターを設定しないといけない為、スクリプトからターゲットを指定する事にします。

UnityNetworkのメインカメラにFollowTargetを取り付ける

設定は↑のような感じにしました。

ターゲットをスクリプトから設定

UCharaスクリプトで自身のキャラクターが登場した時にFollow TargetのTargetに自身のTransformを指定するようにします。

自身のキャラクターがオンラインに登場した時はOnStartLocalPlayerメソッドが呼ばれるので、その中に処理を記述します。

↑のような処理をUCharaスクリプトに追記します。

こうすることで、自身のキャラクターを追いかけるカメラの設定が出来ます。

動作確認

これでキャラクターの位置とアニメーションの同期が出来るようになりましたので、確認してみましょう。

↑のようになりました。

キャラクターの位置とアニメーションの同期、カメラが自身のキャラクターを追いかけるようになりました。

次回はキャラクターのHP表示機能とプレイヤー同士で戦えるようにしていきます。

参考サイト

Unityチュートリアループレイヤーの動きをネットワーク化するー

スポンサーリンク

記事をシェアして頂ける方はこちら

フォローして頂くとやる気が出ます

コメント

  1. あい より:

    質問です。
    カメラをHierarchy上に配置するのではなく、プレファブであるプレイヤーキャラクターのオブジェクトの子要素にさせて追従させてた場合、カメラを分ける方法ってありますか?
    どうしても片方の画面でカメラを動かすともう片方の画面でも動いてしまいます。
    自分用のカメラを設定するには、カメラをHierarchy上に配置するしかないのでしょうか?

    • カメラを全キャラクターで共有している現象ですね。

      以前PhotonCloudでのオンライン対応の記事にコメントを頂いたのですが、その時と同じような対処で出来ます。

      https://gametukurikata.com/online/multiplay/multiplay4

      ヒエラルキー上のMainCameraをキャラクターの子要素に配置しプレハブとした場合は、

      MainCameraのインスペクタで名前の横のチェックを外し無効化した状態にしておきます。

      この記事の中でカメラのターゲットを設定しているOnStartLocalPlayerメソッドはローカルプレイヤーの時だけ呼ばれるので、その中で

      ↑のように自身の子要素からMain Cameraの名前で検索し、そのゲームオブジェクトをアクティブにします。

      こうすることで自身のキャラクターの子要素のカメラ以外は無効のままで、自身のキャラクターのカメラだけ有効に出来ると思います。

      • あい より:

        できました!ずっとここで詰まってたのですごく助かりました!
        どうもありがとうございました!

      • あい より:

        もう1つ質問をさせてください。
        ゲームを実行して同期した際に、Hierarchy上でホスト側のキャラクターオブジェクトとクライアント側のキャラクターオブジェクトをそれぞれ見てみると、
        ホスト側キャラクターオブジェクトの子要素であるMainCameraはアクティブ状態になっているのですが、クライアント側のキャラクターオブジェクトの子要素のMainCameraは非アクティブ状態になっています。
        これは実際にクライアント側のMainCameraは非アクティブ状態なのでしょうか?それともアクティブ状態なのに反映されてないだけなのでしょうか?

        • 正しいかどうかはわかりませんが、スクリプトに作成したOnStartLocalPlayerメソッドはローカルプレイヤー、つまり自身のアプリケーション上でインスタンス化したゲームオブジェクト(この場合はキャラクター)で呼ばれるので、その時にカメラをアクティブにしています。

          自身のアプリケーション上に登場した他のプレイヤーが操作しているキャラクターの子要素にもカメラは設置されていますが、自身がインスタンス化したプレイヤーキャラクターではない為、カメラは非アクティブな状態のままです。

          自身のアプリケーション上には他のプレイヤーがインスタンス化したプレイヤーキャラクターを登場させる必要がある為、ヒエラルキー上にはキャラ操作スクリプトやAnimator等同じ機能を持ったキャラクターが登場します。

          (元々同じプレハブからインスタンス化している為)

          ここで重要になるのが、自身のキャラクターか他のプレイヤーのキャラクターかどうかです。

          ここの区別がないとしたら、自身のキャラクターを動かすつもりでも、他のプレイヤーのキャラクターも同じ移動スクリプト等の機能を持っているので同じように他のキャラクターも動いてしまいます(自身のアプリケーション上では)。

          そこで必要になるのが自身のキャラクター以外の移動処理をしないようにしたり、カメラを無効にしたりといった事になります。

          PhotonCloudのオンライン対応の記事の「自分のキャラクター以外は動かさないようにする」の項目でも同じような事をしています、

          https://gametukurikata.com/online/multiplay/multiplay4

          同一のプレハブを自身も他のプレイヤーも使用する事になる為、元々ある機能を有効にしたり無効にしたりして他のプレイヤーキャラクターを操作出来ないようにします。

          なので、UnityEditor上に表示されたホスト(ローカルクライアント)のキャラクターのMainCameraはアクティブ、クライアント側(リモートクライアント)のキャラクターのMainCameraは非アクティブで表示されています。

          その為、答えとしてはホスト側(ヒエラルキー上)に表示されるクライアントのキャラクターは他のプレイヤーのキャラクター(を自身のアプリケーション上で表示する為に登場させただけ、分身)なのでMainCameraは非アクティブな状態のままとなります。

          実際のクライアントのアプリケーションではホストのプレイヤーのMainCameraは非アクティブ、クライアントのプレイヤーのMainCameraはアクティブ、

          ホスト側のアプリケーションではホストのプレイヤーのMainCameraはアクティブ、クライアントのプレイヤーのMainCameraは非アクティブ、

          という感じです。

          スタンドアロンの方をホスト、UnityEditor上でクライアント接続をするとクライアントのキャラクターのMainCameraがアクティブになっているのではないでしょうか。

          ここら辺は混乱しますね・・・(^_^;)

          • あい より:

            なるほど、自身のアプリケーション上にある相手キャラクターのカメラを非アクティブにすることでカメラを分けるということですか…。
            すごい…こんな方法があったんですね!スッキリしました。
            ご丁寧に説明いただき、ありがとうございました

  2. てん より:

    このような仕組みになっていたんですね!解説が分かりやすくよく理解できました!
    これからは同じエラーが出た時は名前空間を確認しようと思います。
    回答ありがとうございましたm(__)m

  3. てん より:

    ターゲットをスクリプトから設定の所の
    public override void OnStartLocalPlayer() {
    Camera.main.GetComponent ().target = transform;
    }
    という部分で、「The type or namespace name FollowTarget' could not be found. Are you missingUnityStandardAssets.Utility’ using directive?」とエラーが表示されFollowTargetを取得できません。
    HierarchyタブにはMainCameraが配置されていて、FollowTargetも付いています。
    原因が分かれば解説お願いしますm(_ _)m

    • エラーの内容をGoogleで翻訳すると

      「FollowTargetが見つからないのでUnityStandardAssets.Utilityのusingディレクティブを間違っていませんか?」と言われています。

      usingディレクティブとは何かと言うと、新しいスクリプトを作成すると上の方にすでに書かれているやつです。

      例えば上の例ではMonoBehaviourクラスを継承してTestクラスを作成していますが、このMonoBehaviourクラスはUnityEngineという名前空間に定義されています。

      そこでusingディレクティブでUnityEngineを指定する事でMonoBehaviourだけの記述で済みます。

      例えばUnityEngineのusingディレクティブを無くすと

      上のようにUnityEngineから毎回記述する事になります。

      usingディレクティブを記述しておくとそれを記述しなくても良くなります。

      またFollowTargetスクリプトはスタンダードアセットにもありますがUnityを使うユーザーがFollowTargetという同じ名前のスクリプトを作りたい、または作ってしまう事もあります。

      (他の人が作ったスクリプトを使おうと思った時に同じ名前のスクリプトがある可能性もあります)。

      その為、同じスクリプトの名前でも別物として扱えないとどのFollowTargetスクリプトなのかがわかりません。

      そこで使用するのがnamespace(名前空間)でスタンダードアセットのFollowTargetはUnityStandardAssets.Utilityという名前空間に定義されています。

      その為スタンダードアセットのFollowTargetを使用する場合は

      ↑のようにusingディレクティブを記述しておきます。

      この名前空間は自分で作る事も出来るので、Kamekumechan名前空間にNamespaceTestクラスを定義する場合は、

      ↑のようにnamespaceの中括弧で囲います。

      このクラスを使用する時は

      ↑のようにusingディレクティブでKamekumechanという名前空間を指定します。

      スタンダードアセットのFollowTargetを見てみると、

      ↑のようにUnityStandardAssets.Utilityという名前空間に設定されている事がわかります。

      名前空間自体も名前が被ってしまう可能性もありますが、C#ではどうやって分けているかわかりませんが、わたくしがJavaの本で見たパッケージの分け方と同じように自分の運営しているドメイン、例えばわたくしの場合は

      gametukurikata.com

      なのでそれを逆にして、

      のようにするとドメイン以降で名前を分け、名前空間の被りがなくなるかもしれません。