SDユニティちゃん オンラインが出来るまで -Photon編-

■はじめに

この記事は「【アルファ版】SDユニティちゃん オンライン」の制作過程を示したブログです。
http://unitygameuploader.jpn.org/game/3189.html

■お断り

この記事は現時点で判明している内容を元に書いています。勘違いしていたり、もっと良い方法があるかもしれません。その場合はtwitterか下のコメント欄でお知らせください。

この記事は「SDユニティちゃん オンラインが出来るまで -Unity編-」からの続きです。

■Photonとは

説明はWebサイトでw
http://photoncloud.jp/

ざっくり説明すると自前でサーバを設置しなくても小規模から商用ベースの製品までのオンライン対戦ゲームを作成することができるPaaSです。

■Photon xxxについて

Photonと名前の付いたものが色々とあります。公式な説明はこちらです。

http://doc.exitgames.com/en/realtime/current/getting-started/onpremise-or-saas

Unityから使うならPUN(Photon Unity Networking)がよいでしょう。PUNはPhoton RealtimeとAPIの互換性があります。実際のパッケージ名はPUN+とPhoton Unity Networking Freeの2種類です。私の理解はこんな感じです(違っていたら修正します)。

  • PUN +(有償版)または Photon Unity Networking Free(フリー版)
    • Photon Realtime(オンライン対戦の部分)
    • Photon Chat(チャット機能)

つまりPUN+あるいはPhoton Unity Netowork Freeのいずれかを利用すればPhoton RealtimeとPhoton Chat機能が使えます。PUN+とFreeの違いはCCU数(同時接続ユーザ数)です。

http://blog.photoncloud.jp/photon-sdk-vs-pun/

アセットストアからPUN+買うとちょっとお得な値段設定ですね。

今回は練習ということでFree版を使用しました。
https://www.assetstore.unity3d.com/jp/#!/content/1786

バージョンは1.50.1(2014/12/18にアップデート)です。

■Unityパッケージのインポートから利用まで

Asset StoreからPhoton Unity Networking Free(以下、Photon)のパッケージをインポートするとPhoton Wizard画面が表示されます。

Photon cloudを利用するためにはPhoton cloudのアカウントが必要です。
アカウントは、このウィザード画面のe-mailフィールドにe-mailを入力して「Send」ボタンを押して指示に従って新規に作成するか、既存のアカウントが利用できます。

アカウントを作成したあとは「Setup」ボタンを押してみましょう。そうするとAppIdの入力とCloud Regionの選択画面になります。

スクリーンショット 2014-12-31 18.39.54

AppIdはオンライン通信するときの識別子となる文字列です。この文字列は、Photon Realtimeのサイトでログインをして、ダッシュボードのマイアカウント画面で作成/編集/削除することができます。

Cloud Regionは最も近い国(私ならjp)を選択するとよいでしょう。

ということで、AppIdの作成のためにPhotonのサイトを表示します。
https://www-jp.exitgames.com/ja/Realtime

スクリーンショット 2014-12-31 18.34.01

マイアカウント画面では、これから作成するアプリケーションがオンライン通信するために必要なアプリケーションID(AppId)の管理を行います。また接続状況なども見ることができます。

新規にアプリケーションを作成するには、右下の「新しいアプリケーションを作成する」ボタンを押します。

スクリーンショット 2014-12-31 18.36.30

「新しいアプリケーションを作成する」画面では最低限アプリケーション名を入力します。あとは必要に応じて入力してください。またアプリケーション名は後からも変更できます。必要事項を入力したら[作成する]ボタンを押してください。

スクリーンショット 2014-12-31 18.37.06

左の方の「アプリケーションID」がAppIdです。この下に書かれている長い文字列をコピーしてUnityに戻ってください。

スクリーンショット 2014-12-31 18.38.23

Unityに戻ったらYour AppId欄に先ほど作成したアプリケーションIDをペースト(貼り付け)します。
Cloud Regionがjpになっていることを確認sにて[Save]ボタンを押します。

スクリーンショット 2014-12-31 18.39.54
[Success!]ダイアログが表示されたら成功です。

スクリーンショット 2014-12-31 18.51.56
AppIdをから変更するには[Window]-[Photon Unity Networking]-[PUN Wizard]を実行するか、Assets/Photon Unity Netwokring/Resourecs/PhotonServerSettingsを選択し、Inspectorから入力することもできます。

スクリーンショット 2014-12-31 18.53.17

■アプリケーション作成の流れ

