This document is about: SERVER 5
SWITCH TO

Photonプラグインマニュアル

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

Photon Server Plugins SDK v5での変更点は、こちらを参照してください。

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

イントロダクション

プラグイン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処理メソッド」を参照してください)。
  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インスタンスを返します。

C#

namespace MyCompany.MyProject.HivePlugin
{
  public class PluginFactory : PluginFactoryBase
  {
    public override IGamePlugin Create(string pluginName)
    {
        return new CustomPlugin();
    }
  }
}

設定

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アプリケーションの「plugin.config」ファイルで以下のXMLノードを追加、または修正する必要があります ("deploy\LoadBalancing\GameServer\bin\plugin.config")。 要素のデフォルトの属性を、以下の例に示します。 「Version」のみが不要です。 プラグインコードに渡すための、その他の設定キー/値のセットは任意で追加できます。 プラグインの有効化/非有効化は、要素のEnabled属性の値を変更することで簡単におこなえます。

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つのいずれかの方法を使用して何を行うかを決定します:

  1. Continue(): デフォルトのPhotonの処理を再開するために使用。
  2. Cancel(): 静かに処理をキャンセルするために使用します。つまり、クライアントにエラーやその他の通知は送信されません。 これによって、それ以降の処理がスキップされます:
    • OnRaiseEvent内で呼ばれると、受信イベントは無視されます。
    • BeforeSetProperties内で呼ばれると、プロパティ変更がキャンセルされます。
  3. クライアントにエラーレスポンスを返し、それ以降の処理をキャンセルするために使用されます。 クライアントからは、OperationResponse.DebugMessagemsgパラメータ、OperationResponse.ParameterserrorDataを取得できます。

注:

  • プラグインは、デフォルトでstrictモードを有効にすべきです (UseStrictMode = true)。 StrictモードではICallInfo処理メソッドのいずれかへの呼び出しが、各プラグインのコールバックで行われます。 利用可能なメソッドをまったく呼び出さないと、例外が発生します。 詳細はこちらを参照してください。
  • IGamePluginコールのPluginBase実装のすべてのコールバックは、 {ICallInfo}.Continue()を呼びます。
  • Continue()Fail()およびCancel()は一度のみ呼び出されることが想定されています。これらを再度呼び出すと例外が発生します。
  • Cancel()OnRaiseEventまたはBeforeSetProperties内でのみ呼び出されます。
  • ICallInfoを実装するすべてのクラスは利用可能な場合、クライアントの元のオペレーションリクエストを公開します。 {ICallInfo}.OperationRequest (または Request)プロパティからオペレーションコードとパラメータを取得できます。
  • ICallInfoを実装するすべてのクラスには、オペレーションリクエストのCallStatus処理を知らせるために、ヘルパーのプロパティが含まれます。
  • IsNew: リクエストが処理されていないか、また延期されていないかを示します
    • IsProcessed: リクエストがすでに処理されたかを示します(つまりContinueCancelまたは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)

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

処理メソッド 処理結果
Continue
  • CreateGame操作レスポンスがReturnCode == ErrorCode.Okでクライアントに送信されます。
  • JoinイベントはSuppressRoomEvents == falseでない限り、クライアントに送り返されます。
  • もしICreateGameCallInfo.BroadcastActorProperties == trueならば、プレイヤーのカスタムプロパティはイベントパラメータに含まれます    
  • 初めて初期化した場合、ルームオプションと初期プロパティはルームの状態に割り当てられ、ActorListには最初のアクターとそのデフォルトプロパティ (UserIdNickName)が含まれている必要があります。 リクエストにカスタムのアクタープロパティが含まれている場合、リスト内のアクターエントリーに追加する必要があります。
  • 変更された場合を除いてルームの状態が読み込まれた場合、ルームの状態はPhotonサーバーのメモリから前回削除をおこなった前と同じでなければなりません。
  • 注:
    • リクエストを処理する前はルームの状態は初期化されず、デフォルト値が含まれています。 これは、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 Serverのメモリ内にある。

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

OnJoin(IJoinGameCallInfo info)

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

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

OnLeave(ILeaveGameCallInfo info)

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

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

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はプレイヤーにアサインされないためです。 このため、これはイベントの原点がクライアントでないことを示すために使用することも可能です。
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 の例を参照してください。

Photon専用の以下の2つのプロパティは、プラグインロジックに重要です:
  • 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.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にあります。

タイマー

タイマーは、特定のタイミングでメソッドを呼び出すために設定するオブジェクトです。 タイマーが作成されると、カウントダウンが自動的に起動します。 これは、プラグインからコード実行をスケジュールまたは遅延させるのに最適なデフォルトの方法です。

プラグインを破棄する前に、タイマーを停止する必要があります。 どのような型でも、タイマーオブジェクトを作成した場合には必ず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;
}
カスタム型の登録は両側で行うべきです。 つまり、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);
}

ロギング

プラグインインスタンスごとに新規の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`としてログされます。 上記のコードスニペットでは、ログレベルが最大レベルに設定された場合に以下のログエントリーが生成されます。

Unknown

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. 「互換性のある」プラグインの展開:新たなクライアントバージョンは不要です。

  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のコード内でどのように構築されているか、またはプラグイン内の受信イベントから詳細を確認してください。

  • Photonプラグイン内でPUNのPhotonNetwork.ServerTimestampを取得するには、Environment.TickCountを使用してください。

AuthCookie

AuthCookieは安全なデータとも呼ばれ、認証プロバイダーとしてセットアップされたWebサービスから返されるオプションのJSONオブジェクトです。 このオブジェクトはクライアントサイドからアクセスできません。 詳細な情報はカスタム認証のドキュメントページを参照してください。

プラグインからAuthCookieにアクセスする手順は、以下のとおりです:

  • ICallInfo.AuthCookie:hookをトリガーしている現在のアクターのAuthCookieを取得します。 ただし、OnBeforeCloseGameOnCloseGameIBeforeCloseGameCallInfo.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)を使用してください。

Back to top