photon勉強会(クライアントサイド)2015/8/4 発表資料

84
Effective PUN Photonを使って簡単マルチプレイ Photon 運営事務局 山本 昇平 2015/08/04

Upload: gmo-cloud-kk

Post on 21-Aug-2015

340 views

Category:

Business


0 download

TRANSCRIPT

Page 1: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Effective PUNPhotonを使って簡単マルチプレイ

Photon 運営事務局

山本昇平 2015/08/04

Page 2: Photon勉強会(クライアントサイド)2015/8/4 発表資料

自己紹介

• 氏名: 山本昇平(Syohei Yamamoto)

• 所属: GMOクラウド株式会社

• 役割: 各種ソリューションの技術担当

• VRとUnityと料理が得意1988年生まれのエンジニア

• 最近ハマってるゲーム: 某イカゲー– 特にオンラインマルチプレイが楽しい!

– スマホもCSもマルチに盛り上がりを見せている

2

Page 3: Photon勉強会(クライアントサイド)2015/8/4 発表資料

皆さん

Page 4: Photon勉強会(クライアントサイド)2015/8/4 発表資料

ネットワーク対戦ゲームを

フルスクラッチで作れますか?

Page 5: Photon勉強会(クライアントサイド)2015/8/4 発表資料

簡単に、かつ高速に作りたい…

マッチング専用のサーバを用意するのは手間が掛かる…

Page 6: Photon勉強会(クライアントサイド)2015/8/4 発表資料

そう、Photonならこのすべてを実現します

Page 7: Photon勉強会(クライアントサイド)2015/8/4 発表資料

アジェンダ

• Photonとは

• HELLO, Photon World!!

– Photonの機能について簡単に紹介します

• Effective PUN

–一歩進んだPUNの使い方を解説します

Page 8: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Photonとは

Page 9: Photon勉強会(クライアントサイド)2015/8/4 発表資料

1ページでPhotonを紹介すると…

• ネットワークゲームを作るための機能を提供したサービス&ミドルウェア

• すべて無料から利用可能

• クラウド型とミドルウェア型がある

Page 10: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Photonのラインナップ

サーバー構築・運用が不要なクラウドサービス

クラウド系基本サービス、迷ったらまずはこれ

ターンベース、低トラフィック向け

Unity向け、サーバー・機能はRealtimeと全く同等

チャットに特化

サーバーにインストールして使うミドルウェアサービス

10

Page 11: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Photonの主な機能

• ロビー

– 名前付ロビー

– マッチメイキング

– プレイヤー検索

• ルーム

– 人数制御・表示制御

– カスタムプロパティ

• 同期関連

– オブジェクト同期

– イベント通知

– RPC (PUN)

• WebHooks/WebRPC

• オフラインモード

11

Page 12: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Photonの特徴

• サーバー経由なので接続性が高い

• 独自の技術による信頼性と低レイテンシーを確保

• 異なるプラットフォーム間でも利用可能

• UnityはAsset StoreからDLするだけで利用可能

12

Page 13: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Photonの開発メンバー

• 開発元はドイツ・ハンブルクの Exitgames社

• トップもエンジニアのエンジニア集団

• World Golf TourのノウハウをPhotonに活用

13

Page 14: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Photon採用事例

大手パブリッシャーさまにも、ご採用頂いております

14

Page 15: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Photonを利用することで

• 手軽に低コストでネットワーク対戦ゲームを実現

• クラウド型ならネットワークやインフラをアウトソーシングする事が可能、クライアント開発に集中できる

• 要件に合わない場合はミドルウェア型を使って、Photon自体のカスタマイズが可能

15

Page 16: Photon勉強会(クライアントサイド)2015/8/4 発表資料

HELLO, Photon World!!

Page 17: Photon勉強会(クライアントサイド)2015/8/4 発表資料

今回はクライアントサイドの話

• PhotonのクライアントSDKは2種類ある

– Photon Native SDK

• ExitGames社からダウンロードできるもの

– Photon Unity Networking

• Asset Storeからダウンロードできるもの

• Native SDKをUnityで使いやすくしたもの

• Unity Networking(旧)と互換性があり、移行が容易

17

Page 18: Photon勉強会(クライアントサイド)2015/8/4 発表資料

今回はクライアントサイドの話

• PhotonのクライアントSDKは2種類ある

– Photon Native SDK

• ExitGames社からダウンロードできるもの

– Photon Unity Networking

• Asset Storeからダウンロードできるもの

• Native SDKをUnityで使いやすくしたもの

• Unity Networking(旧)と互換性があり、移行が容易

18

今回はこっち

Page 19: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Photon Native SDK

• 従来のPhoton SDK