Photonでアプリケーションを作成するためには以下のことを念頭に作業します。

  • Photon couldとの通信イベントに対する処理(Photon.MonoBehaviouクラスを継承したクラス。PUN 1.50では新しく便利なクラスが増えているけど今回は未使用)
  • Dynamicオブジェクト(移動やアニメーションに関する情報をサーバに送ってクライアント間で同期を取るもの)
    • Transform(Position、Rotation、Scale)
    • Animation(MecanimのParameter)
  • Staticオブジェクト(通信処理は一切なくクライアントだけで処理するもの)

今回の例では以下のようになります。

  • Photon couldとの通信イベントに対する処理
    NetworkManager.cs
  • Dynamicオブジェクト
    SDユニティちゃん
  • Staticオブジェクト
    ステージ、ライト、カメラ

なお、「Dynamicオブジェクト」、「Staticオブジェクト」は私が便宜上付けた名称であり、正式な名称ではありません。もし、正式な呼び方がありましたら教えてください。

■Photon couldとの通信イベントに対する処理

Photon couldとの通信イベントに対する処理は最低限必要な処理はお約束なので、そのまま流用します(本格的な開発をするならここが重要)。

Awake()でPhoton cloudの初期設定を行います。引数にアプリケーションのバージョンをしています。バージョンが一致しないと同じロビーやルームに入れません。したがってバージョンアップするときはここを変えるといいでしょう。

あとはPhoton.MonoBehaviourクラスで定義されているメソッドが呼び出されます。この例では必要最小限度の実装にとどめています。

  • OnJoinedLobby()
  • OnPhotonRandomJoinFailed()
  • OnJoinedRoom()

まず最初にプレイヤはロビーに入ります(OnJoinedLobby())。ロビーは全プレイヤが待機する場所で、ここからルームを作成したり、既存のルームを探して入ることができます。ルームに入ることで対戦が可能になります。大型MMORPGの場合、ロビーをサーバ、ルームをチャネルなど別の表現をしているかもしれません。

この例ではランダムにルームへの参加を指示しています。

しかし最初はルームは作成されていないので参加できません。この場合は、OnPhotonRandomJoinFailed()(Room参加失敗)が呼び出されます。この例では失敗が前提ですので、特に何も調べずに新規に「名無しのルーム」を作成しています。ルームの作成と同時にルームに参加します。

ルームに参加出来たらOnJoinedRoom()が呼び出されます。2プレイヤ以降は既存のルーム(=ランダムルーム)があるのでそちらに入ることになります。

ここでルームに入った後の処理を記述します(この時点では何も書いていませんが、あとで記述を追加します)。

スクリプトを作成したら空のGameObjectを作成し、適当な名前(ここではスクリプトと同じNetworkManagerとする)に変更し、NetworkManager.csをアタッチしてください。

実行してConsoleに以下のメッセージが出れば成功です。

スクリーンショット 2014-12-31 20.55.36

■Dynamicオブジェクト

1. 基本設定

ルームに入ったらオンライン通信が始まります。通信のデータの詳細な内容は不明ですが、ドキュメントを読む限りGameObjectの位置情報、角度、スケール、アニメーション、物理エンジンの値などが送受信されています。また最初にルームに入った人がマスター・クライアントとなりすべての情報を管理します。仮に2プレイヤ以上ルームに参加して、最初のマスター・クライアントがルームを退出したとしても他の誰かがマスター・クライアントとして情報を引き継ぎます。全員退出した場合は情報が消えてしまいます。

つまり刻一刻と情報が変わるオブジェクト(今回の例ではSDユニティちゃん)はプレイヤ間で情報を共有するする必要があります。このようなオブジェクトをここでは「Dynamicオブジェクト」と呼ぶことにしています。

互いに情報を共有するオブジェクト、つまりDynamicオブジェクトとするにはPhotonView.csをアタッチするだけでOKです。あとは具体的にどのような情報を共有するのかを必要に応じて以下のスクリプトをアタッチし、Inspectorで詳細情報をセットしてPhotonView.csのObserverとして登録するだけで自動的に同期します。これらのスクリプトはObserved Componentと呼ばれています。

  • PhotonTransformView.cs
  • PhotonAnimationView.cs
  • PhotonRigidbodyView.cs
  • PhotonRigidbody2DView.cs

これらのコンポーネントを追加するときは、対象のGameObjectを選択し、InspectorでAdd Componmentボタンを押して”Photon”と検索入力すると素早く選択できると思います。

スクリーンショット 2014-12-31 20.16.08

SDユニティちゃんでは、PhotonTransformView、PhotonAnimationView、PhotonRigidbodyViewの3つを追加しました。

次に同期対象のデータと細かい設定を行います。

SDユニティちゃんはキー入力や物理エンジンの動作により位置や角度が変わりますので同期が必要です。スケールは特に変わらないので同期不要です。したがって、TransformのPositionとRotationの同期は必要となります。
この場合、PhotonTransformViewコンポーネントの「Synchronize Position」と「Synchronize Rotation」のそれぞれにチェックを入れます。するとさらに詳細情報が表示されます。

