Oculus Touchで対戦じゃんけんする方法 後編  面白法人カヤック VR部ラボ 第3回

こんにちは!面白法人カヤックのVR部です。

VRでじゃんけんの後編です!今回は前回よりテクニカルな内容になっております。

今回のソースコード

こちらのプロジェクトをcloneするか、downloadしてください。

本プロジェクトには

Oculus Utilities for Unity 5

及び

・Oculus Touch SDK

が必要ですが、上記のプロジェクトには同梱していないため別途ご準備ください。

Oculus Touch SDKは現在一般に公開されておりませんが、Oculus Touch発売後には公式サイトで公開されると思われます。

公開され次第リンクを追記しますが、Touchをお持ちでない方は今しばらくはソースを眺めるのみにとどめておいてください。

また、開発した環境はUnity5.4.1f1ですが、5.4系であればおおむねね問題なく動くようです。

Oculus Touchでマルチプレイをするには 〜UNETの基本的な使い方〜

さて、前回しれっとUNETを使って通信させてましたが、今回はこれをどうやって実装しているかを詳しく書いていきます。

なお、本記事中のリンクは、Unityの公式ドキュメントやgithubで参考となるコードに繋がっています。

そもそもUNETとは

UNETとは、Unity5から提供されているマルチプレイのためのフレームワークです。

Unity マニュアル – マルチプレイヤーとネットワーキング

高レベルAPI(HLAPI)といって、マルチプレイに必要なさまざまな要素を使いやすくまとめていて、UnityEngine.Networkingの名前空間に色々と用意されています。

では、基本概念から見ていきましょう。

サーバー、クライアント、ホストについて

ネットワーク通信の時に気にしなくてはいけないことは、接続しているプレイヤー同士の関係です。

まず、サーバーとクライアント、ホストについて。

UNETにおいては、基本的に1つのサーバーに複数のクライアントを持ちます。

しかし、専用のサーバーがない場合にはいずれかのクライアントがサーバーを兼ねることもできるようになっています。このクライアントのことをホストと呼びます。

今回は、専用のサーバーを立てないサンプルとなりますので、片方のクライアントがホストの役割をすることになります。

Player Authorityについて

例えばプレイヤーキャラが1人いたとして、プレイヤーの操作者からはオリジナルのキャラクターとして扱うことができますが、他のプレイヤーからはどう扱われるでしょうか。

答えは、「オリジナルのキャラクターのクローン」として他プレイヤーからは扱われることになります。

しかし、元になるprefabは全く同じものということになります。ややこしいですね。

ここがややこしくならないように、この時オリジナルのプレイヤーキャラクターに「Authorityがある状態」、コピーされた側に「Authorityがない状態」というようにフラグが設定できます。

基本コンポーネント「NetworkBehaivior」「NetworkIdentity」

このように、ネットワーク上で共有されつつ、お互いに同一個体として認識できるようにするための基本機能を提供してくれるのが「NetworkBehaivior」および「NetworkIdentity」コンポーネントです。

「NetworkIdentity」はネットワーク上で共有したいオブジェクトにはかならず付与する必要があるコンポーネントです。その名の通り、ネットワーク上でのアイデンティティを保持してくれるということですね。

このコンポーネントの「Local Player Authority」にチェックを入れると、プレイヤーオリジナルの個体のみAuthorityのフラグを立ててくれるようになります。

次にNetworkBehaivior。このクラスを継承することで、ネットワーク上で共有した際の色々な機能を使えるようになります。

前述のAuthorityもここで利用することができて、Authorityを所有している場合は「isPlayerAuthority」のフラグがtrueになります。

こうすることでどんなメリットがあるかというと、例えば「このオブジェクトはプレイヤーの入力を直接反映したい」という時に両方共通したコードとしてこれを書いてしまうとそれぞれのプレイヤーの入力を反映してしまい、同じ空間にいて同じオブジェクトなのに矛盾した動きになってしまいます。

そんな時にこのisAuthorityのフラグを使うと、isAuthority = trueの時だけプレイヤーの入力を直接反映、そうでない時は共有された動きのデータに従うよ、というコードを書けば矛盾のない動きをすることになります。

UNETを実際にどのように使っているか

さて、概念の把握も大事ですが、せっかく実例があるのでこれを見ながら解説していきましょう。

今回のじゃんけんサンプルは、ごくごく簡単に

・1対1で

・一方がホスト、一方はクライアント

・ローカルエリアネットワーク、IP指定で接続

・ただじゃんけんができて、プレイヤーの動きがそれぞれで同期するだけ

という仕様に絞ります。

ではまず、プレイヤーキャラクターの同期から見ていきましょう。

キャラクターを構成するオブジェクト

まず、共有するべき「キャラクターを構成するオブジェクト」ですが、今回はこのような感じです。

NetworkManagerを継承したクラスと、同期オブジェクトの登録

まず、NetworkManagerを継承したコンポーネント「NetMan」を準備します。NetworkManagerは、マルチプレイヤーゲームの制作・実行・デバッグをできる限り簡単に行えるように設計されている非常に頼れるコンポーネントです。今回ももちろん使用します。

同期したいオブジェクトがある場合、まずこのNetworkManagerのSpawn Infoに登録する必要があるため、あらかじめ登録します。

オブジェクトの生成

ネットワーク上で共有したいオブジェクトがあったとして、それを通常の生成、つまりシーンへの配置やInstantiateしても共有されることはありません。

ネットワーク上で共有したい時には、「NetworkServer.Spawn(GameObject obj)」というメソッドを使います。これを使って、頭と両手のオブジェクトを生成します。

