This document is about: SERVER 4
SWITCH TO

Photonプラグインマニュアル

PhotonプラグインはEnterprise CloudまたはセルフホスティングされたPhoton Server v4のみで利用可能です。

Photon 3では継承によっておこなわれていたゲーム/ルームの動作を拡張する新しい方法として、Photon 4ではプラグインが導入されています。

ベストプラクティスとよくある質問については、Photon Plugins FAQを参照してください。

イントロダクション

PhotonLoadbalancing は、クラウド上での「そのまま」(サーバ側のカスタムコード無し)の形式と、カスタム動作で拡張されたセルフホスティングの両方の環境で、ルームベースゲームのためのプラットフォームとして進化してきました。 数年間にわたって機能セットは進化してきましたが、クライアントとサーバー間のコアフロー(ゲーム作成、参加、退室などのオペレーションとそれらのイベント)は非常に安定しているため、下位互換性は維持されています。

Photon 3までは、カスタマイズはソースコード (Lite および Loadbalancing)にもとづいており、一般的にゲームクラスから継承していました。 これは柔軟でしたが、クライアント、ゲームサーバーとマスターとの間のフローに予期せぬ中断が発生する可能性があるので(クライアントがレスポンスまたはイベントを待機して「スタック」する場合など)開発上は柔軟性に欠け、より複雑でした。

プラグインAPIはコアフローの近くに設計されています(ルーム作成、参加、退室など)。

  1. 高度な柔軟性を維持: その処理の前後のコアフローにフックすることができます。
  2. フローを破壊する可能性を最小化: クライアントとサーバー上のエラー提供の迅速化に失敗。
  3. ロックフリーコードの使用を許可: プラグインのインスタンスは一度に複数のコールを受けることはありません。また、フレームワークは基礎となるPhotonメッセージパッシングアーキテクチャ(ファイバー)に統合されるHTTPクライアントとタイマーを提供します。
  4. 複雑さを低減させ、利便性を向上します: 「最小限のプラグイン」」を参照してください。

概念

カスタムのサーバーロジックを追加するには、あらかじめ定義されたPhotonのサーバーhookにコードを注入します。 hookはルームのイベントによってトリガーされるので、現在Photon ServerはGameServerプラグインにしか対応していません。

定義上、Photonのプラグインには固有の名前があ​​り、それらのイベントコールバックを実装します。 カスタムのプラグインはプラグインアセンブリというDLLファイルにコンパイルされます。 その後、必要なファイルはPhotonサーバー上でデプロイされるか、Enterprise Cloudにアップロードされます。

設定されたプラグインアセンブリは各ルームの作成で、実行時に動的に読み込まれます。 次に、プラグインのインスタンスが ファクトリパターンにもとづいて作成されます。

プラグイン作成をトリガーするルームは、「ホスト」ゲームと呼ばれます。 後者はプラグインから直接アクセスすることが可能で、どちらも同じライフサイクルを共有しています。

WebhookはPhotonプラグインの良い例です。 Webhook 1.2のソースコードは、プラグインSDKで提供されています。 確認していただくことをお勧めします。

基本フロー

Photonのhookメカニズムには、6段階のフローがあります:

  1. hookコールを受信 コールバックがトリガーされると、ホストは制御をプラグインに転送します。
  2. [任意] コール情報を変更 処理される前に、クライアント/サーバーによって送信されたリクエストにアクセスおよび変更します。
  3. [任意] カスタムコードを注入 コールを処理する前にホストとインタラクトします(例:HTTPリクエストの送信、ルーム/アクターのクエリ、タイマーの設定など)。
  4. hookコールを処理 リクエストの方法と処理を決定します(「ICallInfo Processing Methods」を参照してください)。
  5. [任意] カスタムコードを注入 処理が完了すると、クライアント/サーバーによって送信されたリクエストは「読み取り専用」となります。 ただし、処理後もプラグインはホストとインタラクトすることができます。
  6. 返却 プラグインはホストに制御を返します。