スクリーンショット 2014-12-31 20.23.41

詳細情報について、公式ブログに少し説明があります。正直なところ情報は少ないので色々と試してみてください。以前のバージョンまでは自力でコーディングをしていたので、その時の情報を調べるとよいかもしれません(後日改めてまとめます)。
http://blog.photoncloud.jp/pun-ver1-5/

次にSDユニティちゃんにはRigidbodyがアタッチされています。自由落下をしますので加速度(Velocity)は必要です。回転はしないこともないですが特に重要ではないと思うのでAnglular.Velocity(角速度)の同期はしないことにしました。

スクリーンショット 2014-12-31 20.26.34

次ににSDユニティちゃんはMecanimによるアニメーションを行います。この時、通信対象はAnimator Controllerに渡すパラメータになります。パラメータの性質に合わせて設定内容を変えてください。

Disabled: 同期しない
Discrete: 不連続、離散している値(bool、triggerなど)
Continuous: 連続している値(float、intなど)

スクリーンショット 2014-12-31 22.49.01

これでDynamicオブジェクトの基本設定はできました。

最後にPhotonViewと、あとから追加した3つ(Observed Components(PhotonTransformView、PhotonAnimationView、PhotonRigidbodyView)のComponentを関連付けてください。

具体的にはPhotonViewのObserved Componentsの右下の+ボタンを2回押して合計3つの入力欄を作成します。次に各Observed Componentsのコンポーネント名のところをドラッグして、PhotonViewコンポーネントのObserved Componentsの入力欄にドロップします。

(公式ブログの説明方が分かりやすいかもしれませんのでそちらを見ましょう)

スクリーンショット 2014-12-31 21.11.26

2. 動的読み込みのための準備

Dynamicオブジェクトには2種類あります。

  1. ステージに最初から存在するDynamicオブジェクト
  2. ルームに入るタイミング、および入った後のプレイヤの操作によって生成されるDynamicオブジェクト

SDユニティは「2.」のDynamicオブジェクトになります。「1.」のタイプのDynamicオブジェクトの実装例としては、SDユニティちゃんオンラインのカラーボールになります(プールの中や周辺に転がっているボールです)。

「1.」のタイプのオブジェクトはそのままシーンに配置するだけで特に何もしなくても良いです。「2.」タイプのオブジェクトはさらに準備が必要です。

「2.」のタイプのオブジェクトはルームに入った時に動的に生成する必要があります。Unityで動的に生成するにはInstantiateメソッドを使用します。オン欄接続時は、動的に生成したオブジェクトもクライアント間で同期を取らなければなりません。そこで、普通にInstantiateメソッドを呼ぶのではなくPhotonNetwork.Instantiateメソッドを使用します。

近下院例では、ルームに入ったらすぐにSDユニティーちゃんをインスタンス化します。その場合は、NetworkManager.csのOnJoinedRoom()メソッドに処理を追加します。

固定座標でも、上記のように少しランダム性を持たせてもいいので任意に座標、回転角度を指定してインスタンスを作成してください。

PhotonNetwork.Instantiateメソッドの第1引数はResourcesフォルダにあるprefab名です。UnityはUnity Editor上でPrefabをInspectorのGameObject型の変数に登録するとその場でファイルを読み込み、デシリアライズしてメモリに展開します。しかし、アプリケーション実行時はAssets/Resourecsフォルダに制限されています。

そこで「2.」のタイプのDynamicオブジェクトはAssets/Resourcesフォルダにprefab化して格納しなければなりません。

ということで、Assets/Resoucesフォルダを作成し、HierarchyにあるSDユニティちゃんのGameObjectをAssets/ResourcesフォルダにPrefabとして格納してください。名前は任意でよいですが、プログラムで指定している文字列と合わせる必要があります。
ここでは上記の例に従って”UnitychanPrefab”という名前でPrefabを作成しました。

スクリーンショット 2014-12-31 21.10.22

HierarchyのUnitychanPrefabは後から生成する予定なので削除します(間違っても上記で作成したResources配下のPrefabは消さないように!)。

スクリーンショット 2014-12-31 21.24.33

とりあえずこれで実行すると大量のエラーが出ます。

スクリーンショット 2014-12-31 21.01.10

3.カメラについて

このエラーはUnityChan.ThridPersonCamera.csが出力しています。

一般的なカメラはクライアント間で同期をとる必要のないStaticオブジェクトの一つですが、今回使用しているカメラはプレイヤーを追いかけるタイプのカメラになっています。元々のスクリプトはシーン内に対象のGameObjectが1つし無いことを前提に作られていましたので、これを動的に複数個生成されても問題無いように書き換えます。

Main CameraにアタッチされているThridPersonCamera.csを以下のように修正します。

単に座標だけならパラメータを一つ追加するだけでよいのですが、このプログラムはそこから子要素を探している部分があるため、動的に生成したGameObjectの子要素を検索する処理も加えています。

PrefabをGameObjectにインスタンス化したら、その情報をThirdPersonCameraにセットするようにNetworkManager.csスクリプトを修正します。さらにカメラは起動時に特に表示する相手がいませんので、シーン上は予めThirdPersonCameraを含むGameObjectを非アクティブにしておきます。そして、プレイヤがインスタンス化したときにアクティブにします。

以上をまとめたプログラムは次の通りです。

NetworkManager.csを修正したら、NetworkManagerゲームオブジェクトを選択し、InspectorでThirdPersonCameraにMain Camera(=ThridPersonCamera)をセットしてください。

これで準備完了です。念のためこれまでの内容に対するチェックリストを掲載します。

  1.  Assets/Resources/UnitychanPrefabがあるか?
  2.  Assets/Resources/UnitychanPrefabにPhotonViewなどのスクリプトはアタッチされているか?
  3.  Assets/Resources/UnitychanPrefabの同期用Observerd Componentsの設定は適切か?
    例えば、DisabledとDiscreteを見間違えていないか?
  4.  Main Cameraは非アクティブか?
  5.  Main CameraにアタッチしているThridPersonCamera.csは修正済みか?
  6.  NetworkManagerにアタッチしているNetworkManager.csは修正済みか?
  7.  NetworkManagerにアタッチしているNetworkManagerのThird Person CameraにMain Cameraをセットしているか?

OKですか?では実行してみましょう。エラーが出ていなければ一応完成です。

完成したら実行ファイルを作って同時起動してみてください。WebPlayerでもStandardアプリケーションでもどちらでも結構です。

アプリケーションを2つ作成して起動してみると二人のSDユニティちゃんが表示されます。ですが、動かしてみると二人とも同時に動いてしまいます。

4. 自インスタンスのときだけ操作をする

二人とも同時に動く原因はキャラ操作スクリプト(UnityChanControlScriptWithRgidBody.cs)に問題があります。

キャラクタを操作するスクリプトは一般的にInputクラスを使用しています。Inputクラスは実行画面がアクティブのときに受け付けますので、プレイヤキャラクターが複数個インスタンス化されることは想定していないと思います。ネットワーク越し作成されたSDユニティちゃんにも同じスクリプトがアタッチされていますので、そのスクリプトも反応しておかしな動きをしてしまいます。

そこで自クライアント側が作成したオブジェクトか、それともリモート越しに作成されたオブジェクトかどうかを判定してそれからキー入力処理をするように処理を変更します。

自クライアント側が作成したオブジェクトかどうかはPhotonView.isMine(bool)で調べることができます。SDユニティちゃんにはPhotonViewがアタッチされているのでGetComponment()で取得して値を調べても良いのですが、今後のためにもUnityChanControlScriptWithRgidBody.csのすーぱクラスをMonoBehaviourクラスからPhoton.MonoBehaviourクラスに変更しておきましょう。
そうするとphotonViewプロパティでPhotonViewクラスをできますので以下のコードで自クライアント側が作成したオブジェクトかどうか判定できます。

これで個別に動作するようになりました。これを応用すれば、シューティングゲームのように弾を発射した場合、自分で作ったものかどうかを判定してから移動処理を行えば良いことが分かると思います。

■Staticオブジェクト

Staticオブジェクトとは普通にUnityのGameObjectとして配置したものです。具体的にはPlain、Box、Lightなどです。これらは特にPhoton用の設定は不要です。

ただし、前節にあったCameraのような特殊なものはその都度考える必要があります。

これで基本機能は完成です。実際に動かしてみましょう。

スクリーンショット 2014-12-31 22.04.21

あとはNetworkとカメラをPrefabにして、別のシーンに取り込めばすぐにオリジナルなSDユニティちゃんオンラインができます。

ここまでの内容からPhotonパッケージを省いた物をgihubで公開しています。ダウンロード後、Unityでプロジェクトファイルを開き、PhotonパッケージをインポートしてAppIdを設定するとすぐに動かせます。

https://github.com/zabaglione/SDUnityChanOnline2

チャット機能はまたの機会に。

ユニティちゃんライセンスこのアセットは、『ユニティちゃんライセンス』で提供されています。このアセットをご利用される場合は、『キャラクター利用のガイドライン』も併せてご確認ください。

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です