More Related Content More from GMO GlobalSign Holdings K.K.
More from GMO GlobalSign Holdings K.K. (8) Photon勉強会(クライアントサイド)2015/8/4 発表資料2. 自己紹介
• 氏名: 山本昇平(Syohei Yamamoto)
• 所属: GMOクラウド株式会社
• 役割: 各種ソリューションの技術担当
• VRとUnityと料理が得意
1988年生まれのエンジニア
• 最近ハマってるゲーム: 某イカゲー
– 特にオンラインマルチプレイが楽しい!
– スマホもCSもマルチに盛り上がりを見せている
2
11. Photonの主な機能
• ロビー
– 名前付ロビー
– マッチメイキング
– プレイヤー検索
• ルーム
– 人数制御・表示制御
– カスタムプロパティ
• 同期関連
– オブジェクト同期
– イベント通知
– RPC (PUN)
• WebHooks/WebRPC
• オフラインモード
11
19. Photon Native SDK
• 従来のPhoton SDK
• C++, .NET, Android NDK, iOSなど様々なプラット
フォームで利用することが可能
• クロスプラットフォームでどの言語も同様のAPI
• Photonのすべての機能が使える
– ロビー、マッチメイキング、ルーム作成・入室
– イベント発生、受信処理
19
20. Photon Unity Networking(PUN)
• Native SDKをベースにUnityで使いやすくしたもの
– Native SDKの上位SDKの位置づけ
• 旧Unity Networkingと互換性があり、移行が容易
• Asset Storeからインポートするだけで利用可能
– 無料ですべての機能をご利用頂けます!
• UnityでPhotonを使うならPUNがオススメ!
20
21. PUNの特徴 / 接続処理
• 接続処理はStaticクラスのメソッドを呼び出すだけ
– PhotonNetwork.ConnectUsingSettings();
• Photon Unity Networking/Resources/PhotonServerSettingsを見て
Photonへ接続しロビーに入る
• ちなみに、PhotonServerSettingsはResources直下であれば、別の
フォルダに移動しても可
21
22. PUNの特徴 / ルーム接続
• Photonではロビー→ルームの順に接続を行う
– 病院の待合室(ロビー)と診察室(ルーム)
– 実際のゲームはルーム(診察室)で行われる
• PhotonNetwork.JoinOrCreateRoom();
– ロビーに居るとき実行可能
– ルームに入室、ルームが無ければ作成を行う
22
24. PUNの特徴 / オブジェクト同期
• PrefabにPhotonViewコンポーネントを追加するだけ
– Transform / Rigidbody / 作成したScriptを同期できる
– Transformを同期するにはPhotonTransformView,
Animatorを同期するにはPhotonAnimatorView を利用す
るとより簡単に同期できる
– コーディングなしでネットワーク同期が可能
• シリアライズのコードを書けばそれ以外のデータも同期可能
24
28. 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 }
29. 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}
30. 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ファイルに事前に設定しておいた接続
先に接続を行う事が可能です
31. 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()が便利です。
32. 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!!”というパラメータを持たせて
います。送信先はプレイヤー全員です(後述)。
33. 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を継承する必要がある。
34. 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では引数を設定し、引き渡すことも可能です。
デモ
38. Effective PUN
• RPCを極める
• マスタークライアントの切替
• オフラインモードを使ってみる
• WebRPCを使ってみる
• マッチメイキングハック
• ルームリストの取得を高速化する
• Photonのコールバックと仲良くなる
• PhotonでUniRxを使ってみる
38
39. Effective PUN
• RPCを極める
• マスタークライアントの切替
• オフラインモードを使ってみる
• WebRPCを使ってみる
• マッチメイキングハック
• ルームリストの取得を高速化する
• Photonのコールバックと仲良くなる
• PhotonでUniRxを使ってみる
39
間に合わなかった
43. PhotonTargetsについて
43
All Others MasterClient
自分自身 サーバ経由せずに実行 実行しない
他のプレイヤー サーバ経由後に実行する サーバ経由後に実行する
後から入ったプレイヤー 実行しない 実行しない
マスタークライアント マスタークライアントのみ実行
オフラインモードの場合 自分自身のみ実行する 実行しない マスタークライアントのみ実行
AllBuffered OthersBuffered AllViaServer AllBufferedViaServer
自分自身 サーバ経由せずに実行 実行しない サーバ経由後に実行する サーバ経由後に実行する
他のプレイヤー サーバ経由後に実行する サーバ経由後に実行する サーバ経由後に実行する サーバ経由後に実行する
後から入ったプレイヤー ルーム入室後に実行する ルーム入室後に実行する 実行しない ルーム入室後に実行する
マスタークライアント
オフラインモードの場合 自分自身のみ実行する 実行しない 自分自身のみ実行する 自分自身のみ実行する
デモソースコード
44. RPCのパラメータについて
• 一例として
– マスタークライアントに対してのみメッセージを送信したい場合
• photonView.RPC("RPCTest", PhotonTargets.MasterClient, “To MasteClient");
– 自分以外の他のプレイヤーと、ルームに後から入室した人に対して
メッセージを送りたい(バッファしたい)場合
• photonView.RPC(“RPCTest”, PhotonTargets.OthersBuffered, “他のプレイヤーとバッ
ファ");
• 用途に応じて使い分けが可能、柔軟性が高く使いやすい
44
47. 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を送信
48. 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 }
マスタークライアントが変更された時の
コールバックが用意されている
パラメータに新しいプレイヤーの情報を含む
58. 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
62. 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されます。
64. 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
65. 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 が返ってくる
66. 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のレスポンスが正しいか判定
67. 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文字列として利用できます
73. 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で
申し訳ないです
適当にランクをセット
74. 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でレベルやランクといった情報を設定し、
カスタムプロパティとしてセットします
75. 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に、カスタムプロパティの
キー情報をセットします。
76. 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 }
ルームが無い場合、ルームリストを取得し同じ名前のルー
ムが無いことを確認してルームを作成します
77. 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が一致すると入室を行う。