はじめに

最小限のプラグイン

初心者は「詳細なガイド」を参照してください。

プラグイン

プラグインの作成をおこなうお勧めの容易な方法は、直接すべてのIGamePluginメソッドを実装するのではなくPluginBaseクラスを拡張することです。 これによって、必要なものだけをオーバーライドすることができます。

最小限のプラグインの実装を取得するには、PluginBase.Nameプロパティをオーバーライドするだけです。これは、プラグインを識別するものです。

「Default」と「ErrorPlugin」は内部での使用に予約された名前のため、プラグイン名として使用することはできません。

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

新しいプラグインを追加するには:

  1. サポートされている、いずれかのPhoton製品タイプのダッシュボードに進みます。
  2. そこに記載されているPhotonアプリケーションの管理ページの1つに進みます。
  3. ページ下部にある「新しいプラグインの作成」ボタンをクリックします。
  4. 文字列型のキー/値のエントリーを追加して、プラグインを設定します。 設定は、文字列のキー/値のセットを定義しておこないます。 各文字列に許容される最大長は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.Pluginsstring[]型であり、最初の文字列 (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つのいずれかの方法を使用して何を行うかを決定します:

  1. Continue(): デフォルトのPhotonの処理を再開するために使用。
  2. Cancel(): 静かに処理をキャンセルするために使用します。つまり、クライアントにエラーやその他の通知は送信されません。 これによって、それ以降の処理がスキップされます:
    • OnRaiseEvent内で呼ばれると、受信イベントは無視されます
    • BeforeSetProperties内で呼ばれると、プロパティ変更がキャンセルされます。
  3. Fail(string msg, Dictionary<byte,object> errorData): クライアントにエラーレスポンスを返し、それ以降の処理をキャンセルするために使用されます。 クライアントからは、OperationResponse.DebugMessagemsgパラメータ、OperationResponse.ParameterserrorDataを取得できます。
  4. 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: リクエストがすでに処理されたかを示します(つまりContinueCancel、またはFailメソッドが呼ばれたかを示します)。
    • IsSucceeded: リクエストが正常に処理されたか示します(つまり、Continue メソッドが呼ばれたかを示します)。
    • IsCanceled: リクエストがキャンセルされたかを示します(つまり、Cancelメソッドが呼ばれたかを示します)。
    • IsDeferred: リクエストが延期されたかを示します(つまり、Deferメソッドが呼ばれたかを示します)。
    • IsFailed: リクエストが「失敗したか」を示します(つまり、Failメソッドが呼ばれたかを示します)。

プラグインのコールバック

Photon Serverには、あらかじめ定義されたhookが9個あります。コードでそれらのhookに明示的に登録する必要はありません。 デフォルトで、どのプラグインクラスもすべての9つのイベントを受信することができます。 ただし、必要なものを実装するべきです。PluginBaseを拡張し、必要なコールバックをオーバーライドすることをお勧めします。

すべてのコアイベントコールバックには、特定のICallInfoコントラクトがあります。 それらのほとんどは、クライアントの動作によって直接トリガーされます。 クライアントによって送信されたオペレーションリクエストは、利用可能な場所でICallInfo.Requestに提供されます。

OnCreateGame(ICreateGameCallInfo info)

前提条件: クライアントがOpCreateRoomOpJoinOrCreateRoom、またはOpJoinRoomを呼び、ルームがPhotonサーバーのメモリで検出できない。

処理メソッド 処理結果
Continue
  • CreateGameオペレーションレスポンスがReturnCode == ErrorCode.Okでクライアントに送信されます
  • JoinイベントはSuppressRoomEvents == falseでない限り、クライアントに送り返されます。
  • もし ICreateGameCallInfo.BroadcastActorProperties == true ならば、プレイヤーのカスタムプロパティはイベントパラメータに含まれます。
  • 初めて初期化した場合、ルームオプションと初期プロパティはルームの状態に割り当てられ、 ActorList には最初のアクターとそのデフォルトプロパティ (UserIdNickName)が含まれている必要があります。 リクエストにカスタムのアクタープロパティが含まれている場合、リスト内のアクターエントリーに追加する必要があります。
  • 変更された場合を除いてルームの状態が読み込まれた場合、ルームの状態はPhotonサーバーのメモリから前回削除をおこなった前と同じでなければなりません。
Fail CreateGameオペレーションレスポンスはReturnCode == ErrorCode.PluginReportedErrorでクライアントに送信されます。
Cancel N/A
Defer N/A
  • 注:
    • リクエストを処理する前はルームの状態は初期化されず、デフォルト値が含まれています。 これは、 IPluginHost.SetGameStateを呼び出すことによってルームの状態を外部ソースから読み込んで解析し、ルームに割り当てることができる唯一の​​状況です。
    • リクエストを処理する前に、PluginHost.SetProperties または PluginHost.BroadcastEventへのすべての呼び出しは無視されます。
    • オペレーションリクエストの種類を把握するには、ICreateGameCallInfo.IsJoinICreateGameCallInfo.CreateIfNotExistsを使用できます。
オペレーションメソッド IsJoin CreateIfNotExist
OpCreateRoom false false
OpJoinRoom true false
OpJoinOrCreateRoom true true

BeforeJoin(IBeforeJoinGameCallInfo info)

前提条件: クライアントがOpJoinRoomOpJoinOrCreateRoom、またはOpJoinRandomRoomを呼び、ルームはPhotonサーバーのメモリ内にある。

OnJoin コールバックをトリガーします
処理メソッド 処理結果/th>
Continue
Fail JoinGame オペレーションレスポンスは ReturnCode == ErrorCode.PluginReportedErrorでクライアントに送信されます。
Cancel N/A
Defer N/A
  • 注:
    • IBeforeJoinGameCallInfoを処理する前にPluginHost.BroadcastEventを呼ぶ場合にはキャッシュしない限り、参加しているアクターはイベントを受信しません。

OnJoin(IJoinGameCallInfo info)

前提条件: IBeforeJoinGameCallInfo.Continue()BeforeJoinで呼ばれる。

処理メソッド 処理結果
Continue
  • 参加が許可されている場合、参加アクターはアクターのデフォルトプロパティ (UserIdNickName)とともに)ActorListに追加されます。
  • リクエストにアクタープロパティが含まれている場合、それらも設定する必要があります。 JoinGameオペレーションレスポンスはReturnCode == ErrorCode.Okとともにクライアントに送り返されます。
  • IJoinGameCallInfo.PublishUserId == trueの場合、他のアクターのUserIdはオペレーションレスポンスで送り返されます。
  • SuppressRoomEvents == falseでない限り、Join イベントはブロードキャストされます。
  • IJoinGameCallInfo.BroadcastActorProperties == trueの場合、プレイヤーカスタムプロパティはイベントパラメータに含まれます。