まず、プレイヤーオブジェクトとして「Player Prefab」を設定します。こちらは画像にある通り、Characterコンポーネントを持ったCharacterプレハブが設定されています。

Characterオブジェクトは、NetManコンポーネントによって生成されます。

さらに、Characterコンポーネントのコードを見るとわかる通り、「頭と両手の生成」はCharacterコンポーネントのOnStartLocalPlayerというメソッドで実行されています。

OnStartLocalPlayerとは、NetworkBehaviorがプレイヤーオブジェクトであり、かつAuthorityを所有している時に最初に呼ばれるメソッドです。

位置と回転の同期

次に、位置と回転の同期。UNETにはもともとNetworkTransformという位置・回転の同期のためのコンポーネントがありますが、遅延がダイレクトに感じられてしまう実装になっているようで、特にVRだとかなり気になってしまうのであまり向きません。よって、ここはNetworkBehaviorを継承したクラス「SyncTrasnformLerp」を作成し、スムーズに位置回転同期するように実装します。

アニメーションの同期

手と頭の同期はTransformでしたが、じゃんけんの手はAnimationを使う必要があります。

Animationを同期するための「NetworkAnimator」というコンポーネントが用意されているため、こちらを利用します。

PlayerAuthorityによる処理の分岐

さて、前述したように、プレイヤーキャラクターなのでAuthorityと非Authorityで実装をわける必要があります。これは、プレイヤーの入力を扱うクラスでは、Authorityがある場合にのみ初期化を実行し(たとえば頭の位置回転を制御するNetworkHead)、前述のSyncTrasnformLerpコンポーネントも付与することによって、Authorityがない場合にはSyncTransformLerpからの座標情報に従う、という実装の分け方になっています。

ここまでやると、プレイヤーキャラクターを同期することができます。

UNETを使ってどのようにしてじゃんけんを判定させているか

さて、次にじゃんけん判定です。

じゃんけんを判定しているオブジェクトは「JankenUI」で、主に判定の処理は「JankenJudge」に書かれています。これもネットワーク上で共有するオブジェクトになるので、頭や手と同じくNetworkIdentityをつけてNetworkManagerに登録します。

じゃんけんはいつ始まるのか

これは決めの問題ではありますが、まずなるべく混乱しないように

・触れ合った2つの手同士がじゃんけんをする

ということにします。

よって、じゃんけんの手の側がOnTriggerEnterのイベントが起こった時に相手が他の手だったら、このJankenJudgeにアクセスしてじゃんけん開始をさせます。

手がAuthorityであってもなくても平等に処理

一方JankenJudgeは、じゃんけんスタートのメソッドが叩かれるとそれぞれの手から「今何の手をだしているか」を取得し、勝ち負けの判定をします。

この時、手の方はAuthorityの場合はユーザーの入力値をそのまま使いつつ、AuthorityでないClientのためにSyncVarという属性がついている変数にじゃんけんの状態を送り続けています。

SyncVar属性はUNETの機能の1つで、変数にこの属性をつけると全クライアントで同じ値の状態を共有してくれます。ただし、サーバー発である場合は値を代入するだけでクライアントに同期しますが、クライアント発の場合は[Command]という属性をつけたメソッドで代入をする必要があります。

このようにすることで、JunkenJudge側は対戦する手がAuthorityであろうとなかろうと変わらずに判定することができます。

判定に矛盾が出ないようにするために

じゃんけんの判定で注意したいのは、「接続しているのは2人だが、判定は一意にする必要がある」ということです。

よって、今回は

・必ずホストのクライアントで結果判定し、もう片方のクライアントは共有された結果を見るのみ

というかたちにします。

今回は、簡単に「クライアントの場合は、接続先ホストのIP情報がある(ホストの場合はない)」という前提条件にしているため、手っ取り早くファイル読込のクラス「FileUtils」から「ホストであるかどうか」を半手にできるようにしております。

「ホストの場合のみ」判定オブジェクトを生成する

じゃんけん判定オブジェクト「JankenJudge」ですが、こちらも手や頭と同じく「Character」クラスにて生成しています。生成時にFileUtilsのメソッド、GetPlayerTypeがHOSTであることを条件に挟むことで、ホストだけが生成するように限定させています。

「ホストの場合のみ」判定処理を行う

また、判定の処理もisHost = trueの場合のみ行いますが、

結果の表示状態を全て「jankenNet」というオブジェクトの値に代入しています。

jankenNetはNetworkBehaviorを継承したコンポーネントで、代入されている変数には必ずSyncVar属性がついています。

HostでないClientである場合は、このjankenNetの変数を常に取得し、表示状態に反映します。

このように実装すると、Oculus Touchで対戦じゃんけんができるようになります。

https://www.youtube.com/watch?v=yuZXudp-fKg

いかがでしたでしょうか?

かなり駆け足でしたが、大事なところは把握していただけたのではないかと思います。

より深くUNETについて知りたい方は、Unity マニュアル – マルチプレイヤーとネットワーキングを熟読することをおすすめいたします。

次回は、この通信機能を使って遊びのバリエーションを増やしていきます! お楽しみに。

カヤックのVR部では Podcast の配信も行っております。

Podcast では、VRに対する情熱や考え方など、ゆるく広く話しておりますので、こちらも併せてお楽しみください。

KAYAC VR部 (SoundCloud)

Podcast用Feed (iTunesなどにドラッグ)

カヤックVR部の連載バックナンバーはこちら

この記事を書いた人

  • q5t4zowd

    面白法人カヤックの中でVRコンテンツ制作をしているチーム。みんな通勤が嫌いなので早くVRの中で仕事がしたいと思っています。

    Twitter:@kayac_vr