• C++, .NET, Android NDK, iOSなど様々なプラットフォームで利用することが可能

• クロスプラットフォームでどの言語も同様のAPI

• Photonのすべての機能が使える

–ロビー、マッチメイキング、ルーム作成・入室

– イベント発生、受信処理

19

Page 20: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Photon Unity Networking(PUN)

• Native SDKをベースにUnityで使いやすくしたもの

– Native SDKの上位SDKの位置づけ

• 旧Unity Networkingと互換性があり、移行が容易

• Asset Storeからインポートするだけで利用可能

– 無料ですべての機能をご利用頂けます!

• UnityでPhotonを使うならPUNがオススメ!

20

Page 21: Photon勉強会(クライアントサイド)2015/8/4 発表資料

PUNの特徴 / 接続処理

• 接続処理はStaticクラスのメソッドを呼び出すだけ

– PhotonNetwork.ConnectUsingSettings();

• Photon Unity Networking/Resources/PhotonServerSettingsを見てPhotonへ接続しロビーに入る

• ちなみに、PhotonServerSettingsはResources直下であれば、別のフォルダに移動しても可

21

Page 22: Photon勉強会(クライアントサイド)2015/8/4 発表資料

PUNの特徴 / ルーム接続

• Photonではロビー→ルームの順に接続を行う

–病院の待合室(ロビー)と診察室(ルーム)

–実際のゲームはルーム(診察室)で行われる

• PhotonNetwork.JoinOrCreateRoom();

–ロビーに居るとき実行可能

–ルームに入室、ルームが無ければ作成を行う

22

Page 23: Photon勉強会(クライアントサイド)2015/8/4 発表資料

PUNの特徴 / イベント通知(RPC)

• イベント通知機能もあるが、RPCを使うとより簡単

• メソッドに[PunRPC]属性を付けるだけで他のプレイヤーを指示することが可能

23

Page 24: Photon勉強会(クライアントサイド)2015/8/4 発表資料

PUNの特徴 / オブジェクト同期

• PrefabにPhotonViewコンポーネントを追加するだけ

– Transform / Rigidbody / 作成したScriptを同期できる

– Transformを同期するにはPhotonTransformView, Animatorを同期するにはPhotonAnimatorViewを利用するとより簡単に同期できる

– コーディングなしでネットワーク同期が可能

• シリアライズのコードを書けばそれ以外のデータも同期可能

24

Page 25: Photon勉強会(クライアントサイド)2015/8/4 発表資料

PUNで簡単にオンラインゲーム

• ネットワーク対戦ゲームに必要な機能を網羅

–キャラクタの位置・状態 : オブジェクト同期

– イベント情報 : イベント通知、RPC

–ゲーム全体の状態 : ルームのカスタムプロパティ

–ロビーやマッチメイキングもあります

25

Page 26: Photon勉強会(クライアントサイド)2015/8/4 発表資料

プログラムを見てみましょう

Page 27: Photon勉強会(クライアントサイド)2015/8/4 発表資料

サンプルプログラムについて

• 基本的な機能の紹介します

1. Photonへ接続

2. ルームへの入室• 無い場合は作成します

3. RPCの送信

27

Page 28: Photon勉強会(クライアントサイド)2015/8/4 発表資料

28

1public class Example : Photon.MonoBehaviour

2{

3 [SerializeField] private Button connecToPhoton, joinRoom, rpc;

4 [SerializeField] private Text log;

5 private string state;

6

7 void Start()

8 {

9 connecToPhoton.onClick.AddListener(() =>

10 {

11 PhotonNetwork.ConnectUsingSettings("0");

12 });

13

14 joinRoom.onClick.AddListener(() =>

15 {

16 PhotonNetwork.JoinOrCreateRoom("TestRoom", new RoomOptions(), new TypedLobby());

17 });

18

19 rpc.onClick.AddListener(() =>

20 {

21 photonView.RPC("RPCTest", PhotonTargets.All, "Hello, Photon World!!");

22 });

23 }

Page 29: Photon勉強会(クライアントサイド)2015/8/4 発表資料

29

25 void Update()

26 {

27 if (state != PhotonNetwork.connectionStateDetailed.ToString())

28 {

29 log.text += string.Format("[{0}] {1}¥n", System.DateTime.Now, state);

30 }

31

32 state = PhotonNetwork.connectionStateDetailed.ToString();

33 }

34

35 [PunRPC]

36 public void RPCTest(string message)

37 {

38 log.text += string.Format("[{0}] {1}¥n", System.DateTime.Now, message);

39 }

40}

Page 30: Photon勉強会(クライアントサイド)2015/8/4 発表資料

30

1public class Example : Photon.MonoBehaviour

