1時間でわかるプラグ
イン開発とその実際
Photon運営事務局
シニアテクニカルアドバイザー
並木 健太郎
アジェンダ
• Photonの外部連携方法
• Pluginとは、その詳細
• Enterprise CloudとPlugin
• デモ・ハンズオン
• ハンズオン内容解説
• まとめ
2
Photonの外部連携方法
3
Photonのしくみ
• すべてのクライアント(=
プレイヤー)はサーバーへ
接続する
• サーバーはクライアント
間の通信を仲介する役割
4
Photon
Client Client Client
SDK SDK SDK
大前提として…
• Photonはストレージ機能などは一切持っていま
せん
• アカウント管理などで何かしらのストレージが
必要になる場合が一般的
• データ保存に関しては、今まで通りWebサービ
ス&ストレージ(DBなど)で作ってください
5
WebサービスとPhoton
• WebサービスとPhoton
は必ずしもつなげる必
要はありません
• Webサービスでマルチ
プレイの準備をして、
Photonでマルチプレイ
を行い、その結果を
Webサービスに戻せば
基本的にはOK
6
Client
Client
Web
Service
準備
Client
Client
Photonマルチプレイ
Client
Client
Web
Service
結果
でもそれじゃ不安…
• マルチプレイ中の状況を把握したい
• ゲームのコントロールをサーバー側から行いたい
• チート対策をしたい
プレイ中にPhotonとの連携が
必要になる場合も!
7
Photonの外部連携方法
• Webhooks & WebRPC
• Photon Serverでカスタマイズ
• Pluginでのカスタムコード実装
8
Webhooks & WebRPC
• ルーム内でのイベント発生時(ルーム作成、入
室、プロパティ設定など)に予め設定しておい
たWebサービスを呼び出すことができる
• 情報はPhoton -> Webサービスの片方向
(WebRPCの情報は逆も可)
• Photonのサーバー側は設定するだけでコーディ
ングの必要性なし
9
Photon Serverでカスタマイズ
• Photonのサーバー側アプリに独自コードを入れ
ることにより、あらゆる挙動を変更したり、
ゲーム独自の動作をさせることが可能
• 自前の運用が必須、アップデートの管理なども
お客さま自身で行う必要あり
• ルームだけでなくロビーもカスタマイズ可能
10
Pluginでのカスタムコード実装
• Webhooksと同様にルーム内のイベント発生時
にコールバックとして呼ばれ、サーバー内でカ
スタムコードを動かすことが可能
• 基本はイベントドリブンになるが、タイマーを
使うことにより主体的に動くことも可能
• ルーム内でのみ動作
11
自由度と手軽さ、運用負荷
• 自由度
Webhooks << Plugin < Serverカスタマイズ
• 手軽さ
Serverカスタマイズ << Plugin < Webhooks
• 運用負荷
Webhooks << Plugin <<< Serverカスタマイズ
12
Pluginとは、その詳細
13
Pluginとは
• .NET(C#)のDLL
• Photon ServerのLoadBalancingアプリの上で動
作し、ルーム内イベントで動く
• ひとつのアプリで使えるPluginは1DLLだが、
1DLL内に複数のPluginを実装することは可能
14
Pluginの内部構造
15
Hive
PluginPluginFactory : IPluginFactory
var plugin = new HogePlugin();
return plugin;
1.
2.
HogePlugin : PluginBase(IGamePlugin)
{
public OnCreate(...) { ... }
}
HogePlugin : PluginBase(IGamePlugin)
{
public OnCreate(...) { ... }
}
HogePlugin : PluginBase(IGamePlugin)
{
public OnCreate(...) { ... }
}
3.
PluginFactory
• Pluginのインスタンスを生成するためのクラス
• HiveよりRoom生成時に呼ばれる
– 実際に呼ばれるのはCreate()メソッド
– ここで指定されたPlugin名やconfigを受け取る
• 指定された情報を元にPluginのインスタンスを
生成し、戻り値として返すのが仕事
16
最小実装例
namespace HogePlugin {
public class PluginFactory : Photon.Hive.Plugin.IPluginFactory {
public IGamePlugin Create(...) {
var plugin = new HogePlugin();
if (plugin.SetupInstance(gameHost, config, out errorMsg)) {
return plugin;
}
return null;
}
}
}
17
なんでFactoryがいるの?
• Pluginを1つだけ動かすなら不要でした
• 複数のPluginをサポートする0.9より実装
• Plugin名を見て生成するインスタンスを分ける
必要がある
• Plugin生成を柔軟に対応できる
18
ClientとPlugin
• Room作成時、RoomOptions.Pluginsを設定する
ことによりPluginのリクエストが可能
– RoomOptions.Plugins = new string[] { "SomePlugin" };
• 配列になっていますが、現在は1つ目のみサポー
ト
• PluginFactoryで失敗した場合は、Clientに
PluginMismatchのエラーが返る
19
Pluginの実クラス
• IGamePluginを実装する
• 実際は最低限を実装している
Photon.Hive.Plugin.PluginBase
を継承して実装するのがおすすめ
– 必要なメソッドだけ実装することが可能
20
プロパティ
• Name: Plugin名
– ゲーム開始後、ClientがどのPluginで動いているかを
確認する
• Version: Pluginのバージョン
– 同上。設定側で決めることも可能
• PluginHost: PluginのホストI/F
– Hive側とのやりとりを行うクラス
21
各コールバックの説明
• ICallInfoの説明が必要なので、もう少し待って
ください…
22
ICallInfo
• 各コールバックのパラメータはICallInfoをベー
スに作られています
• 実際はICallInfoからそれぞれのコールバック毎
に再定義されています
– 例: ICreateGameCallInfo : ICallInfo
23
ICallInfoの重要な仕事
• リクエストデータなどにアクセスするため
– ICallInfo.Request
• このリクエストを処理するかどうかをHiveに伝
える
– ICallInfo.Continue(), Cancel(), Fail(), Defer()
24
ICallInfoのMethods
• Continue(): 処理継続
– 通常通り処理します
• Cancel(): 静かに破棄
– 処理は中断、クライアントへのエラー通知なし
• Fail(): エラー
– 処理は中断、エラーがクライアントに返される
• Defer(): 処理を遅延
– 処理を一旦遅らせ、後で再度判断を行う
25
どう使うの?
• Fail()
– Room作成時に条件を満たさないユーザーのRoom作
成を拒否する(Room参加時も同様に)
• Cancel()
– 不正なイベントが送られてきたので、破棄させる
• Defer()
– 外部へHTTPアクセスする場合で、レスポンスの内容
を確認してから処理する
26
コールバック一覧
• BeforeCloseGame()
• BeforeJoin()
• BeforeSetProperties()
• OnCloseGame()
• OnCreateGame()
• OnJoin()
• OnLeave()
• OnRaiseEvent()
• OnSetProperties()
• OnUnknownType()
• SetupInstance()
27
Room作成・入室
• Roomが存在しない場合
– OnCreateGame()
• Roomが存在する場合
– BeforeJoin()
• 入室させる前に情報を収集してOnJoin()で判断が可能
– OnJoin()
28
Room退出・削除
• RoomからPlayerが退出した時
– OnLeave()
• RoomがServer上のメモリから削除される時
(EmptyRoomTTL経過後)
– BeforeCloseGame()
– OnCloseGame()
29
イベント受信・プロパティ設定
• イベント受信
– OnRaiseEvent()
• httpForwardのフラグに関わらず送信される
• Cancel()も可能
• プロパティ設定(Room, Player共通)
– BeforeSetProperties()
• ここでCancel()が可能
• ここでContinue()すると、プロパティの設定が行なわれる
– OnSetProperties()
30
Pluginの準備
• SetupInstance()
– PluginFactory()から呼ばれる
– Pluginの初期化を行い、利用できる状況にする
31
Pluginのインスタンス
• PluginのインスタンスはRoom毎に作られる
• Roomの新規作成か復元時に生成され、Roomか
ら全員退出しTTLを超えるまで存在
• SetupInstance()とOnCreateGame()は同じよう
なタイミングで呼ばれますが、全く違うもので
す
– SetupInstance()はPluginの初期化
– OnCreateGame()はRoom作成処理
32
スレッドモデル
• Pluginのインスタンス毎
• 1Room内は非同期アクションを含め1スレッド
なので、Room内でスレッドを意識する必要は
なし
• Roomをまたいで共有メモリなどを使う場合は
注意を
33
Pluginの中でできるアクション
• イベント送信
• タイマー
• HTTPアクセス
• プロパティ設定
• キックアウト
34
イベント送信
• IPluginHost.BroadcastEvent()を使って送信
• ターゲット指定により2つのオプション
– 送信先のActorNrのListを渡す
– targetGroupで送信先を指定する
• senderActorを0にするとServerから送られたと
Clientは認識
• イベントコードやデータの扱いはクライアントから
の扱いと同様
35
使用例
public void TimerAction() {
...
PluginHost.BroadcastEvent(ReceiverGroup.All, 0, 0, 1, new Dictionary<byte, string>
{ 1, "test" }, 0);
}
36
class TestPlugin : PluginBase {
...
this.BroadcastEvent(2, new Directory<byte, string> { 1, "params" });
タイマー
• 1回のみと繰り返しの2種類を用意
• Actionを指定して、初回起動までの時間、繰り
返しの場合は2回目以降の時間を指定すればOK
37
使用例
public override void BeforeSetProperties(IBeforeSetPropertiesCallInfo info) {
PluginHost.CreateOneTimeTimer(
() => info.Continue(),
2000);
info.Defer();
}
38
public void TimerAction() { ... }
object timer = PluginHost.CreateTimer(this.TimerAction, 2000, 1000);
...
PluginHost.StopTimer(timer);
HTTPアクセス
• 外部へのHTTPアクセスは
IPluginHost.HttpRequestを利用してください
• HttpRequestクラスでリクエストを構築し、
IPluginHost.HttpRequestで送信
• 同期、非同期どちらも可能です
39
実装例-非同期
public override void OnRaiseEvent(IRaiseEventCallInfo info) {
...
HttpRequest request = new HttpRequest() {
Callback = OnHttpResponse,
Url = "https://hogehoge.com/hogehoge",
Async = true,
UserState = info
};
PluginHost.HttpRequest(request);
info.Defer();
}
40
実装例-非同期 (続き)
public override void OnHttpRequest(IHttpReponse response, object userState) {
ICallInfo info = userState as ICallInfo;
...
if (info.IsDeffered) {
info.Continue();
}
}
41
実装例-JSON
string json = "...";
var stream = new MemoryStream();
var data = Encoding.UTF8.GetBytes(json);
stream.Write(data, 0, data,length);
HttpRequest request = new HttpRequest() {
Callback = callback,
Url = "https://api.hoge.com/api/json",
DataStream = stream,
Method = "POST",
ContentType = "application/json"
};
PluginHost.HttpRequest(request);
42
プロパティ設定・キックアウト
• Plugin内からのカスタムプロパティの設定が可
能です
– IPluginHost.SetProperties()
• キックアウト
– IPluginHost.RemoveActor()
43
Pluginのマニュアルについて
• その他詳細については、下記をご覧ください
• http://doc.photonengine.com/ja-
jp/onpremise/current/plugins/manual
• http://doc-
api.photonengine.com/en/plugins/current/index.h
tml
44
名前空間、クラス名
• Namespaceは自由に設定してください
– が、Plugin内では統一してください
• Plugin名にDefaultは使えません
• Pluginの名前はクラス名と合わせておくのがわ
かりやいでしょう
45
開発を始めるにあたり
• 開発はPhoton ServerのSDKページにある、
Plugin SDK
をダウンロードして、Webhooksのソリュー
ションとプロジェクトをコピーしてください
• 今日ご提供するPluginTemplateプロジェクトも
是非ご利用ください
46
サンプル
• サンプルは、Server SDK (Plugin SDKでない)に
ある、LoadBalancingソリューション内の
TestPluginsプロジェクトがお勧めです
• 十数個のPluginを1つのDLLでまとめているので、
複数Pluginのサンプルとしてもお勧めです
47
Pluginでの実装範囲
• Photon内部で完結する処理はPlugin内にて実装
– 特定のイベントを受信したら、プロパティを設定す
る
– 定期的に情報を更新してイベントを発生させる
• 外部ストレージが絡む場合などは処理そのもの
は外部で行い、HTTPでアクセス
– 直接DBにアクセスなどは決してお勧めしません
48
Pluginでの実装範囲
49
Photon
Plugin Web Server
DB
OnSetProperties
値チェック
⇨ここで実装
OnJoinRoom
参加者
チェック&記録
⇨HTTPで呼ぶ
参加者
チェック&記録
⇨ここで実装
Pluginのメリット
• Photon Serverのコードと、カスタムコードを完
全に分離することが可能
• 独立してデプロイができ、互いに干渉せずに
アップデートも可能!
• Photon Serverをご検討でも、ぜひPluginでカス
タマイズしてみてください!
50
Enterprise CloudとPlugin
51
Pluginのデプロイ
• 各サーバーにPlugin DLLを設置すればOK
• Photon Serverの再起動は必要です
• 設定の変更はXMLの設定ファイルを変更する必
要あり
52
Pluginで運用負荷は減りますが…
• 多くのサーバーがある場合、Plugin DLLをアッ
プデートするのは面倒
• Plugin設定を1台ずつ変更するのも面倒…
もっと楽しませんか?
53
Enterprise Cloudとは
• Photon Cloudの上位サービス
• Private Cloudとして、サーバーを専有
• 複数のタイトルでの利用も可能
• クラウドサービスなので、
サーバー運用不要
• 運用体制も万全
54
Enterprise Cloudでのデプロイ
• PluginのDLLなどをZIPで固めて、PowerShellの
ツールでアップロードすれば完了!
• 複数バージョンに対応、適用はダッシュボード
でVersionを切り替えるだけ、ロールバックも簡
単
• Photon ServerのPluginそのまま使えます
55
Enterprise Cloudでの設定
• ダッシュボード上で、設定値を変更すれば反映
される
56
ステージング対応
• アプリケーション(AppID)毎にPluginの設定を変
更可能
• ステージング用AppID、本番用AppIDを分けるこ
とにより、ステージング環境で十分テストをし
て、その後本番に適用させることも可能
57
Cloud vs. Enterprise Cloud
Cloud
• Publicサービス (共有)
• 5,000CCUまで
• Webhooks対応
• 複数タイトル共有不可
• サーバー設定変更不可
Enterprise Cloud
• Privateサービス (専有)
• CCU上限なし
• Webhooks &
Plugin対応
• 複数タイトル共有可能
• サーバー設定変更可能
58
Enterprise Cloud + Plugin
• PhotonもPluginも運用フリー!
• ClientとPluginの開発に注力できます!
• 開発工数、運用工数を削減して、コンテンツ開
発に注力してください!
59
デモ・ハンズオン
60
ハンズオンの目標
• プラグイン開発を本格的に始められるまで
• テンプレートとしてご用意しています
• ここを覚えておけば、あとは自前のコードを書
くだけ!
• マルチプラグインの実装もこれで理解できます
61
まずはダウンロード
• Webページにある4つをダウンロード
– Plugin Template プロジェクト
– Photon Plugin SDK 4.0.29
– Photon Server SDK 4.0.29
– デモクライアント(いずれか)
62
Plugin SDKとPlugin Template展開
• Plugin SDKを実行して、
任意のフォルダに展開
• Plugin Templateのブロッ
クを解除
• PluginTemplateを下記に
展開
– (plugin-sdk)¥src-
server¥Plugins
63
Visual Studio起動
• Visual Studioを起動
– 空のソリューションとするために、ファイルから開
くのではなくスタートメニューで起動
• PluginTemplateプロジェクトをオープン
– PluginTemplateフォルダにある
PluginTemplate.csproj
をオープン
64
ソリューション保存
• 一旦ソリューションを保存
• ソリューション名
– PluginTemplate
• 保存先
– (plugin-sdk)¥src-server¥Plugins
65
プロジェクト設定変更
• PluginTemplateプロジェク
トのプロパティを開く
• デバッグのタブを選択
• 外部プログラムを再選択
– (plugin-
sdk)bin_Win64¥PhotonSock
etServer.exe
• コマンドライン引数の最後
を変更
– bin_Win64
• 変更が終わったら保存
• aa
66
Plugin読み込み設定変更
• (plugin-
sdk)¥deploy¥LoadBalancing¥GameServer¥bin¥
Photon.LoadBalancing.dll.configを開く
• 254行目からのPluginSettingsを見つける
• 256行目からのPluginを削除、Webページ通り記
載の内容に置換
67
ビルド & デバッグ開始
• ビルドを実行
• 下記メッセージが2つ出ればビルド成功
– 5個のファイルをコピーしました
• 続いてデバッグを開始
– 停止せず動いていればOK
• Windowsファイアウォールのメッセージが出た
場合は許可を
68
デモクライアント起動
• テストクライアント
を起動
• 接続先は
127.0.0.1(localhost)の
ままで
• 使用Pluginの指定はお
好みで
69
デモクライアント操作
• Enterキーでイベント
コード10を送信
• Spaceでイベントコー
ド5を送信
• イベントを受信する
と画面に表示
70
動きの解説
71
全体の構造
• PluginFactory
• 2つのプラグイン
– Plugin1
– Plugin2
72
PluginFactory
• Pluginインスタンスを生成するPluginFactory
• pluginNameを受け取って、該当するpluginのイ
ンスタンスを生成
• pluginNameを細かく解釈して、Plugin名とパラ
メータと分けて生成することも可能
73
Plugin1
• EventCode = 10を受信したら、EventCode = 20
のイベントを全クライアントに送信する
• OnRaiseEvent()では基底クラスのメソッドを先
に呼んで処理を進めていますが、キャンセルな
どを行う可能性があるなら、基底クラスのメ
ソッドは呼ばないで
74
Plugin2
• SetupInstance()にてタイマー生成
• 間隔は最初1000ms、以後も1000ms
• タイマーのコールバックメソッドtimerAction()
にて、イベントを生成
• EventCodeは20からインクリメント、100まで
いったら20に戻る
75
Plugin開発のTips
76
Plugin名
• 1タイトル内で全く挙動の異なるPluginが必要な
ら、このテンプレートのように複数のPluginを
使い分けるほうが効率的
• Plugin名を工夫してパラメータを渡すことも可
能だが、インスタンスの再利用時に混乱しない
よう、注意すること
– Pluginが返すNameプロパティが同一の場合、Photon
内でインスタンスを再利用する場合もある
77
インスタンス再利用
• すでに生成済みかつルームが非アクティブ(=メ
ンバー全員非アクティブ)で、要求Plugin名と
Nameプロパティが一致する場合、インスタン
スを再利用することがある
• その場合、PluginFactoryやSetupInstance()が呼
ばれないので、それらが必ず呼ばれることを前
提にした実装にはしない
78
イベント処理
• OnRaiseEvent()は、Webhooksと違い全てのイ
ベントにおいて呼ばれる
• 本当に必要なイベントのみ検知し、監視・介入
を行うこと
• Webhooksと同様、Request内のHttpFowardを見
てそのイベントだけ処理する方法も
79
ICallInfoのメソッド
• ICallInfoのメソッドである
Continue(), Cancel(), Fail(), Defer()
は可能な限り早く呼び出してください
• さもないと、Photon自身の処理に大きく影響し
ます
• 必要のないイベントは早急にContinue()で処理
継続を
80
PluginTemplateからの始め方
81
やるべきこと
• PluginTemplateフォルダコピー
• ファイル名変更
• 名前空間、クラス名変更
• プロジェクト設定変更
• Photon Server設定変更
82
フォルダコピー
• (plugin-sdk)¥src-server¥Plugins¥
PluginTemplate
フォルダを任意の名前で丸ごとコピー
83
ファイル名変更
• Plugin本体をクラス名に合わせてファイル名を
変更
– 1つだけなら、Plugin2.csは削除
84
名前空間、クラス名変更
• PluginFactory、Plugin本体の名前空間を変更
– Photon配下でなくても全然OK
– ただしDLL内は統一すること
• PluginFactoryのクラス名はそのままを推奨
• Plugin本体は任意の名前で
– Plugin名とクラス名は一致させておくと楽です
85
プロジェクト設定変更
• 「アプリケーション」タブ
– アセンブリ名、既定の名前空間
– アセンブリ情報内
• タイトル、アセンブリバージョン、ファイルバージョン、GUID
• 「ビルド イベント」タブ
– ビルド後イベントのコマンドライン
• 「デバッグ」タブ
– 開始動作 - 外部プログラムの開始
86
Photon Server設定変更
• 68ページのPlugin読み込み設定変更と同様
87
まとめ
88
まとめ
• Pluginはロジック組み込みを簡単にします!
• Photon Enterprise Cloudは運用フリーで柔軟性
の高いクラウドサービスです!
• Photon Enterprise Cloud + Pluginは最強の組み
合わせです!
• Photon ServerでPluginも有効です!
89
Photon SNSアカウント
• Twitter
– @PhotonCloudJP
• Facebook
– https://www.facebook.com/photoncloudjp/
• ヘルプセンター
– https://support.photonegine.jp/hc/ja
90
ありがとうございまし
た!
developer@photonengine.jp

【Photon勉強会】1時間でわかるプラグイン開発とその実際(2017/3/23講演)