Photonプラグインマニュアル
Photon Server Plugins SDK v5での変更点は、こちらを参照してください。
ベストプラクティスとよくある質問については、PhotonプラグインFAQを参照してください。
イントロダクション
プラグイン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処理メソッド」を参照してください)。 - [任意] カスタムコードを注入
処理が完了すると、クライアント/サーバーによって送信されたリクエストは 「読み取り専用」となります。 ただし、処理後もプラグインはホストとインタラクトすることができます。 - 返却
プラグインはホストに制御を返します。
はじめに
最小限のプラグイン
プラグイン
プラグインの作成をおこなうお勧めの容易な方法は、直接すべての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
インスタンスを返します。
C#
namespace MyCompany.MyProject.HivePlugin
{
public class PluginFactory : PluginFactoryBase
{
public override IGamePlugin Create(string pluginName)
{
return new CustomPlugin();
}
}
}
設定
Enterprise Cloudの設定
新しいプラグインを追加するには:
サポートされている、いずれかのPhoton製品タイプのダッシュボードに進みます。
そこに記載されているPhotonアプリケーションの管理ページの1つに進みます。
ページ下部にある「新しいプラグインの作成」ボタンをクリックします。
文字列型のキー/値のエントリーを追加して、プラグインを設定します。
設定は、文字列のキー/値のセットを定義しておこないます。
各文字列に許容される最大長は256文字です。
必要な設定は以下のとおりです:AssemblyName
: プラグインを含み、アップロードされたDLLファイルの正式な名前。Version
: プラグインのバージョン。Exit Gamesによって提供されるPowerShellスクリプトを使用してプラグインファイルをアップロードする際に使用、または返されるのと同じバージョンの文字列。Path
: アセンブリファイルへのパス。以下の形式にする必要があります:「{customerName}{pluginName}」Type
: 使用するPluginFactoryクラスの正式な名前。以下の形式となります:「{plugins namespace}.{pluginfactory class name}」
セルフホスティングサーバーの設定
適切なGameServerアプリケーションの「plugin.config」ファイルで以下のXMLノードを追加、または修正する必要があります ("deploy\LoadBalancing\GameServer\bin\plugin.config")。
要素のデフォルトの属性を、以下の例に示します。 「Version」のみが不要です。
プラグインコードに渡すための、その他の設定キー/値のセットは任意で追加できます。 プラグインの有効化/非有効化は、
XML
<Configuration>
<PluginSettings Enabled="true">
<Plugins>
<Plugin
Name="{pluginName}"
Version="{pluginVersion}"
AssemblyName="{pluginDllFileName}.dll"
Type="{pluginNameSpace}.{pluginFactoryClassName}"
/>
</Plugins>
</PluginSettings>
</Configuration>
プラグイン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"
「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#
using System.Collections.Generic;
public class PluginFactory : IPluginFactory
{
public IGamePlugin Create(IPluginHost gameHost, string pluginName, Dictionary<string, string> config, out string errorMsg)
{
IGamePlugin 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
内で呼ばれると、プロパティ変更がキャンセルされます。
- クライアントにエラーレスポンスを返し、それ以降の処理をキャンセルするために使用されます。 クライアントからは、
OperationResponse.DebugMessage
でmsg
パラメータ、OperationResponse.Parameters
でerrorData
を取得できます。
注:
- プラグインは、デフォルトでstrictモードを有効にすべきです (
UseStrictMode = true
)。
StrictモードではICallInfo
処理メソッドのいずれかへの呼び出しが、各プラグインのコールバックで行われます。
利用可能なメソッドをまったく呼び出さないと、例外が発生します。 詳細はこちらを参照してください。 IGamePlugin
コールのPluginBase
実装のすべてのコールバックは、{ICallInfo}.Continue()
を呼びます。Continue()
、Fail()
およびCancel()
は一度のみ呼び出されることが想定されています。これらを再度呼び出すと例外が発生します。Cancel()
はOnRaiseEvent
またはBeforeSetProperties
内でのみ呼び出されます。ICallInfo
を実装するすべてのクラスは利用可能な場合、クライアントの元のオペレーションリクエストを公開します。
{ICallInfo}.OperationRequest
(またはRequest
)プロパティからオペレーションコードとパラメータを取得できます。ICallInfo
を実装するすべてのクラスには、オペレーションリクエストのCallStatus
処理を知らせるために、ヘルパーのプロパティが含まれます。IsNew
: リクエストが処理されていないか、また延期されていないかを示しますIsProcessed
: リクエストがすでに処理されたかを示します(つまりContinue
、Cancel
またはFail
メソッドが呼ばれたかを示します)。IsSucceeded
: リクエストが正常に処理されたか示します(つまり、Continue
メソッドが呼ばれたかを示します)。IsCanceled
: リクエストがキャンセルされたかを示します(つまり、Cancel
メソッドが呼ばれたかを示します)。IsPaused
: リクエストが内部的に一時停止されたかを示します(たとえば、Synchronous HTTPリクエストが送信された場合など)。IsDeferred
: リクエストが内部的に遅延されたかを示します(たとえば、Asynchronous HTTPが送信された場合など)。IsFailed
: リクエストが「失敗したか」を示します(つまり、Fail
メソッドが呼ばれたかを示します)。
プラグインのコールバック
Photon Serverには、あらかじめ定義されたhookが9個あります。コードでそれらのhookに明示的に登録する必要はありません。
デフォルトで、どのプラグインクラスもすべての9つのイベントを受信することができます。
ただし、必要なものを実装するべきです。PluginBase
を拡張し、必要なコールバックをオーバーライドすることをお勧めします。
すべてのコアイベントコールバックには、特定のICallInfoコントラクトがあります。
それらのほとんどは、クライアントの動作によって直接トリガーされます。
クライアントによって送信されたオペレーションリクエストは、利用可能な場所でICallInfo.Request
に提供されます。
OnCreateGame(ICreateGameCallInfo info)
前提条件: クライアントがOpCreateRoom
、OpJoinOrCreateRoom
、または OpJoinRoom
を呼び、ルームがPhotonサーバーのメモリで検出できない。
処理メソッド | 処理結果 |
---|---|
Continue |
|
- 注:
- リクエストを処理する前はルームの状態は初期化されず、デフォルト値が含まれています。
これは、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 Serverのメモリ内にある。
処理メソッド | 処理結果 |
---|---|
Continue |
OnJoin コールバックをトリガーします。 |
Fail |
JoinGame 操作レスポンスはReturnCode == ErrorCode.PluginReportedError でクライアントに送信されます。 |
Cancel |
N/A |
- 注:
IBeforeJoinGameCallInfo
を処理する前にPluginHost.BroadcastEvent
を呼ぶ場合にはキャッシュしない限り、参加しているアクターはイベントを受信しません。
OnJoin(IJoinGameCallInfo info)
前提条件: IBeforeJoinGameCallInfo.Continue()
がBeforeJoin
で呼ばれる。
処理メソッド | 処理結果 |
---|---|
Continue |
|
Fail |
|
Cancel |
N/A |
OnLeave(ILeaveGameCallInfo info)
前提条件:: クライアントがOpLeaveを呼び、ピアが切断するか、またはPlayerTTLが経過。(アクターのライフサイクルを参照してください)。
処理メソッド | 処理結果 |
---|---|
Continue |
|
Fail |
OpLeave オペレーションでトリガーされた場合、そのレスポンスはReturnCode == ErrorCode.PluginReportedError でクライアントに送信されます。 |
Cancel |
N/A |
- 注:
PlayerTTL
はルーム作成中に設定できます。PluginHost.BroadcastEvent
を呼ぶ場合、退出するアクターはイベントを受信しなくなります。
OnRaiseEvent(IRaiseEventCallInfo info)
前提条件: クライアントがOpRaiseEventを呼ぶ。
処理メソッド | 処理結果 |
---|---|
Continue |
|
Fail |
RaiseEvent 操作レスポンスはReturnCode == ErrorCode.PluginReportedError でクライアントに送信されます。
|
Cancel |
静かに処理をスキップします。 |
- 注:
IRaiseEventCallInfo
が正常に処理されている場合、操作レスポンスはクライアントに送り返されません。
BeforeSetProperties(IBeforeSetPropertiesCallInfo info)
前提条件: クライアントがOpSetPropertiesを呼ぶ。
処理メソッド | 処理結果 |
---|---|
Continue |
|
Fail |
SetProperties 操作レスポンスはReturnCode == ErrorCode.PluginReportedError でクライアントに送信されます。 |
- 注:
- 変更するプロパティがルームまたはアクターに属しているかを把握するには、
IBeforeSetPropertiesCallInfo.Request.ActorNumber
の値を確認します。
0の場合、ルームのプロパティは更新されようとしています。その他の場合には、プロパティの更新を必要としているターゲットアクターの番号です。 - 前述の
ActorNumber
とIBeforeSetPropertiesCallInfo.ActorNr
を混同しないように注意してください。後者はオペレーションリクエストを作成するアクターを指します。
- 変更するプロパティがルームまたはアクターに属しているかを把握するには、
OnSetProperties(ISetPropertiesCallInfo info)
前提条件: IBeforeSetPropertiesCallInfo.Continue()
がBeforeSetProperties
で呼ばれる。
処理メソッド | 処理結果 |
---|---|
Continue |
nil. |
Fail |
失敗のみログに記録します。 |
Cancel |
N/A |
BeforeCloseGame(IBeforeCloseGameCallInfo info)
前提条件 すべてのピアが切断されている。
処理メソッド | 処理結果 |
---|---|
Continue |
OnCloseGame をトリガーします。 |
Fail |
失敗のみログに記録します。 |
Cancel |
N/A |
- 注:
EmptyRoomTTL
はクライアントによって、ルーム作成時に設定可能です。PluginHost.BroadcastEvent
へのコールは、ルームイベントキャッシュを変更しない限り無視されます。
OnCloseGame(ICloseGameCallInfo info)
前提条件 IBeforeCloseGameCallInfo.Continue()
がBeforeCloseGame
で呼ばれ、EmptyRoomTTL
が経過。
処理メソッド | 処理結果 |
---|---|
Continue |
ルームはPhoton Serverのメモリから削除され、プラグインのインスタンスがアンロードされます。 |
- 注:
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
パラメータを使用してキャッシュオプションを定義することができます。
IPluginHost.Broadcastevent
メソッドは、すべてのキャッシュ操作をサポートするわけではありません。
6よりも大きい値のcacheOp
はいずれも承諾されず、BroadcastEvent
コールは失敗します。
通常、ルームのイベントキャッシュとのやりとりについては、IPluginHost.ExecuteCacheOperation
メソッドを使用してください。
「Photonイベントキャッシュ」についてはこちらを参照してください。
Photonイベントはイベント(送信者)の原点としてアクター番号を必要とするので、2つの選択肢があります:
- アクターになりすます:
senderActor
引数をルームに参加したアクター(アクティブなアクターである必要があります)のアクター番号に設定します。 - 「オーソリテーティブ」または「グローバル」なルームイベントを送信する:senderActor引数を0に設定します。
これは、アクター番号0はプレイヤーにアサインされないためです。
このため、これはイベントの原点がクライアントでないことを示すために使用することも可能です。
senderActor
と、0または6以外のcacheOp
値を組み合わせることはできません。
PluginBase
からプラグインクラスを拡張する場合(推奨)、ルームに参加しているすべてのアクターにイベントをブロードキャストするには以下のヘルパーメソッドを使用することができます:
C#
protected void BroadcastEvent(byte code, Dictionary<byte, object> data)
アウトバウンドのHTTPコール
HttpRequest
はHTTPリクエストを構築するヘルパークラスです。 このクラスを使用して、URLとHTTPメソッド(デフォルトは「GET」)や、
必要なAcceptおよびContentTypeヘッダーを設定することができます。 これらのプロパティの値は、 HttpWebRequestで サポートされる必要があります。
IDictionary<string, string>
として指定して、HttpRequest.CustomHeaders
に割り当てることができます。 また、最初にMemoryStream
オブジェクトへの変換を行ってから HttpRequest.DataStream
に割り当てることによって リクエストデータを追加することも可能です。 詳細な手順については、Post JSON の例を参照してください。
-
Async
: レスポンスを受信するまで、ルームロジックの通常の処理を中断するかどうかを示すフラグです。 ルームのロジックがHTTPレスポンスに依存する場合、これをfalseに設定する必要があります。 -
UserState
: リクエストごとにPhoton Serverによって保存され、レスポンスのコールバックに送り返されるオブジェクトです。
HttpRequestクラスは、以下のシグネチャを持つレスポンスのコールバックへの参照も保持する必要があります:
public delegate void HttpRequestCallback(IHttpResponse response, object userState)
。
リクエストオブジェクトが設定されたら、IPluginHost.HttpRequest(HttpRequest request)
を呼ぶことによってそれを送ることができます。
非同期HTTPリクエスト
HTTPレスポンスを受信するまで、hookの処理を遅延できます:
C#
HttpRequest request = new HttpRequest()
{
Callback = OnHttpResponse,
Url = yourCustomUrl,
Async = true,
UserObject = yourOptionalUserObject
};
PluginHost.HttpRequest(request, info);
レスポンスが受信されると、必要に応じてICallInfo
の処理方法を決定する必要があります。
C#
private void OnHttpResponse(IHttpResponse response, object userState)
{
ICallInfo info = response.CallInfo;
ProcessCallInfoIfNeeded(info);
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
にあります。
タイマー
タイマーは、特定のタイミングでメソッドを呼び出すために設定するオブジェクトです。
タイマーが作成されると、カウントダウンが自動的に起動します。
これは、プラグインからコード実行をスケジュールまたは遅延させるのに最適なデフォルトの方法です。
StopTimer
を呼び出してください。
これらのタイマーをクリーンアップするのに適した最終タイミングはBeforeCloseGame
です。
また、タイマーにnull
を設定できるよう、タイマーを停止させた後はタイマーの参照を保持しないこともお勧めします。
Photon plugins SDKは、使用例に応じて2つの異なる変数を提供します:
ワンタイムタイマー
ワンタイムタイマーの目的は、期限後にメソッドを1回トリガーすることです。
このようなタイマーを作成するには、以下のメソッドを使用する必要があります:
object CreateOneTimeTimer(ICallInfo callInfo, Action callback, int dueTimeMs);
タイマーを停止するには、void IPluginHost.StopTimer(object timer)
を呼び出します。
タイマーが経過する前に停止した場合、アクションは呼び出されません。
しかし、スケジュールされたアクションが実行された後でも、タイマーを停止させることをお勧めします。
Example: SetPropertiesを遅延させる
C#
public override void BeforeSetProperties(IBeforeSetPropertiesCallInfo info)
{
object oneTimeTimer = null;
ontTimeTimer = PluginHost.CreateOneTimeTimer(
info,
() => {
info.Continue();
PluginHost.StopTimer(oneTimeTimer);
oneTimeTimer = null;
},
1000);
}
Example 2: スケジュールされた単発のイベント
C#
private object ontTimeTimer;
public override void OnCreateGame(ICreateGameCallInfo info)
{
info.Continue();
ontTimeTimer = PluginHost.CreateOneTimeTimer(
info,
ScheduledOneTimeEvent,
1000);
}
private void ScheduledOneTimeEvent(){
// ...
}
public override void BeforeCloseGame(IBeforeCloseGameCallInfo info)
{
PluginHost.StopTimer(ontTimeTimer);
oneTimeTimer = null;
info.Continue();
}
反復タイマー
反復タイマーは、定期的にメソッドを呼び出します。
最初のコールバックの実行時間と、後続の実行同士の間隔を定義することができます。
このようなタイマーを作成するには、以下のメソッドを使用する必要があります:
object CreateTimer(Action callback, int dueTimeMs, int intervalMs);
この種類のタイマーは、それが実行されていて、プラグインがに読み込まれている(ルームが閉じていない)限り、対応するメソッドを呼び出し続けます。
このタイマーはvoid IPluginHost.StopTimer(object timer)
を使用すれば、いつでも停止することができます。
例: スケジュールされたイベント
C#
private object repeatingTimer;
public override void OnCreateGame(ICreateGameCallInfo info)
{
info.Continue();
repeatingTimer = PluginHost.CreateTimer(
ScheduledEvent,
1000,
2000);
}
private void ScheduledEvent()
{
BroadcastEvent(1, new Dictionary<byte, string>() { { 245, "Time is up" } });
}
public override void BeforeCloseGame(IBeforeCloseGameCallInfo info)
{
PluginHost.StopTimer(repeatingTimer);
repeatingTimer = null;
info.Continue();
}
### カスタム型
Photonでカスタムクラスのシリアル化をサポートするには、それらの型を登録する必要があります。
それぞれの型にコード(byte)を割り当てて、クラスのフィールドとプロパティのシリアル化および非シリアル化のメソッドを提供する必要があります。
新しい型を登録するのと同じコードをクライアントでも使用します。 その後、登録を完了するために次のメソッドを呼び出します:
```csharp
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);
}
ロギング
プラグインインスタンスごとに新規のIPluginLoggerオブジェクトを作成し、これを使用してプラグインからすべてをログします。
C#
public const string PluginName = "MyPlugin";
private IPluginLogger pluginLogger;
public override bool SetupInstance(IPluginHost host, Dictionary<string, string> config, out string errorMsg)
{
pluginLogger = host.CreateLogger(PluginName);
// ...
}
// ...
this.pluginLogger.LogDebug("debug");
this.pluginLogger.LogWarning("warning");
this.pluginLogger.LogError("error");
this.pluginLogger.LogFatal("fatal");
ロガーの名前(IPluginHost.CreateLogger(loggerName)に渡されます)を設定する際、ロガー名に
Plugin.をプリペンドします。 たとえば
MyPlugin.MyClassを設定すると、
Plugin.MyPlugin.MyClass`としてログされます。
上記のコードスニペットでは、ログレベルが最大レベルに設定された場合に以下のログエントリーが生成されます。
2020-01-31 17:10:07,394 [1] DEBUG Plugin.MyPLugin - debug
2020-01-31 17:10:07,901 [1] WARN Plugin.MyPLugin - warning
2020-01-31 17:10:08,152 [1] ERROR Plugin.MyPLugin - error
2020-01-31 17:10:08,724 [1] FATAL Plugin.MyPLugin - fatal
Enterprise Cloudのロギング設定
弊社のサーバー上のログファイルへのアクセスは許可されていないため、ログや警告には外部サービスを使用する必要があります。 Logentries または Papertrailの使用を推奨します。 このため、Enterprise Cloudを利用するユーザーは弊社までご連絡ください。ご要望に応じて、ロギングサービスを含むプライベートクラウドを設定いたします。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: new way, the new loggers have prefix "Plugin" -->
<logger name="Plugin" additivity="false">
<level value="INFO" />
<appender-ref ref="PluginLogFileAppender" />
</logger>
<!-- CUSTOM PLUGINS: old way -->
<logger name="Photon.Hive.HiveGame.HiveHostGame.Plugin" additivity="false">
<level value="DEBUG" />
<appender-ref ref="PluginLogFileAppender" />
</logger>
Enterprise Cloudでのプラグインバージョニング
現在、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のコード内でどのように構築されているか、またはプラグイン内の受信イベントから詳細を確認してください。Photonプラグイン内でPUNの
PhotonNetwork.ServerTimestamp
を取得するには、Environment.TickCount
を使用してください。
AuthCookie
AuthCookieは安全なデータとも呼ばれ、認証プロバイダーとしてセットアップされたWebサービスから返されるオプションのJSONオブジェクトです。 このオブジェクトはクライアントサイドからアクセスできません。 詳細な情報はカスタム認証のドキュメントページを参照してください。
プラグインからAuthCookieにアクセスする手順は、以下のとおりです:
ICallInfo.AuthCookie
:hookをトリガーしている現在のアクターのAuthCookieを取得します。
ただし、OnBeforeCloseGame
とOnCloseGame
、IBeforeCloseGameCallInfo.AuthCookie
および`ICloseGameCallInfo.AuthCookieはそれぞれなにも値を持ちません。これは、これらはユーザーのコンテキスト外でトリガーされるためです。例
C#
public void OnCreateGame(ICreateGameCallInfo info) { Dictionary<string, object> authCookie = info.AuthCookie;
IActor.Secure
: アクティブなアクターにAuthCookieを取得します。例
C#
foreach (var actor in this.PluginHost.GameActorsActive) { var authCookie = actor.Secure as Dictionary<string, object>; }
アクターごとの安全な認証cookieをアップデートするには、
void IActor.UpdateSecure(string key, object value)
を使用してください。