Photonプラグインマニュアル
Photon 3では継承によっておこなわれていたゲーム/ルームの動作を拡張する新しい方法として、Photon 4ではプラグインが導入されています。
ベストプラクティスとよくある質問については、Photon Plugins FAQを参照してください。
イントロダクション
PhotonLoadbalancing
は、クラウド上での「そのまま」(サーバ側のカスタムコード無し)の形式と、カスタム動作で拡張されたセルフホスティングの両方の環境で、ルームベースゲームのためのプラットフォームとして進化してきました。 数年間にわたって機能セットは進化してきましたが、クライアントとサーバー間のコアフロー(ゲーム作成、参加、退室などのオペレーションとそれらのイベント)は非常に安定しているため、下位互換性は維持されています。
Photon 3までは、カスタマイズはソースコード (Lite
および Loadbalancing
)にもとづいており、一般的にゲームクラスから継承していました。
これは柔軟でしたが、クライアント、ゲームサーバーとマスターとの間のフローに予期せぬ中断が発生する可能性があるので(クライアントがレスポンスまたはイベントを待機して「スタック」する場合など)開発上は柔軟性に欠け、より複雑でした。
プラグインAPIはコアフローの近くに設計されています(ルーム作成、参加、退室など)。
- 高度な柔軟性を維持: その処理の前後のコアフローにフックすることができます。
- フローを破壊する可能性を最小化: クライアントとサーバー上のエラー提供の迅速化に失敗。
- ロックフリーコードの使用を許可: プラグインのインスタンスは一度に複数のコールを受けることはありません。また、フレームワークは基礎となるPhotonメッセージパッシングアーキテクチャ(ファイバー)に統合されるHTTPクライアントとタイマーを提供します。
- 複雑さを低減させ、利便性を向上します: 「最小限のプラグイン」」を参照してください。
概念
カスタムのサーバーロジックを追加するには、あらかじめ定義されたPhotonのサーバーhookにコードを注入します。 hookはルームのイベントによってトリガーされるので、現在Photon ServerはGameServerプラグインにしか対応していません。
定義上、Photonのプラグインには固有の名前があり、それらのイベントコールバックを実装します。
カスタムのプラグインはプラグインアセンブリというDLLファイルにコンパイルされます。 その後、必要なファイルはPhotonサーバー上でデプロイされるか、Enterprise Cloudにアップロードされます。
設定されたプラグインアセンブリは各ルームの作成で、実行時に動的に読み込まれます。
次に、プラグインのインスタンスが ファクトリパターンにもとづいて作成されます。
プラグイン作成をトリガーするルームは、「ホスト」ゲームと呼ばれます。
後者はプラグインから直接アクセスすることが可能で、どちらも同じライフサイクルを共有しています。
WebhookはPhotonプラグインの良い例です。
Webhook 1.2のソースコードは、プラグインSDKで提供されています。
確認していただくことをお勧めします。
基本フロー
Photonのhookメカニズムには、6段階のフローがあります:
- hookコールを受信
コールバックがトリガーされると、ホストは制御をプラグインに転送します。 - [任意] コール情報を変更
処理される前に、クライアント/サーバーによって送信されたリクエストにアクセスおよび変更します。 - [任意] カスタムコードを注入
コールを処理する前にホストとインタラクトします(例:HTTPリクエストの送信、ルーム/アクターのクエリ、タイマーの設定など)。 - hookコールを処理
リクエストの方法と処理を決定します(「ICallInfo Processing Methods」を参照してください)。 - [任意] カスタムコードを注入
処理が完了すると、クライアント/サーバーによって送信されたリクエストは「読み取り専用」となります。
ただし、処理後もプラグインはホストとインタラクトすることができます。 - 返却
プラグインはホストに制御を返します。
はじめに
最小限のプラグイン
プラグイン
プラグインの作成をおこなうお勧めの容易な方法は、直接すべてのIGamePlugin
メソッドを実装するのではなくPluginBase
クラスを拡張することです。
これによって、必要なものだけをオーバーライドすることができます。
最小限のプラグインの実装を取得するには、PluginBase.Name
プロパティをオーバーライドするだけです。これは、プラグインを識別するものです。
C#
namespace MyCompany.MyProject.HivePlugin
{
public class CustomPlugin : PluginBase
{
public override string Name
{
get
{
return "CustomPlugin"; // anything other than "Default" or "ErrorPlugin"
}
}
}
}
ファクトリ
プラグインファクトリクラスはプラグインアセンブリーの一部として実装される必要があります。 これは、ルームごとのプラグインインスタンスの作成を行います。
簡略化するため、次のスニペットではファクトリはクライアントからリクエストされたプラグイン名を確認せずに、デフォルトでCustomPluginインスタンスを返します。
プラグインのインスタンス化をトリガーするルームはIPluginFactory.Create
メソッドにIPluginHost
パラメータとして渡されます。
プラグイン自体の中のゲームへの参照を保持するため、同じパラメータをIGamePlugin.SetupInstance
に渡す必要があります。
IPluginHost
はルームデータおよびオペレーションへのアクセスを提供します。
C#
namespace MyCompany.MyProject.HivePlugin
{
public class PluginFactory : IPluginFactory
{
public IGamePlugin Create(
IPluginHost gameHost,
string pluginName, // name of plugin requested by client
Dictionary<string, string> config, // plugin settings
out string errorMsg)
{
var plugin = new CustomPlugin();
if (plugin.SetupInstance(gameHost, config, out errorMsg))
{
return plugin;
}
return null;
}
}
}
設定
Enterprise Cloud
新しいプラグインを追加するには:
- サポートされている、いずれかのPhoton製品タイプのダッシュボードに進みます。
- そこに記載されているPhotonアプリケーションの管理ページの1つに進みます。
- ページ下部にある「新しいプラグインの作成」ボタンをクリックします。
- 文字列型のキー/値のエントリーを追加して、プラグインを設定します。
設定は、文字列のキー/値のセットを定義しておこないます。 各文字列に許容される最大長は256文字です。必要な設定は以下のとおりです:
AssemblyName
: プラグインを含み、アップロードされたDLLファイルの正式な名前。Version
: プラグインのバージョン。Exit Gamesによって提供されるPowerShellスクリプトを使用してプラグインファイルをアップロードする際に使用、または返されるのと同じバージョンの文字列。Path
: アセンブリファイルへのパス。以下の形式にする必要があります:「{customerName}\{pluginName}」Type
: 使用するPluginFactory
クラスの正式な名前。以下の形式となります:「{plugins namespace}.{pluginfactory class name}」
オンプレミス
適切なGameServerアプリケーションの「Photon.LoadBalancing.dll.config」ファイルで以下のXMLノードを追加、または修正する必要があります ("deploy\LoadBalancing\GameServer\bin\Photon.LoadBalancing.dll.config")。
<Plugin.../>要素のデフォルトの属性を、以下の例に示します。 「Version」のみが不要です。
プラグインコードに渡すための、その他の設定キー/値のセットは任意で追加できます。
プラグインの有効化/非有効化は、<PluginSettings>
要素のEnabled
属性の値を変更することで簡単におこなえます。
XML
<PluginSettings Enabled="true">
<Plugins>
<Plugin
Name="{pluginName}"
Version="{pluginVersion}"
AssemblyName="{pluginDllFileName}.dll"
Type="{pluginNameSpace}.{pluginFactoryClassName}"
/>
</Plugins>
</PluginSettings>
プラグインDLL、依存関係、およびビルドで必要な他のファイルはフォルダ「"deploy\plugins{pluginName}{pluginVersion}\bin"」に格納しなければなりません。 その際にはプラグイン名で設定された値に準拠します。
このため、少なくとも以下の2つのファイルが存在する必要があります:
- "deploy\plugins\{pluginName}\{pluginVersion}\bin\{pluginDllFileName}.dll"
- "deploy\plugins\{pluginName}\{pluginVersion}\bin\PhotonHivePlugin.dll"
もし「Version」設定キーが使用されていない、またはその値が空の場合、パスは「deploy\plugins{pluginName}\bin\」となります。 また「\」は「\」とみなされるため、予期されるパスは「deploy\plugins{pluginName}\bin\」です。
このため、少なくとも以下の2つのファイルが存在する必要があります:
- "deploy\plugins\{pluginName}\bin\{pluginDllFileName}.dll"
- "deploy\plugins\{pluginName}\bin\PhotonHivePlugin.dll"
プラグインファクトリ
私たちのプラグインモデルはファクトリ設計パターンを使用しています。プラグインはオンデマンドで名前からインスタンス化されます。
単一のPhotonのプラグインアセンブリには、複数のプラグインクラスと1つのアクティブな「PluginFactory」を含むことができます。
複数の「PluginFactory」を作成することは可能ですが、特に必要はありません。 一方、複数のプラグインクラスを書くことは非常に有用です。
たとえば、ゲームタイプ(またはモードや難易度など)ごとにプラグインを持つことができます。 ファクトリは、サーバーの複数のプラグインバージョンに使用可能です。
使用例については、 こちら.を参照してください。
クライアントはroomOptions.Plugins
を使用して、プラグインの設定をリクエストするルームを作成します。
roomOptions.Plugins
はstring[]
型であり、最初の文字列 (roomOptions.Plugins[0]
) はファクトリに渡されるプラグインの名前にする必要があります。
例:
roomOptions.Plugins = new string[] { "NameOfYourPlugin" };
または
roomOptions.Plugins = new string[] { "NameOfOtherPlugin" };
クライアントが何も送信しない場合、サーバーはデフォルト(何も設定されていない場合)、または設定されている場合はプラグインファクトリが作成時に返すものを使用します。
PluginFactory.Create
で返されたプラグインの名前がクライアントによってリクエストされたものと一致しない場合は、プラグインはアンロードされ、クライアントの作成または参加オペレーションは失敗してPluginMismatch (32757)
エラーとなります。
また、現在roomOptions.Plugins
には最大で1つの要素(プラグイン名の文字列)が含まれている必要があります。
複数の要素が送信された場合(roomOptions.Plugins.Length > 1
)、PluginMismatchエラーが受信され、ルームへの参加または作成は失敗します。
ファクトリでは名前を使用して、対応するプラグインを以下のように読み込みます:
C#
public class PluginFactory : IPluginFactory
{
public IGamePlugin Create(IPluginHost gameHost, string pluginName, Dictionary<string, string> config, out string errorMsg)
{
PluginBase plugin = new DefaultPlugin(); // default
switch(pluginName)
{
case "Default":
// name not allowed, throw error
break;
case "NameOfYourPlugin":
plugin = new NameOfYourPlugin();
break;
case "NameOfOtherPlugin":
plugin = new NameOfOtherPlugin();
break;
default:
//plugin = new DefaultPlugin();
break;
}
if (plugin.SetupInstance(gameHost, config, out errorMsg))
{
return plugin;
}
return null;
}
}
ICallInfo
処理メソッド
プラグインの概念は、受信リクエストを処理する前または後に「通常」のPhotonフローにフックすることです。
開発者は、リクエストのタイプに応じ、以下の4つのいずれかの方法を使用して何を行うかを決定します:
Continue()
: デフォルトのPhotonの処理を再開するために使用。Cancel()
: 静かに処理をキャンセルするために使用します。つまり、クライアントにエラーやその他の通知は送信されません。 これによって、それ以降の処理がスキップされます:OnRaiseEvent
内で呼ばれると、受信イベントは無視されますBeforeSetProperties
内で呼ばれると、プロパティ変更がキャンセルされます。
Fail(string msg, Dictionary<byte,object> errorData)
: クライアントにエラーレスポンスを返し、それ以降の処理をキャンセルするために使用されます。 クライアントからは、OperationResponse.DebugMessage
でmsg
パラメータ、OperationResponse.Parameters
でerrorData
を取得できます。Defer()
: Photonは制御が返される前に処理メソッドの1つが呼ばれることを予期します。 このメソッドは処理を遅延させるために使用されます。これは、後から継続できるように使用することが可能です。たとえば、タイマーのコールバック内、または非同期HTTPリクエストを作成する場合などです。この使用例については、 アウトバウンドHTTP の項目をご覧ください。
注:
- プラグインは、デフォルトでstrictモードを有効にすべきです(
UseStrictMode = true
)。
StrictモードではICallInfo処理メソッドのいずれかへの呼び出しが、各プラグインのコールバックで行われます。 利用可能なメソッドをまったく呼び出さないと、例外が発生します。 IGamePlugin
コールのPluginBase
実装のすべてのコールバックは、{ICallInfo}.Continue()
を呼びます。Continue()
、Fail()
、Cancel()
は一度のみ呼び出されることが想定されています。これらを再度呼び出すと例外が発生します。Defer()
およびCancel()
はOnRaiseEvent
またはBeforeSetProperties
内でのみ呼び出されます。ICallInfo
を実装するすべてのクラスは利用可能な場合、クライアントの元のオペレーションリクエストを公開します。
{ICallInfo}.OperationRequest
(またはRequest
)プロパティからオペレーションコードとパラメータを取得できます。ICallInfo
を実装するすべてのクラスには、オペレーションリクエストのCallStatus
処理を知らせるために、ヘルパーのプロパティが含まれます。IsNew
: リクエストが処理されていないか、また延期されていないかを示しますIsProcessed
: リクエストがすでに処理されたかを示します(つまりContinue
、Cancel
、またはFail
メソッドが呼ばれたかを示します)。IsSucceeded
: リクエストが正常に処理されたか示します(つまり、Continue
メソッドが呼ばれたかを示します)。IsCanceled
: リクエストがキャンセルされたかを示します(つまり、Cancel
メソッドが呼ばれたかを示します)。IsDeferred
: リクエストが延期されたかを示します(つまり、Defer
メソッドが呼ばれたかを示します)。IsFailed
: リクエストが「失敗したか」を示します(つまり、Failメソッドが呼ばれたかを示します)。
プラグインのコールバック
Photon Serverには、あらかじめ定義されたhookが9個あります。コードでそれらのhookに明示的に登録する必要はありません。
デフォルトで、どのプラグインクラスもすべての9つのイベントを受信することができます。
ただし、必要なものを実装するべきです。PluginBase
を拡張し、必要なコールバックをオーバーライドすることをお勧めします。
すべてのコアイベントコールバックには、特定のICallInfo
コントラクトがあります。
それらのほとんどは、クライアントの動作によって直接トリガーされます。 クライアントによって送信されたオペレーションリクエストは、利用可能な場所でICallInfo.Request
に提供されます。
OnCreateGame(ICreateGameCallInfo info)
前提条件: クライアントがOpCreateRoom
、OpJoinOrCreateRoom
、またはOpJoinRoom
を呼び、ルームがPhotonサーバーのメモリで検出できない。
処理メソッド | 処理結果 |
---|---|
Continue |
|
Fail |
CreateGame オペレーションレスポンスはReturnCode == ErrorCode.PluginReportedError でクライアントに送信されます。 |
Cancel |
N/A |
Defer |
N/A |
- 注:
- リクエストを処理する前はルームの状態は初期化されず、デフォルト値が含まれています。 これは、
IPluginHost.SetGameState
を呼び出すことによってルームの状態を外部ソースから読み込んで解析し、ルームに割り当てることができる唯一の状況です。 - リクエストを処理する前に、
PluginHost.SetProperties
またはPluginHost.BroadcastEvent
へのすべての呼び出しは無視されます。 - オペレーションリクエストの種類を把握するには、
ICreateGameCallInfo.IsJoin
とICreateGameCallInfo.CreateIfNotExists
を使用できます。
- リクエストを処理する前はルームの状態は初期化されず、デフォルト値が含まれています。 これは、
オペレーションメソッド | IsJoin | CreateIfNotExist |
---|---|---|
OpCreateRoom |
false |
false |
OpJoinRoom |
true |
false |
OpJoinOrCreateRoom |
true |
true |
BeforeJoin(IBeforeJoinGameCallInfo info)
前提条件:
クライアントがOpJoinRoom
、OpJoinOrCreateRoom
、またはOpJoinRandomRoom
を呼び、ルームはPhotonサーバーのメモリ内にある。
処理メソッド | 処理結果/th> |
---|---|
Continue |
|
Fail |
JoinGame オペレーションレスポンスは ReturnCode == ErrorCode.PluginReportedError でクライアントに送信されます。 |
Cancel |
N/A |
Defer |
N/A |
- 注:
IBeforeJoinGameCallInfo
を処理する前にPluginHost.BroadcastEvent
を呼ぶ場合にはキャッシュしない限り、参加しているアクターはイベントを受信しません。
OnJoin(IJoinGameCallInfo info)
前提条件: IBeforeJoinGameCallInfo.Continue()
がBeforeJoin
で呼ばれる。
処理メソッド | 処理結果 |
---|---|
Continue |
|
Fail |
|
Cancel |
N/A |
Defer |
N/A |
OnLeave(ILeaveGameCallInfo info)
前提条件: クライアントがOpLeave
を呼び、ピアが切断するか、またはPlayerTTL
が経過。(アクターのライフサイクルを参照してください)。
処理メソッド | 処理結果 |
---|---|
Continue |
|
Fail |
OpLeave オペレーションでトリガーされた場合、そのレスポンスはReturnCode == ErrorCode.PluginReportedError でクライアントに送信されます。 |
Cancel |
N/A |
Defer |
N/A |
- 注:
PlayerTTL
はルーム作成中に設定できます。
-PluginHost.BroadcastEvent
を呼ぶ場合、退出するアクターはイベントを受信しなくなります。
OnRaiseEvent(IRaiseEventCallInfo info)
前提条件: クライアントがOpRaiseEvent
を呼ぶ。
処理メソッド | 処理結果 |
---|---|
Continue |
|
Fail |
RaiseEvent オペレーションレスポンスはReturnCode == ErrorCode.PluginReportedError でクライアントに送信されます。 |
Cancel |
静かに処理をスキップします。 |
Defer |
処理が延期されます。 |
- 注:
IRaiseEventCallInfo
が正常に処理されている場合、オペレーションレスポンスはクライアントに送り返されません。
BeforeSetProperties(IBeforeSetPropertiesCallInfo info)
前提条件: クライアントがOpSetProperties
を呼ぶ。
処理メソッド | 処理結果 |
---|---|
Continue |
|
Fail |
SetProperties オペレーションレスポンスはReturnCode == ErrorCode.PluginReportedError でクライアントに送信されます。 |
Cancel |
静かに処理をスキップします。 |
Defer |
処理は延期されます。 |
- 注:
- 変更するプロパティがルームまたはアクターに属しているかを把握するには、
IBeforeSetPropertiesCallInfo.Request.ActorNumber
の値を確認します。 0の場合、ルームのプロパティは更新されようとしています。その他の場合には、プロパティの更新を必要としているターゲットアクターの番号です。 - 前述の
ActorNumber
とIBeforeSetPropertiesCallInfo.ActorNr
を混同しないように注意してください。 後者はオペレーションリクエストを作成するアクターを指します。
- 変更するプロパティがルームまたはアクターに属しているかを把握するには、
OnSetProperties(ISetPropertiesCallInfo info)
前提条件: IBeforeSetPropertiesCallInfo.Continue()
が BeforeSetProperties
で呼ばれる。
処理メソッド | 処理結果 |
---|---|
Continue |
nil. |
Fail |
失敗のみログに記録します。 |
Cancel |
N/A |
Defer |
N/A |
BeforeCloseGame(IBeforeCloseGameCallInfo info)
前提条件: すべてのピアが切断されている。
処理メソッド | 処理結果 |
---|---|
Continue |
OnCloseGame をトリガーします。 |
Fail |
失敗のみログに記録します。 |
Cancel |
N/A |
Defer |
N/A |
- 注:
EmptyRoomTTL
はクライアントによって、ルーム作成時に設定可能です。PluginHost.BroadcastEvent
へのコールは、ルームイベントキャッシュを変更しない限り無視されます。
OnCloseGame(ICloseGameCallInfo info)
前提条件: IBeforeCloseGameCallInfo.Continue()
がBeforeCloseGame
で呼ばれ、EmptyRoomTTL
が経過。
処理メソッド | 処理結果 |
---|---|
Continue |
ルームはPhoton Serversのメモリから削除され、プラグインのインスタンスがアンロードされます。 |
Fail |
失敗のみログに記録します。 |
Cancel |
N/A |
Defer |
N/A |
- 注:
ICloseGameCallInfo
を処理する前に、ルームの状態を保存するか、永久に失うかを選択できます。 Webhookでは、ルーム内に少なくとも一人のインアクティブなアクターがいればこれを行うことができます。
高度なコンセプト
アクターのライフサイクル
ピア<-> アクター<-> ルーム
アクターはルーム内のプレイヤーです。
プレイヤーは、ルームを作成またはルームに参加することでルームに入ると、アクターとして表されます。
アクターはActorNr
と、それに続く UserId
とNickName
を使用して定義されます。 また、アクターにはカスタムプロパティを設定することができます。 プレイヤーが最初にルームに入ると、他のプレイヤーが取得することのできないアクター番号をルーム内で取得します。 また、新しい各プレイヤーに対してルームのActorsList
にアクターが追加されます。
プレイヤーが完全にルームを出ると、そのアクターはリストから削除されます。 しかし、ルームのオプションにより許可されている場合、
プレイヤーはルームを出ても後から戻ることができます。 この場合、プレイヤーが退出する際にそのプレイヤーに対応するアクターがインアクティブとしてマークされます。
イベントのタイムスタンプは、DeactivationTime
アクタープロパティに保存されます。
アクターがインアクティブの状態ルーム内に滞在できる時間を制限することができます。
この時間は、必要な値にミリ秒単位でPlayerTTL
オプションを設定することで、ルーム作成時にこの時間を定義することができます。
負の値または最大のint値に等しい場合、アクターは無期限にインアクティブで滞在することができます。 それ以外の場合は、DeactivationTime
後にPlayerTTL
が経過するとインアクティブなアクターはルームから削除されます。
それまではプレイヤーはルームに再び戻ることができます。
アクターが再び一時的にルームを出る場合は、新しいDeactivationTime
が計算され、カウントダウンがリセットされます。
このため、再入室の回数に制限はありません。
PhotonプラグインSDKでは、すべてのアクターが以下のプロパティの1つを任意の時点で使用できる方法を提供します:
IPluginHost.GameActors
にはルーム内のすべてのアクターが含まれます(アクティブおよびインアクティブ)。IPluginHost.GameActorsActive
には現在ルームに参加しているすべてのアクターが含まれています。IPluginHost.GameActorsInActive
にはルームを退出したすべてのアクター(放棄は除きます)が含まれています。
プラグインからイベントを送信
PhotonプラグインSDKを使用して、ルーム内でカスタムイベントを送信することができます。
カスタムのイベントの種類と内容は、そのコードによって定義されるべきです。
また、イベントコードは200未満にとどめる必要があります。
そのためには2つの負荷メソッドがあります。
BroadcastEvent
という名前は、イベントがブロードキャストされることを示唆していますが、
フィルタに基づいてマルチキャストを行うか、単一のアクターに送信することができます。
- アクターグループに送信:
C#
void IPluginHost.BroadcastEvent(byte target, int senderActor, byte targetGroup, byte evCode, Dictionary<byte, object> data, byte cacheOp, SendParameters sendParameters = null);
以下の値にtarget
引数を設定できます:
0
(ReciverGroup.All
): すべてのアクティブなアクター。
targetGroup
パラメータは無視されます。1
(ReciverGroup.Others
): アクター番号がsenderActor
であるアクター以外のすべてのアクティブなアクター。
targetGroup
パラメータは無視されます。
senderActor
が0
の場合、target
が0
(ReciverGroup.All
)に設定された場合と同じ挙動になります。2
(ReciverGroup.Group
):targetGroup
引数を使用して指定したインタレストグループに登録されたアクティブなアクターのみ。
ReciverGroup
enumおよび値を、PUNを含むPhotonのC#クライアントSDK内の ReceiverGroup
enumおよび値と混同しないでください。
- アクター番号を使用して特定のアクターリストに送信:
C#
void IPluginHost.BroadcastEvent(IList<int> recieverActors, int senderActor, byte evCode, Dictionary<byte, object> data, byte cacheOp, SendParameters sendParameters = null);
ルームイベントキャッシュをアップデートするには、いずれかの方法を使用することでも可能です。
cacheOp
パラメータを使用してキャッシュオプションを定義することができます。
「Photonイベントキャッシュ」についてはこちらを参照してください。
cacheOp
値は承諾されず、BroadcastEvent
コールは失敗します。
Photonイベントはイベント(送信者)の原点としてアクター番号を必要とするので、2つの選択肢があります:
- アクターになりすます:
senderActor
引数をルームに参加したアクター(アクティブなアクターである必要があります)のアクター番号に設定します。 - 「オーソリテーティブ」または「グローバル」なルームイベントを送信する:
senderActor
引数を0に設定します。
これは、アクター番号0はプレイヤーにアサインされないためです。
このため、これはイベントの原点がクライアントでないことを示すために使用することも可能です。
senderActor
と、0または6以外のcacheOp
値を組み合わせることはできません。
PluginBase
からプラグインクラスを拡張する場合(推奨)、ルームに参加しているすべてのアクターにイベントをブロードキャストするには以下のヘルパーメソッドを使用することができます:
C#
protected void BroadcastEvent(byte code, Dictionary<byte, object> data)
(Dictionary<byte, object>)eventData
ではなくnew Dictionary<byte, object>(){{245,eventData },{254,senderActorNr}}
。 以下のヘルパーまたはラッパーメソッドのいずれか1つを利用できます。
C#
public void RaiseEvent(byte eventCode, object eventData,
byte receiverGroup = ReciverGroup.All,
int senderActorNumber = 0,
byte cachingOption = CacheOperations.DoNotCache,
byte interestGroup = 0,
SendParameters sendParams = default(SendParameters))
{
Dictionary<byte, object> parameters = new Dictionary<byte, object>();
parameters.Add(245, eventData);
parameters.Add(254, senderActorNumber);
PluginHost.BroadcastEvent(receiverGroup, senderActorNumber, interestGroup, eventCode, parameters, cachingOption, sendParams);
}
public void RaiseEvent(byte eventCode, object eventData, IList<int> targetActorsNumbers,
int senderActorNumber = 0,
byte cachingOption = CacheOperations.DoNotCache,
SendParameters sendParams = default(SendParameters))
{
Dictionary<byte, object> parameters = new Dictionary<byte, object>();
parameters.Add(245, eventData);
parameters.Add(254, senderActorNumber);
PluginHost.BroadcastEvent(targetActorsNumbers, senderActorNumber, eventCode, parameters, cachingOption, sendParams);
}
```</div>
<a id="http"></a>
### アウトバウンドのHTTPコール
`HttpRequest` はHTTPリクエストを構築するヘルパークラスです。
このクラスを使用して、URLとHTTPメソッド(デフォルトは「GET」)や、必要な`Accept`および`ContentType`ヘッダーを設定することができます。
これらのプロパティの値は、[HttpWebRequest](https://msdn.microsoft.com/en-us/library/system.net.httpwebrequest(v=vs.110).aspx)で サポートされる必要があります。
また、他のカスタムHTTPヘッダーを`IDictionary<string, string>`として指定して、`HttpRequest.CustomHeaders`に割り当てることができます。 また、最初に`MemoryStream` オブジェクトへの変換を行ってから`HttpRequest.DataStream`に割り当てることによって リクエストデータを追加することも可能です。 詳細な手順については、 [Post JSON](#json)の例を参照してください。
<div class="alert alert-info">
Photon専用の以下の2つのプロパティは、プラグインロジックに重要です:
<ul>
<li> <code>Async</code>: レスポンスを受信するまで、ルームロジックの通常の処理を中断するかどうかを示すフラグです。 ルームのロジックがHTTPレスポンスに依存する場合、これをfalseに設定する必要があります。
<li> <code>UserState</code>: リクエストごとにPhoton Serverによって保存され、レスポンスのコールバックに送り返されるオブジェクトです。
使用の一例は、延期された<code>ICallInfo</code>を保存し (<code>OnRaiseEvent</code> または <code>BeforeSetProperties</code>内)、後に処理できるようにすることです( Continue()を呼びます)。 </code>).
</ul>
</div>
HttpRequestクラスは、以下のシグネチャを持つレスポンスのコールバックへの参照も保持する必要があります:
`public delegate void HttpRequestCallback(IHttpResponse response, object userState)`.
リクエストオブジェクトが設定されたら、`IPluginHost.HttpRequest(HttpRequest request)`を呼ぶことによってそれを送ることができます。
#### 使用例:
**例: OnRaiseEventでDeferとAsyncを使用**
この基本的な例では、HTTPレスポンスを受信するまで `RaiseEvent`オペレーション処理を遅延させる方法を示しています。
まず、`OnRaiseEvent`コールバックでHTTPリクエストを送信し、その後`ICallInfo`を延期します。
```csharp
public override void OnRaiseEvent(IRaiseEventCallInfo info)
{
HttpRequest request = new HttpRequest()
{
Callback = OnHttpResponse,
Url = "https://requestb.in/<token>", // change URL
Async = !WebFlags.ShouldSendSync(info.Request.WebFlags),
UserState = info
};
// here you can modify the request to suit your needs
PluginHost.HttpRequest(request);
info.Defer();
}
レスポンスが受信されると、正常にRaiseEventオペレーションを処理し続けるか、中止するかを決定する必要があります。
C#
private void OnHttpResponse(IHttpResponse response, object userState)
{
ICallInfo info = userState as ICallInfo;
// here you can make an extra check to resume or cancel the RaiseEvent operation
if (info.IsDeferred)
{
info.Continue();
}
}
C#
void PostJson(string url, HttpRequestCallback callback, 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 = url,
DataStream = stream,
Method = "POST",
ContentType = "application/json"
};
// here you can modify the request to suit your needs
PluginHost.HttpRequest(request);
}
例:querystringを送信
C#
void HttpGet(string url, HttpRequestCallback callback, Dictionary<string, object> getParams)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}?", url);
foreach(var p in getParams)
{
sb.AppendFormat("{0}={1}&", p.Key, p.Value);
}
HttpRequest request = new HttpRequest()
{
Callback = callback,
Url = sb.ToString().TrimEnd('&'),
Method = "GET"
};
// here you can modify the request to suit your needs
PluginHost.HttpRequest(request);
}
HTTPレスポンスの処理
レスポンスコールバックでは、最初にIHttpResponse.Status
を確認するべきです。
これは、以下のいずれかのHttpRequestQueueResult
値を持ちます。
Success (0)
: エンドポイントがHTTPステータスコードを返すことに成功しました (すなわち2xx
コード)。RequestTimeout (1)
: エンドポイントは時間内にレスポンスを返しませんでした。QueueTimeout (2)
: リクエストがHttpRequestQueue
内でタイムアウトします。 リクエストがキューに登録されると、タイマーが起動します。前のクエリに時間がかかりすぎるときにタイムアウトします。Offline (3)
: アプリケーションの各HttpRequestQueueがオフラインモードになっています。HttpRequestQueue
再接続の所要時間である10秒間にHttpRequest
は行われません。QueueFull (4)
:HttpRequestQueue
が各アプリケーションの特定の閾値に到達しました。Error (5)
: リクエストのURLが解析できなかったか、ホスト名が解決できませんでした。もしくはエンドポイントに到達できません。
これは、エンドポイントがエラーHTTPステータスコードを返す場合にも発生する可能性があります(例400:BAD REQUEST
)。
結果がSuccess (0)
でない場合、以下のプロパティを使用して問題の詳細を取得できます:
Reason
: エラーの可読形式。IHttpResponse.Status
がHttpRequestQueueResult.Error
と同等の場合に有用です。WebStatus
: 結果的に生じたWebExceptionを示すWebExceptionStatusのコードを含みます。HttpCode
: 返されたHTTPステータスコードを含みます。
コードでこれを行う方法の例は、以下のとおりです:
C#
private void OnHttpResponse(IHttpResponse response, object userState)
{
switch(response.Status)
{
case HttpRequestQueueResult.Success:
// on success logic
break;
case HttpRequestQueueResult.Error:
if (response.HttpCode <= 0)
{
PluginHost.BroadcastErrorInfoEvent(
string.Format("Error on web service level: WebExceptionStatus={0} Reason={1}",
(WebExceptionStatus)response.WebStatus, response.Reason));
}
else
{
PluginHost.BroadcastErrorInfoEvent(
string.Format("Error on endpoint level: HttpCode={0} Reason={1}",
response.HttpCode, response.Reason));
}
break;
default:
PluginHost.BroadcastErrorInfoEvent(
string.Format("Error on HttpQueue level: {0}", response.Status));
break;
}
}
使いやすくするため、PhotonプラグインSDKはHTTPレスポンスからデータを取得するための2つの方法を提供します。
IHttpResponse
を実装するクラスでは2つのプロパティが公開されています:
ResponseData
: レスポンスボディのバイト配列。受信データがテキスト形式でない場合に有用です。ResponseText
: レスポンスボディのUTF8文字列バージョン。受信したデータがテキストである場合に役立ちます。
後で必要となる場合のために、レスポンスクラスは対応するHttpRequest
への参照も保持します。この参照はIHttpResponse.Request
にあります。
タイマー
タイマーは、特定のタイミングでメソッドを呼び出すために設定するオブジェクトです。
タイマーが作成されると、カウントダウンが自動的に起動します。 これは、プラグインからコード実行をスケジュールまたは遅延させるのに最適なデフォルトの方法です。
Photon plugins SDKは、使用例に応じて2つの異なる変数を提供します:
StopTimer
を呼び出すことを忘れないでください。
これらのタイマーをクリーンアップする最後の良いタイミングはBeforeCloseGame
です。
また、停止した後のタイマーへの参照を保持しないことが推奨されますので、null
に設定してもよいでしょう。
PhotonプラグインSDKは、ユースケースに応じて2種類のタイマーを提供しています。
ワンタイムタイマー
ワンタイムタイマーの目的は、期限後にメソッドを1回トリガーすることです。
このようなタイマーを作成するには、2つの引数のみを受ける次の負荷メソッドを使用する必要があります:
object CreateOneTimeTimer(Action callback, int dueTimeMs);
スケジュールされたアクションを発生前にキャンセルする場合を除き、この種のタイマーは停止する必要はありません。
その場合はvoid IPluginHost.StopTimer(object timer)
を使用するべきです。
例: SetPropertiesの遅延
C#
public override void BeforeSetProperties(IBeforeSetPropertiesCallInfo info)
{
PluginHost.CreateOneTimeTimer(
() => info.Continue(),
1000);
info.Defer();
}
反復タイマー
反復タイマーは、定期的にメソッドを呼び出します。
最初のコールバックの実行時間と、後続の実行同士の間隔を定義することができます。
このようなタイマーを作成するには、3つの引数を取る以下の負荷メソッドを使用する必要があります:
object CreateTimer(Action callback, int dueTimeMs, int intervalMs);
この種類のタイマーは、それが実行されていて、プラグインがに読み込まれている(ルームが閉じていない)限り、対応するメソッドを呼び出し続けます。
このタイマーはvoid IPluginHost.StopTimer(object timer)
を使用すれば、いつでも停止することができます。
例: スケジュールされたイベント
C#
private object timer;
public override void OnCreateGame(ICreateGameCallInfo info)
{
info.Continue();
timer = PluginHost.CreateTimer(
ScheduledEvent,
1000,
2000);
}
private void ScheduledEvent()
{
BroadcastEvent(1, new Dictionary<byte, string>() { { (byte)245, "Time is up" } });
}
public override void BeforeCloseGame(IBeforeCloseGameCallInfo info)
{
PluginHost.StopTimer(timer);
info.Continue();
}
カスタム型
Photonでカスタムクラスのシリアル化をサポートするには、それらの型を登録する必要があります。
それぞれの型にコード(バイト)を割り当てて、クラスのフィールドとプロパティのシリアル化および非シリアル化のメソッドを提供する必要があります。
新しい型を登録するのと同じコードをクライアントでも使用します。
その後、登録を完了するために次のメソッドを呼び出します:
C#
bool IPluginHost.TryRegisterType(Type type, byte typeCode, Func<object, byte[]> serializeFunction, Func<byte[], object> deserializeFunction);
例:CustomPluginTypeの登録
登録するカスタム型クラスの例:
C#
class CustomPluginType
{
public int intField;
public byte byteField;
public string stringField;
}
シリアル化メソッドは、カスタム型のオブジェクトをバイト配列に変換する必要があります。
まず、予想される型(CustomPluginType
)にオブジェクトをキャストする必要があります。
C#
private byte[] SerializeCustomPluginType(object o)
{
CustomPluginType customObject = o as CustomPluginType;
if (customObject == null) { return null; }
using (var s = new MemoryStream())
{
using (var bw = new BinaryWriter(s))
{
bw.Write(customObject.intField);
bw.Write(customObject.byteField);
bw.Write(customObject.stringField);
return s.ToArray();
}
}
}
非シリアル化のメソッドはその反対です。
カスタム型オブジェクトが、バイト配列から構築し戻されます。
C#
private object DeserializeCustomPluginType(byte[] bytes)
{
CustomPluginType customObject = new CustomPluginType();
using (var s = new MemoryStream(bytes))
{
using (var br = new BinaryReader(s))
{
customObject.intField = br.ReadInt32();
customObject.byteField = br.ReadByte();
customObject.stringField = br.ReadString();
}
}
return customobject;
}
最後に、CustomPluginType
を登録する必要があります。 これは、SetupInstance
でプラグインが初期化されるとすぐに行うことができます:
C#
public override bool SetupInstance(IPluginHost host, Dictionary<string, string> config, out string errorMsg)
{
host.TryRegisterType(typeof(CustomPluginType), 1,
SerializeCustomPluginType,
DeserializeCustomPluginType);
return base.SetupInstance(host, config, out errorMsg);
}
ロギング
プラグインからログを生成するには、PluginHost.LogXXX
メソッドのうちの1つを使用します。
Enterprise Cloud
弊社のサーバー上のログファイルへのアクセスは許可されていないため、ログや警告には外部サービスを使用する必要があります。
Logentries または Papertrailの使用を推奨します。
このため、Enterprise Cloudを利用するユーザーは弊社までご連絡ください。ご要望に応じて、ロギングサービスを含むGMOグローバルサイン・ホールディングス Privateクラウドを設定いたします。
Logentries を利用したい場合には、設定されたログトークンをご連絡ください。
Papertrail を利用したい場合には、ポートとカスタムURLをご連絡ください。
オンプレミス
デフォルトでは、ログ出力はGameServerログエントリーとともに「GSGame.log」ファイルで取得可能です。
個別のログファイルを使用するには、GameServerのlog4net設定ファイル ("log4net.config")に以下のスニペットを追加します。
XML
<!-- "plugin" log file appender -->
<appender name="PluginLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="%property{Photon:ApplicationLogPath}\\Plugins.log" />
<param name="AppendToFile" value="true" />
<param name="MaxSizeRollBackups" value="20" />
<param name="MaximumFileSize" value="10MB" />
<param name="RollingStyle" value="Size" />
<param name="LockingModel" type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<!-- CUSTOM PLUGINS: -->
<logger name="Photon.Hive.HiveGame.HiveHostGame.Plugin" additivity="false">
<level value="DEBUG" />
<appender-ref ref="PluginLogFileAppender" />
</logger>
バージョニング
現在、Photonのプラグインはサイド・バイ・サイドのアセンブリバージョニングのみをサポートしています:AppIDごとに1つのプラグインDLLバージョンです。
新しいプラグインバージョンを展開するには、以下の2つの方法を推奨します:
A. 「互換性のある」プラグインの展開:新たなクライアントバージョンは不要です。
- 新しいバージョンのプラグインアセンブリをアップロードします。
- AppIDをステージングする際:新たなバージョンが予期されたとおりに動作している点を確認してください(推奨)。
- 新たなプラグインアセンブリバージョンを使用するため、本番のAppID設定を更新します。
B. 「互換性のない」プラグインの展開:新たなクライアントバージョンが必要です。
- プラグインアセンブリの新たなバージョンをアップロードします。
- 新たな本番のAppIDを設定します。
- 新たなプラグインアセンブリバージョンを使用するため、新たな本番のAppIDを設定します。
PUN固有のプラグイン
PUNをクライアントSDKとして使用し、これと連携するサーバーサイドのプラグインを実装したい場合には以下を把握しておく必要があります。
PUNは、おもにUnityの基本クラスに追加のカスタム型を登録します。 これらをプラグインから処理したい場合には、プラグインからも同じカスタム型を登録する必要があります。 これらのカスタム型はすべてPUNパッケージの「CustomTypes.cs」クラスにあり、登録方法もここで参照できます。 カスタム型の登録に失敗すると、エラーや予期せぬ挙動が発生する可能性があります。 こうしたエラーや挙動を把握するため、
IGamePlugin.OnUnknownType
またはIGamePlugin.ReportError
を実装する必要があります。PUNの固有かつ高度な機能はすべて、内部でRaiseEventを使用しています。 それぞれの機能は、1つまたは複数のイベントコードや特殊なイベントデータ構造を使用しています。 PUNに実装されたイベントのリストを取得するには、PUNパッケージ内の「PunClasses.cs」ファイルから「PunEvent」クラスを参照してください。 たとえば、OnSerializeViewコールを受信するには、
OnRaiseEvent
コールバックを実装して対応する型のイベントコードを取得する必要があります。 この場合のイベントコードは「SendSerialize = 201」です。 各イベントで予期される内容を把握するには、そのイベントデータがPUNのコード内でどのように構築されているか、またはプラグイン内の受信イベントから詳細を確認してください。
AuthCookie
AuthCookieは安全なデータとも呼ばれ、認証プロバイダーとしてセットアップされたWebサービスから返されるオプションのJSONオブジェクトです。
詳細な情報はカスタム認証のドキュメントページを参照してください。
プラグインからAuthCookieにアクセスする手順は、以下のとおりです:
ICallInfo.AuthCookie
: hookをトリガーしている現在のアクターのAuthCookieを取得します。ただし、OnBeforeCloseGame
とOnCloseGame
、IBeforeCloseGameCallInfo.AuthCookie
とICloseGameCallInfo.AuthCookie
はそれぞれなにも値を持ちません。これは、これらはユーザーのコンテキスト外でトリガーされるためです。
例
csharp public void OnCreateGame(ICreateGameCallInfo info) { Dictionary<string, object> authCookie = info.AuthCookie;
IActor.Secure
: アクティブなアクターにAuthCookieを取得します。
例