2{

3 [SerializeField] private Button connecToPhoton, joinRoom, rpc;

4 [SerializeField] private Text log;

5 private string state;

6

7 void Start()

8 {

9 connecToPhoton.onClick.AddListener(() =>

10 {

11 PhotonNetwork.ConnectUsingSettings("0");

12 });

13

14 joinRoom.onClick.AddListener(() =>

15 {

16 PhotonNetwork.JoinOrCreateRoom("TestRoom", new RoomOptions(), new TypedLobby());

17 });

18

19 rpc.onClick.AddListener(() =>

20 {

21 photonView.RPC("RPCTest", PhotonTargets.All, "Hello, Photon World!!");

22 });

23 }

接続ボタンが押されたタイミングでPhotonに接続を行う。PhotonNetwork.ConnectUsingSettings()を使えば、PhotonServerSettingsファイルに事前に設定しておいた接続先に接続を行う事が可能です

Page 31: Photon勉強会(クライアントサイド)2015/8/4 発表資料

31

1public class Example : Photon.MonoBehaviour

2{

3 [SerializeField] private Button connecToPhoton, joinRoom, rpc;

4 [SerializeField] private Text log;

5 private string state;

6

7 void Start()

8 {

9 connecToPhoton.onClick.AddListener(() =>

10 {

11 PhotonNetwork.ConnectUsingSettings("0");

12 });

13

14 joinRoom.onClick.AddListener(() =>

15 {

16 PhotonNetwork.JoinOrCreateRoom("TestRoom", new RoomOptions(), new TypedLobby());

17 });

18

19 rpc.onClick.AddListener(() =>

20 {

21 photonView.RPC("RPCTest", PhotonTargets.All, "Hello, Photon World!!");

22 });

23 }

入室ボタンが押されたタイミングでルームに入室、ルームが無い場合は作成します。余計なコールバックを書かずに済むPhotonNetwork.JoinOrCreateRoom()が便利です。

Page 32: Photon勉強会(クライアントサイド)2015/8/4 発表資料

32

1public class Example : Photon.MonoBehaviour

2{

3 [SerializeField] private Button connecToPhoton, joinRoom, rpc;

4 [SerializeField] private Text log;

5 private string state;

6

7 void Start()

8 {

9 connecToPhoton.onClick.AddListener(() =>

10 {

11 PhotonNetwork.ConnectUsingSettings("0");

12 });

13

14 joinRoom.onClick.AddListener(() =>

15 {

16 PhotonNetwork.JoinOrCreateRoom("TestRoom", new RoomOptions(), new TypedLobby());

17 });

18

19 rpc.onClick.AddListener(() =>

20 {

21 photonView.RPC("RPCTest", PhotonTargets.All, "Hello, Photon World!!");

22 });

23 }

RPCボタンが押されたタイミングでRPC Testというリモートプロシージャ(メソッド)を呼び出します。このとき”Hello, Photon World!!”というパラメータを持たせています。送信先はプレイヤー全員です(後述)。

Page 33: Photon勉強会(クライアントサイド)2015/8/4 発表資料

33

1public class Example : Photon.MonoBehaviour

2{

3 [SerializeField] private Button connecToPhoton, joinRoom, rpc;

4 [SerializeField] private Text log;

5 private string state;

6

7 void Start()

8 {

9 connecToPhoton.onClick.AddListener(() =>

10 {

11 PhotonNetwork.ConnectUsingSettings("0");

12 });

13

14 joinRoom.onClick.AddListener(() =>

15 {

16 PhotonNetwork.JoinOrCreateRoom("TestRoom", new RoomOptions(), new TypedLobby());

17 });

18

19 rpc.onClick.AddListener(() =>

20 {

21 photonView.RPC("RPCTest", PhotonTargets.All, "Hello, Photon World!!");

22 });

23 }

RPCを利用するにはPhotonViewを使う必要がある。Photon.MonoBehaviorかPhotonNetwork.Get(GameObejct)を使うと取得可能です。

photonViewインスタンスを取得するにはPhotn.MonoBehaviorを継承する必要がある。

Page 34: Photon勉強会(クライアントサイド)2015/8/4 発表資料

34

25 void Update()

26 {

27 if (state != PhotonNetwork.connectionStateDetailed.ToString())

28 {

29 log.text += string.Format("[{0}] {1}¥n", System.DateTime.Now, state);

30 }

31

32 state = PhotonNetwork.connectionStateDetailed.ToString();

33 }

34

35 [PunRPC]

36 public void RPCTest(string message)

37 {

38 log.text += string.Format("[{0}] {1}¥n", System.DateTime.Now, message);

39 }

40}

RPCで呼び出されるRPCTestメソッドを定義しています。このとき[PunRPC]属性を指定すると、Photonで呼ばれます。RPCでは引数を設定し、引き渡すことも可能です。

デモ

Page 35: Photon勉強会(クライアントサイド)2015/8/4 発表資料

いかがでしたでしょうか

Page 36: Photon勉強会(クライアントサイド)2015/8/4 発表資料

ここまでが基礎編

Page 37: Photon勉強会(クライアントサイド)2015/8/4 発表資料

ここからが本題

Page 38: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Effective PUN• RPCを極める• マスタークライアントの切替• オフラインモードを使ってみる• WebRPCを使ってみる• マッチメイキングハック• ルームリストの取得を高速化する• Photonのコールバックと仲良くなる• PhotonでUniRxを使ってみる

38

Page 39: Photon勉強会(クライアントサイド)2015/8/4 発表資料

Effective PUN• RPCを極める• マスタークライアントの切替• オフラインモードを使ってみる• WebRPCを使ってみる• マッチメイキングハック• ルームリストの取得を高速化する• Photonのコールバックと仲良くなる• PhotonでUniRxを使ってみる

39

間に合わなかった

Page 40: Photon勉強会(クライアントサイド)2015/8/4 発表資料

RPCを極める

Page 41: Photon勉強会(クライアントサイド)2015/8/4 発表資料

RPCのパラメータについて

• RPCは用途によって使い分けられることが可能

– 自分自身が実行するかどうか

– バッファリングして入室時に実行するかどうか

– オフラインモードで実行するかどうか

• photonView.RPC("RPCTest", PhotonTargets.All, "Hello");

41

Page 42: Photon勉強会(クライアントサイド)2015/8/4 発表資料

RPCのパラメータについて

• RPCは用途によって使い分けられることが可能

– 自分自身が実行するかどうか

– バッファリングして入室時に実行するかどうか

– オフラインモードで実行するかどうか

• photonView.RPC("RPCTest", PhotonTargets.All, "Hello");

42

このパラメータ!

Page 43: Photon勉強会(クライアントサイド)2015/8/4 発表資料

PhotonTargetsについて

43

All Others MasterClient

自分自身 サーバ経由せずに実行 実行しない

他のプレイヤー サーバ経由後に実行する サーバ経由後に実行する

後から入ったプレイヤー 実行しない 実行しない

マスタークライアント マスタークライアントのみ実行

オフラインモードの場合 自分自身のみ実行する 実行しない マスタークライアントのみ実行

AllBuffered OthersBuffered AllViaServer AllBufferedViaServer

自分自身 サーバ経由せずに実行 実行しない サーバ経由後に実行する サーバ経由後に実行する

他のプレイヤー サーバ経由後に実行する サーバ経由後に実行する サーバ経由後に実行する サーバ経由後に実行する

後から入ったプレイヤー ルーム入室後に実行する ルーム入室後に実行する 実行しない ルーム入室後に実行する

マスタークライアント

オフラインモードの場合 自分自身のみ実行する 実行しない 自分自身のみ実行する 自分自身のみ実行する

デモソースコード

Page 44: Photon勉強会(クライアントサイド)2015/8/4 発表資料

RPCのパラメータについて

• 一例として– マスタークライアントに対してのみメッセージを送信したい場合

• photonView.RPC("RPCTest", PhotonTargets.MasterClient, “To MasteClient");

– 自分以外の他のプレイヤーと、ルームに後から入室した人に対してメッセージを送りたい(バッファしたい)場合• photonView.RPC(“RPCTest”, PhotonTargets.OthersBuffered, “他のプレイヤーとバッファ");

• 用途に応じて使い分けが可能、柔軟性が高く使いやすい

44

Page 45: Photon勉強会(クライアントサイド)2015/8/4 発表資料

マスタークライアントの切替

Page 46: Photon勉強会(クライアントサイド)2015/8/4 発表資料

マスタークライアントとは

• Photonではホストでは無いが、ルームを代表するプレイヤーとしてマスタークライアントというものがある

• 基本的には一番最初に入室したプレイヤーが設定される

• PhotonNetwork.isMasterClientを利用することで、マスタークライアントに代表して何らか処理を実行させることが可能– 例えば、ルーム情報や、ゲームデータの集計など…

• PUNの最近のバージョンでマスタークライアントの切替処理が実装されたのでご紹介します

46

Page 47: Photon勉強会(クライアントサイド)2015/8/4 発表資料

1 rpc.onClick.AddListener(() =>

2 {

3 photonView.RPC("RPCTest", PhotonTargets.MasterClient, "Received a message for Master Client.");

4 });

5

6 master.onClick.AddListener(() =>

7 {

8 PhotonPlayer[] players = PhotonNetwork.playerList;

9 foreach(PhotonPlayer player in players)

10 {

11 if(masterName.text == player.name)

12 {

13 PhotonNetwork.SetMasterClient(player);

14 }

15 }

16 });

47

マスタークライアント変更ボタンが押された場合、プレイヤーリストを取得し入力した変更したいプレイヤー名にマスタークライアントの権限を付与する

RPCボタンが押された場合、マスタークライアントに対してRPCを送信

Page 48: Photon勉強会(クライアントサイド)2015/8/4 発表資料

48

1 void OnJoinedRoom()

2 {

3 state = "Master Client: " + PhotonNetwork.isMasterClient;

4 }

5

6 void OnMasterClientSwitched(PhotonPlayer newMasterClient)

7 {

8 state = newMasterClient.name + " is the Master Client.";

9 }

10

11 [PunRPC]

12 public void RPCTest(string message)

13 {

14 log.text += string.Format("[{0}] {1}¥n", System.DateTime.Now, message);

15 }

マスタークライアントが変更された時のコールバックが用意されているパラメータに新しいプレイヤーの情報を含む

Page 49: Photon勉強会(クライアントサイド)2015/8/4 発表資料

49

実行結果

デモ

Page 50: Photon勉強会(クライアントサイド)2015/8/4 発表資料

オフラインモードを使ってみる

Page 51: Photon勉強会(クライアントサイド)2015/8/4 発表資料

オフラインモードとは

• Photonのプログラムをオフラインで利用する機能

• シングルプレイヤーモードを実装する際にコードを使い回すことが可能!

– 例えば、プレイヤーが落ちてマルチの継続が不可能なときに、シングルプレイヤーモードとして継続する

51

Page 52: Photon勉強会(クライアントサイド)2015/8/4 発表資料

オフラインモードの利用方法

• Photonと接続がされていない状態で…

1. PhotonNetwork.offlineMode = true;

オフラインモードに入ります

2. PhotonNetwork.JoinRoom(“RoomName”);オフラインモードの状態でルームに入室します

3. オフライン状態でPhotonNetwork.Instantiate()やphotonView.RPC()といった操作が可能になる。

52

Page 53: Photon勉強会(クライアントサイド)2015/8/4 発表資料

53

実行結果

デモソースコード

Page 54: Photon勉強会(クライアントサイド)2015/8/4 発表資料

WebRPCを使ってみる

Page 55: Photon勉強会(クライアントサイド)2015/8/4 発表資料

WebRPCとは

• クライアント側からRESTで外部APIを呼び出す機能

• クラウド型サービスでも外部サービスと連携することが可能

• 現在 Photon Turnbasedのみでの提供– Photon Turnbasedとは2014年6月より開始した新しいサービス

– Webhook/WebRPCやクラウドセーブといった機能を搭載

• 詳細は Photon Turnbased公式サイトをご覧ください– https://www.photonengine.com/ja/Turnbased

55

Page 56: Photon勉強会(クライアントサイド)2015/8/4 発表資料

受口となるWebアプリを作る

• Photon側から実際に呼び出すWeb側のアプリを作る

• 今回は webscript.ioというWebサービスを利用しました

56

Page 57: Photon勉強会(クライアントサイド)2015/8/4 発表資料

webscriptとは

• webscriptとはブラウザ上でプログラムを記入し、すぐに実行することができるWebサービス

• 無料枠だとスクリプトが7日間保持される

• 簡単なテストを行いたいときに便利です!

• 詳細についてはwebscript公式サイトをご覧ください– https://webscript.io/

57

Page 58: Photon勉強会(クライアントサイド)2015/8/4 発表資料

58

1 local jsonRequest = json.parse(request.body)

2 local _name = jsonRequest.name

3 local _age = jsonRequest.age

4

5 if _name ~= nil and _age ~= nil then

6 return {

7 ResultCode = 0,

8 Data = {

9 name = _name,

10 age = _age

11 }

12 }

13 else

14 return { ResultCode = 9 }

15 end

JSONをパースしてnameとageを返すだけの簡単なプログラムです(言語はLua)。

https://gist.github.com/syyama/ac5516bcfaa4fea14a9f

Page 59: Photon勉強会(クライアントサイド)2015/8/4 発表資料

ダッシュボードの設定

• WebRPCの接続先の設定はダッシュボード上から行います

• Photon Turnbasedのダッシュボード→[詳細へ]を押下

• 最下部の[Webhook]ボタンを押下

59

Page 60: Photon勉強会(クライアントサイド)2015/8/4 発表資料

ダッシュボードの設定

• 外部URLの設定を行います

• BaseURLにWebRPCで用いるURLを指定

• 例)http://photon-tb.webscript.io/webhookの赤字の部分

60

Page 61: Photon勉強会(クライアントサイド)2015/8/4 発表資料

クライアント側からリクエストを行う

• Webscriptで作ったプログラムをPUNで呼び出します

• PhotonNetwork.WebRpc()の第1引数にダッシュボードで指定したBaseURL以降のURLを指定します

• 例)http://photon-tb.webscript.io/webhookの青字の部分

61

Page 62: Photon勉強会(クライアントサイド)2015/8/4 発表資料

62

1 // WebRPC

2 webRpc.onClick.AddListener(() =>

3 {

4 Dictionary<string, object> parameters = new Dictionary<string, object>();

5 parameters.Add("name", "Nico Yazawa");

6 parameters.Add("age", "17");

7

8 PhotonNetwork.WebRpc("webhook", parameters);

9 });

第1引数にwebhookと指定。第2引数にPOST時のリクエストパラメータを指定します。型はDictionary型です。第2引数のDictionaryはJSONに変換されPOSTされます。

Page 63: Photon勉強会(クライアントサイド)2015/8/4 発表資料

クライアント側でレスポンスを受ける

• WebRPCのレスポンスを受け取るにはOnWebRpcResponseメソッドを利用します

• RPCのレスポンスが返ってきたタイミングで呼ばれる

63

Page 64: Photon勉強会(クライアントサイド)2015/8/4 発表資料

1 void OnWebRpcResponse(OperationResponse operationResponse)

2 {

3 if (operationResponse.ReturnCode != 0)

4 {

5 state = "WebRPCに失敗しました. Response: " + operationResponse.ToStringFull();

6 return;

7 }

8

9 WebRpcResponse webRpcResponse = new WebRpcResponse(operationResponse);

10

11 if (webRpcResponse.ReturnCode != 0)

12 {

13 state = "WebRPC '" + webRpcResponse.Name + "' に失敗しました. Error: "

14 + webRpcResponse.ReturnCode + " Message: " + webRpcResponse.DebugMessage;

15 return;

16 }

17

18 Dictionary<string, object> parameters = webRpcResponse.Parameters;

19

20 foreach (KeyValuePair<string, object> pair in parameters)

21 {

22 log.text += string.Format("[{0}] Key : {1} / Value : {2}¥n",

23 System.DateTime.Now, pair.Key, pair.Value);

24 }

25 } 64

Page 65: Photon勉強会(クライアントサイド)2015/8/4 発表資料

1 void OnWebRpcResponse(OperationResponse operationResponse)

2 {

3 if (operationResponse.ReturnCode != 0)

4 {

5 state = "WebRPCに失敗しました. Response: " + operationResponse.ToStringFull();

6 return;

7 }

8

9 WebRpcResponse webRpcResponse = new WebRpcResponse(operationResponse);

10

11 if (webRpcResponse.ReturnCode != 0)

12 {

13 state = "WebRPC '" + webRpcResponse.Name + "' に失敗しました. Error: "

14 + webRpcResponse.ReturnCode + " Message: " + webRpcResponse.DebugMessage;

15 return;

16 }

17

18 Dictionary<string, object> parameters = webRpcResponse.Parameters;

19

20 foreach (KeyValuePair<string, object> pair in parameters)

21 {

22 log.text += string.Format("[{0}] Key : {1} / Value : {2}¥n",

23 System.DateTime.Now, pair.Key, pair.Value);

24 }

25 } 65

レスポンスが正しく返ってきているか判定正しく返ってきた場合は OperationResponseのReturnCodeに 0 が返ってくる

Page 66: Photon勉強会(クライアントサイド)2015/8/4 発表資料

1 void OnWebRpcResponse(OperationResponse operationResponse)

2 {

3 if (operationResponse.ReturnCode != 0)

4 {

5 state = "WebRPCに失敗しました. Response: " + operationResponse.ToStringFull();

6 return;

7 }

8

9 WebRpcResponse webRpcResponse = new WebRpcResponse(operationResponse);

10

11 if (webRpcResponse.ReturnCode != 0)

12 {

13 state = "WebRPC '" + webRpcResponse.Name + "' に失敗しました. Error: "

14 + webRpcResponse.ReturnCode + " Message: " + webRpcResponse.DebugMessage;

15 return;

16 }

17

18 Dictionary<string, object> parameters = webRpcResponse.Parameters;

19

20 foreach (KeyValuePair<string, object> pair in parameters)

21 {

22 log.text += string.Format("[{0}] Key : {1} / Value : {2}¥n",

23 System.DateTime.Now, pair.Key, pair.Value);

24 }

25 } 66

WebRPCの結果を読み出すWebRpCResponseクラスの

インスタンスを生成し、レスポンスとして返されたデータを利用

WebRPCのレスポンスが正しいか判定

Page 67: Photon勉強会(クライアントサイド)2015/8/4 発表資料

1 void OnWebRpcResponse(OperationResponse operationResponse)

2 {

3 if (operationResponse.ReturnCode != 0)

4 {

5 state = "WebRPCに失敗しました. Response: " + operationResponse.ToStringFull();

6 return;

7 }

8

9 WebRpcResponse webRpcResponse = new WebRpcResponse(operationResponse);

10

11 if (webRpcResponse.ReturnCode != 0)

12 {

13 state = "WebRPC '" + webRpcResponse.Name + "' に失敗しました. Error: "

14 + webRpcResponse.ReturnCode + " Message: " + webRpcResponse.DebugMessage;

15 return;

16 }

17

18 Dictionary<string, object> parameters = webRpcResponse.Parameters;

19

20 foreach (KeyValuePair<string, object> pair in parameters)

21 {

22 log.text += string.Format("[{0}] Key : {1} / Value : {2}¥n",

23 System.DateTime.Now, pair.Key, pair.Value);

24 }

25 } 67

レスポンスはKey-Value形式のDictionary型で取得可能そのままstring文字列として利用できます

Page 68: Photon勉強会(クライアントサイド)2015/8/4 発表資料

68

実行結果

デモ

Page 69: Photon勉強会(クライアントサイド)2015/8/4 発表資料

WebRPCを使うメリット

• 独自に立てたゲームサーバからルームリストを取得することで、プレイヤー情報を取得したり、より高度なマッチングを実装することが可能!

• マルチプレイの開始・終了時にゲームデータをやりとりするといったこともできる

• Photon自体のカスタマイズを検討する前に、WebRPCで実現できないか検討してみるのもアリ

69

Page 70: Photon勉強会(クライアントサイド)2015/8/4 発表資料

マッチメイキングハック

Page 71: Photon勉強会(クライアントサイド)2015/8/4 発表資料

PUNのマッチングはイカしてない

• PhotonNetwork.JoinRoom()

– ルーム名を指定して入室しマッチングする

• PhotonNetwork.JoinRandomRoom()

– ランダムでルームに入室しマッチングする

• PUNではこの2つしか用意されていない!

71

Page 72: Photon勉強会(クライアントサイド)2015/8/4 発表資料

近いレベルごとでマッチングするには

• カスタムプロパティを利用する方法1. レベルをグルーピングし、適当なランクを設定する

• 例えば0~20: Eランク、21~40: Dランク…など

2. ランクをプレイヤーのPhotonの機能であるカスタムプロパティにセット

3. 「同じランクのみ入室」という条件を付けたルームを作成

4. ランクが一致したプレイヤーが入室する

• 他にもTypedLobbyを使う方法などがある

72

Page 73: Photon勉強会(クライアントサイド)2015/8/4 発表資料

73

1 string LevelToMatchRank(int level)

2 {

3 if (level <= 20)

4 return "E";

5 if (level <= 40)

6 return "D";

7 if (level <= 60)

8 return "C";

9 if (level <= 80)

10 return "B";

11 if (level <= 99)

12 return "A";

13 if (level == 100)

14 return "S";

15 return "";

16 }

1 rank = this.LevelToMatchRank((int)levelValue);

1 GUILayout.Label("レベル: " + ((int)levelValue).ToString(), GUILayout.Width(80));

2 levelValue = GUILayout.HorizontalSlider(levelValue, 0.0f, 100.0f);

旧GUIで申し訳ないです

適当にランクをセット

Page 74: Photon勉強会(クライアントサイド)2015/8/4 発表資料

74

1 PhotonNetwork.playerName = userName;

2 propeties = new Hashtable() { { "UserName", userName }, { "Level", ((int)levelValue).ToString() }, { "Rank", rank }, { "RoomName", roomName } };

3 PhotonNetwork.SetPlayerCustomProperties(propeties);

1 using Hashtable = ExitGames.Client.Photon.Hashtable;

1 private Hashtable propeties;

Photonで利用するHashtableは別途usingする必要があります

Hashtableでレベルやランクといった情報を設定し、カスタムプロパティとしてセットします

Page 75: Photon勉強会(クライアントサイド)2015/8/4 発表資料

75

1 RoomOptions roomOptions = new RoomOptions ();

2 roomOptions.isVisible = true;

3 roomOptions.isOpen = true;

4 roomOptions.maxPlayers = 4;

5 roomOptions.customRoomProperties = new Hashtable (){{"Rank", (string)properties["Rank"]} };

6 roomOptions.customRoomPropertiesForLobby = new string[] {"Rank"};

7

8 if (PhotonNetwork.GetRoomList ().Length == 0) {

9 PhotonNetwork.CreateRoom ((string)properties ["RoomName"],roomOptions, null);

10 return;

11 }

12 foreach (RoomInfo roomInfo in PhotonNetwork.GetRoomList()) {

13 if (roomInfo.name != (string)properties ["RoomName"]) {

14 PhotonNetwork.CreateRoom ((string)properties ["RoomName"],roomOptions, null);

15 } else {

16 isRoomEnabled = true;

17 }

18 }

ルームの作成時に、RoomOptions.customRoomPropertiesにHashtabke型でカスタムプロパティにRank情報をセット。同時にロビーからカスタムプロパティが見えるように、customRoomPropertiesForLobbyに、カスタムプロパティのキー情報をセットします。

Page 76: Photon勉強会(クライアントサイド)2015/8/4 発表資料

76

1 RoomOptions roomOptions = new RoomOptions ();

2 roomOptions.isVisible = true;

3 roomOptions.isOpen = true;

4 roomOptions.maxPlayers = 4;

5 roomOptions.customRoomProperties = new Hashtable (){{"Rank", (string)properties["Rank"]} };

6 roomOptions.customRoomPropertiesForLobby = new string[] {"Rank"};

7

8 if (PhotonNetwork.GetRoomList ().Length == 0) {

9 PhotonNetwork.CreateRoom ((string)properties ["RoomName"],roomOptions, null);

10 return;

11 }

12 foreach (RoomInfo roomInfo in PhotonNetwork.GetRoomList()) {

13 if (roomInfo.name != (string)properties ["RoomName"]) {

14 PhotonNetwork.CreateRoom ((string)properties ["RoomName"],roomOptions, null);

15 } else {

16 isRoomEnabled = true;

17 }

18 }

ルームが無い場合、ルームリストを取得し同じ名前のルームが無いことを確認してルームを作成します

Page 77: Photon勉強会(クライアントサイド)2015/8/4 発表資料

77

1 if (PhotonNetwork.GetRoomList ().Length == 0) {

2 isRoomNothing = true;

3 return;

4 }

5

6 foreach (RoomInfo roomInfo in PhotonNetwork.GetRoomList ()) {

7 Hashtable cp = roomInfo.customProperties;

8

9 if (roomInfo.name == (string)properties ["RoomName"]) {

10 if ((string)properties ["Rank"] == (string)cp ["Rank"]) {

11 PhotonNetwork.JoinRoom ((string)properties ["RoomName"]);

12 } else {

13 isLevelUnmatch = true;

14 }

15 return;

16 }

17 }

18

19 isRoomNothing = true;

入室時、ルームリストを取得します。RoomInfoクラスではcustomPropertiesを取得することが可能。

ルームのカスタムプロパティのRankとプレイヤーのカスタムプロパティのRankが一致すると入室を行う。

Page 78: Photon勉強会(クライアントサイド)2015/8/4 発表資料

78

実行結果

デモ

レベルの近い(Rankの一致する)ユーザ同士がマッチングする

Page 79: Photon勉強会(クライアントサイド)2015/8/4 発表資料

マッチングを実装するには

• マッチング情報の近いプレイヤー同士をグルーピングし、そのグループでマッチングを行う

• 例)某イカゲーのマッチングを妄想してみる– 基本的な情報:プレイヤー名、リージョン、など

– マッチング情報:ランク、ウデマエ、勝率、総マッチング数、キルデス比、称号、チーム、など

– プレイヤーのプロパティとしてこれらの情報を保持させる

79

Page 80: Photon勉強会(クライアントサイド)2015/8/4 発表資料

80

プログラムの完成が間に合わなかった

Page 81: Photon勉強会(クライアントサイド)2015/8/4 発表資料

まとめ

• 本日のクライアントサイドの勉強会はいかがでしたか?

• 意外と知らない機能があったのでは無いでしょうか?

• 他にこんな事が知りたいといった話があればご教示ください

• 今回はUnityを使いましたが、他の開発環境と組み合わせた内容をも今後検討して参ります– 何かご要望があればお伝えください!

81

Page 82: Photon勉強会(クライアントサイド)2015/8/4 発表資料

最後に…

Page 83: Photon勉強会(クライアントサイド)2015/8/4 発表資料

最後に…

一緒に盛り上げてくれる

を大募集中!(エンジニア、営業などなど)

83

Page 84: Photon勉強会(クライアントサイド)2015/8/4 発表資料

ありがとうございました!

[email protected]