Fail
  • JoinGame オペレーションレスポンスは ReturnCode == ErrorCode.PluginReportedErrorでクライアントに送信されます。
  • アクターの追加は元に戻されます。
Cancel N/A
Defer N/A

OnLeave(ILeaveGameCallInfo info)

前提条件: クライアントがOpLeaveを呼び、ピアが切断するか、またはPlayerTTLが経過。(アクターのライフサイクルを参照してください)。

処理メソッド 処理結果
Continue
  • OpLeaveオペレーションでトリガーされた場合、そのレスポンスはReturnCode == ErrorCode.Okでクライアントに送信されます。
  • SuppressRoomEvents == falseでない限り、Leaveイベントは他のアクターに送信されます。
  • ILeaveGameCallInfo.IsInactive == trueの場合:
    • アクターはインアクティブとしてマークされます。
    • DeactivationTimeがプロパティに追加されます。
  • ILeaveGameCallInfo.IsInactive == falseの場合:
    • アクターとそのプロパティはActorListから削除されます。
    • DeleteCacheOnLeave == trueの場合、相対キャッシュイベントも削除可能です。
    • ActiveActorListが空になると、EmptyRoomTTLミリ秒後にBeforeCloseGameコールが発生します。
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
  • ルームまたはアクターのプロパティが更新されます。
  • SetPropertiesオペレーションレスポンスはReturnCode == ErrorCode.Okでクライアントに送り返されます。
  • PropertiesChangedイベントがブロードキャストされます。
  • OnSetPropertiesをトリガーします。 
Fail SetPropertiesオペレーションレスポンスはReturnCode == ErrorCode.PluginReportedErrorでクライアントに送信されます。
Cancel 静かに処理をスキップします。
Defer 処理は延期されます。
  • 注:
    • 変更するプロパティがルームまたはアクターに属しているかを把握するには、IBeforeSetPropertiesCallInfo.Request.ActorNumberの値を確認します。 0の場合、ルームのプロパティは更新されようとしています。その他の場合には、プロパティの更新を必要としているターゲットアクターの番号です。
    • 前述のActorNumberIBeforeSetPropertiesCallInfo.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と、それに続く UserIdNickNameを使用して定義されます。 また、アクターにはカスタムプロパティを設定することができます。 プレイヤーが最初にルームに入ると、他のプレイヤーが取得することのできないアクター番号をルーム内で取得します。 また、新しい各プレイヤーに対してルームの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 パラメータは無視されます。 senderActor0の場合、target0 (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イベントキャッシュ」についてはこちらを参照してください。

プラグインAPIはすべてのキャッシュ操作をサポートするわけではありません。 6よりも高いcacheOp値は承諾されず、BroadcastEventコールは失敗します。

Photonイベントはイベント(送信者)の原点としてアクター番号を必要とするので、2つの選択肢があります:

  • アクターになりすます: senderActor引数をルームに参加したアクター(アクティブなアクターである必要があります)のアクター番号に設定します。
  • 「オーソリテーティブ」または「グローバル」なルームイベントを送信する:senderActor引数を0に設定します。 これは、アクター番号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();
    }
}

例:JSONを送信

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.StatusHttpRequestQueueResult.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;
}
カスタム型の登録は両側で行うべきです。 つまり、Photonクライアントは同じコードとシリアル化メソッドでカスタム型を登録する必要があります。

シリアル化メソッドは、カスタム型のオブジェクトをバイト配列に変換する必要があります。 まず、予想される型(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 Enterprise Cloudのユーザーにのみ関連します。

現在、Photonのプラグインはサイド・バイ・サイドのアセンブリバージョニングのみをサポートしています:AppIDごとに1つのプラグインDLLバージョンです。

新しいプラグインバージョンを展開するには、以下の2つの方法を推奨します:

A. 「互換性のある」プラグインの展開:新たなクライアントバージョンは不要です。

  1. 新しいバージョンのプラグインアセンブリをアップロードします。
  2. AppIDをステージングする際:新たなバージョンが予期されたとおりに動作している点を確認してください(推奨)。
  3. 新たなプラグインアセンブリバージョンを使用するため、本番のAppID設定を更新します。

B. 「互換性のない」プラグインの展開:新たなクライアントバージョンが必要です。

  1. プラグインアセンブリの新たなバージョンをアップロードします。
  2. 新たな本番のAppIDを設定します。
  3. 新たなプラグインアセンブリバージョンを使用するため、新たな本番の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を取得します。ただし、OnBeforeCloseGameOnCloseGameIBeforeCloseGameCallInfo.AuthCookieICloseGameCallInfo.AuthCookieはそれぞれなにも値を持ちません。これは、これらはユーザーのコンテキスト外でトリガーされるためです。

csharp public void OnCreateGame(ICreateGameCallInfo info) { Dictionary<string, object> authCookie = info.AuthCookie;

  • IActor.Secure: アクティブなアクターにAuthCookieを取得します。

Back to top