server | v5 switch to v4  

Photon 플러그인 지침서

Photon 플러그인은 Enterprise Cloud 또는 자체 호스팅 된 Photon Server에만 사용할 수 있습니다.

목차

Photon Server 플러그인 SDK v5의 새로운 기능에 대해서는 여기를 확인해 보세요.

모범 사례 및 자주 묻는 질문은 Photon 플러그인 FAQ를 참고하세요.

소개

Plugins API는 코어 흐름(룸 생성, 참여, 퇴장 등)에 가깝게 설계되었습니다.

  1. 높은 수준의 유연성 유지: 코어 흐름을 처리하기 전과 후에 코어 흐름을 가로챌 수 있습니다
  2. 흐름을 끊을 수 있는 기회 최소화: 클라이언트 및 서버에서 오류를 제공하여 빠르게 실패합니다.
  3. 잠금 없는 코드 사용 허용: 플러그인 인스턴스는 한 번에 두 개 이상의 호출을 받지 않으며 프레임워크는 기본 Photon 메시지 전달 아키텍처(파이버)에 통합된 HTTP 클라이언트 및 타이머를 제공합니다.
  4. 복잡성을 낮추고 사용 편의성을 높입니다: "Minimal 플러그인"을 참조하십시오.

메인 화면으로

개념

사용자 지정 서버 로직을 추가하려면 미리 정의된 Photon 서버 후크에 코드를 주입합니다. 현재 Photon 서버는 룸 이벤트에서만 후크가 트리거 되는 경우에만 GameServer 플러그인을 지원합니다.

정의에 따라 Photon 플러그인은 고유한 이름을 가지며 이러한 이벤트 콜백을 구현합니다. 사용자 정의 플러그인은 플러그인 어셈블리라는 DLL 파일로 컴파일됩니다. 그런 다음 필요한 파일이 Photon 서버에 "배포" 되거나 Enterprise Cloud에 업로드됩니다.

구성된 플러그인 어셈블리는 각 룸 생성 시 동적으로 로드됩니다. 그런 다음 팩토리 패턴을 기반으로 플러그인 인스턴스가 생성됩니다.

플러그인 생성을 트리거 하는 룸을 "호스트" 게임이라고 합니다. 후자는 플러그인에서 직접 액세스할 수 있으며 둘 다 동일한 라이프 사이클을 공유합니다.

Webhook은 Photon 플러그인의 좋은 예입니다. Webhooks 1.2의 소스 코드는 플러그인 SDK에서 확인하실 수 있습니다. 자세히 확인해 보시길 바랍니다.

메인 화면으로

기본 흐름

Photon 후킹 메커니즘은 다음의 6단계에 의존합니다:

  1. 후크 호출 가로채기 콜백이 트리거 되면 호스트는 플러그인으로 제어를 변경합니다.
  2. [선택] 호출 정보 변경 클라이언트/서버가 보낸 요청을 처리하기 전에 접근하고 수정합니다.
  3. [선택] 사용자 지정 코드 주입 호출을 처리하기 전에 호스트와 상호 작용합니다(예: HTTP 요청, 룸/액터 쿼리, 타이머 설정 등).
  4. 후크 호출 처리 요청을 어떤 방식으로 처리할지 결정 ("ICallInfo 처리 메소드" 참고)
  5. [선택] 사용자 지정 코드 주입 처리되면, 클라이언트/서버에서 전송된 요청은 "읽기 전용"으로 유용합니다. 그러나 처리 후에도 플러그인은 호스트와 상호 작용할 수 있습니다.
  6. 리턴
    플러그인은 호스트로 제어권을 넘겨줍니다.

메인 화면으로

시작하기

최소한의 플러그인

"단계별 지침서"는 초보자용입니다.

메인 화면으로

플러그인

권장되고 쉬운 플러그인 제작 방법은 모든 IGamePlugin 메소드를 직접 구현하는 대신 PluginBase 클래스를 확장하는 것입니다. 그럼 필요한 것만 오버라이드 할 수 있습니다.

최소 플러그인 구현을 얻으려면 PluginBase.Name을 재정의하기만 하면 됩니다. 이 이름으로 플러그인을 식별하게 됩니다.

"Default" 그리고 "ErrorPlugin"은 내부적으로 사용하는 예약어로 사용자 지정 플러그인 이름을 사용하시면 안 됩니다.
namespace MyCompany.MyProject.HivePlugin
{
  public class CustomPlugin : PluginBase
  {
    public override string Name
    {
        get
        {
            return "CustomPlugin"; // anything other than "Default" or "ErrorPlugin"
        }
    }
  }
}

메인 화면으로

팩토리

플러그인 팩토리 클래스는 플러그인 어셈블리의 일부로 구현되어야 합니다. 룸 당 플러그인 인스턴스 생성을 담당합니다. 단순성을 위해 다음 코드에서는 클라이언트에서 요청한 플러그인 이름을 확인하지 않고 기본적으로 CustomPlugin 인스턴스를 반환합니다.

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

메인 화면으로

환경 구성

Enterprise Cloud 환경 구성

새로운 플러그인 추가를 위해서는:

  1. 지원되는 Photon 제품 유형 중 하나의 관리 화면으로 이동합니다.
  2. 화면에 나열되어 있는 Photon 애플리케이션 중 하나의 관리 페이지로 이동합니다.
  3. 페이지 하단의 "신규 플러그인 생성" 버튼을 클릭합니다.
  4. 문자열 유형의 키/값 항목을 추가하여 플러그인을 구성합니다. 문자열의 키/값 쌍을 정의하여 구성합니다. 각 문자열에 허용되는 최대 길이는 256자입니다. 필요한 설정은 다음과 같습니다:

    • AssemblyName: 플러그인이 포함된 업로드된 DLL 파일의 전체 이름입니다.
    • Version: 플러그인의 버전입니다. Exit Games에서 제공하는 PowerShell 스크립트를 사용하여 플러그인의 파일을 업로드할 때 사용되거나 리턴되는 것과 동일한 버전 문자열입니다.
    • Path: 어셈블리 파일의 경로입니다. 다음의 형식으로 지정되어야 합니다: "{customerName}\{pluginName}".
    • Type: 사용될 PluginFactory 클래스의 전체 이름입니다. 다음의 형식으로 지정되어야 합니다: "{plugins namespace}.{pluginfactory class name}".

메인 화면으로

자체 호스트된 서버 환경 구성

GameServer 애플리케이션("deploy\LoadBalancing\GameServer\bin\plugin.config")의 "plugin.config" 파일에서 다음 XML 노드를 추가하거나 수정합니다. <Plugin.../>요소의 기본 속성들은 다음 예제에 나와있습니다. "Version"만 필요하지 않습니다. 플러그인 코드에 전달되는 다른 선택적 구성 키/값 쌍을 추가할 수 있습니다. <PluginSettings> 요소의 Enabled 속성 값을 변경하여 플러그인을 쉽게 활성화하거나 비활성화할 수 있습니다.

<Configuration>
  <PluginSettings Enabled="true">
    <Plugins>
      <Plugin
          Name="{pluginName}"
          Version="{pluginVersion}"
          AssemblyName="{pluginDllFileName}.dll"
          Type="{pluginNameSpace}.{pluginFactoryClassName}"
      />
    </Plugins>
  </PluginSettings>
</Configuration>

빌드에 필요한 플러그인 DLL, 그리고 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 플러그인 어셈블리는 여러 플러그인 클래스와 하나의 활성 "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는 최소한 하나의 요소(플러그인 이름 문자열)이 있어야 합니다. 만약 하나 이상의 요소가 전송(roomOptions.Plugins.Length > 1) 되면 PluginMismatch오류가 수신되고 룸 참여 또는 생성이 실패하게 됩니다.

팩토리에서 이름을 사용하여 다음과 같이 해당 플러그인을 로드합니다.:

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 흐름을 후크 하는 것입니다. 개발자는 요청 유형에 따라 다음 네 가지 메소드 중 하나를 사용하여 수행할 작업을 결정해야 합니다:

  1. Continue(): 기본 Photon 처리를 재개하는 데 사용.
  2. Cancel(): 처리를 조용하게 취소하는 데 사용. 예, 오류가 없거나 클라이언트에게 아무런 알림이 없음. 이것은 앞으로의 처리를 스킵 하는 것과 동일합니다:
    • OnRaiseEvent 내에서 호출되면 수신 이벤트를 무시합니다..
    • BeforeSetProperties 내에서 호출되면 속성 변경을 취소합니다.
  3. Fail(string msg, Dictionary<byte,object> errorData): 앞으로의 처리를 취소하고 클라이언트에 오류 응답을 반환하는 데 사용됩니다. 클라이언트에서, OperationResponse.Parameters 안에서 OperationResponse.DebugMessage 그리고 errorData 내에서 msg 파라미터를 얻을 수 있습니다.

노트:

  • 플러그인은 기본적으로 엄격한 모드를 활성화해야 합니다(UseStrictMode = true). 엄격한 모드는 각 플러그인 콜백에서 ICallInfo 처리 메소드 중 하나에 대한 호출이 필요함을 의미합니다. 사용 가능한 메소드를 호출하지 않으면 예외가 발생합니다. 여기를 더 읽어보시기 바랍니다.
  • IGamePlugin 호출 {ICallInfo}.Continue()PluginBase 구현의 모든 콜백.
  • Continue(), Fail() 그리고 Cancel() 은 한 번만 호출되게 됩니다. 이 메소드 중 하나라도 다시 호출하면 예외가 발생됩니다.
  • Cancel()OnRaiseEvent 또는 BeforeSetProperties 내에서만 호출됩니다.
  • ICallInfo를 구현하는 모든 클래스는 사용 가능한 경우 클라이언트의 원래 작업 요청을 노출합니다. {ICallInfo}.OperationRequest (또는 Request) 속성으로부터 오퍼레이션 코드와 파라미터를 얻을 수 있습니다.
  • ICallInfo를 구현하는 모든 클래스에는 작업 요청의 CallStatus 처리 상황을 알려주는 헬퍼 속성이 포함되어 있습니다:
    • IsNew: 요청이 처리되지 않았거나 연기된 것을 나타냅니다.
    • IsProcessed: 요청이 이미 처리되었다는 것을 나타냅니다 (즉, Continue 또는 Cancel 또는 Fail 메소드가 호출되었음).
    • IsSucceeded: 요청이 성공적으로 처리되었음을 나타냅니다 (즉, Continue 메소드가 호출되었음).
    • IsCanceled: 요청이 취소되었음을 나타냅니다 (즉, Cancel 메소드가 호출되었음).
    • IsPaused: 요청이 내부적으로 중지되었다는 것을 나타냅니다 (예, 동기화 HTTP 요청 전송됨).
    • IsDeferred: 요청이 내부적으로 연기되었다는 것을 나타냅니다 (예, 비동기화 HTTP 요청 전송됨).
    • IsFailed: 요청이 "실패했다"는 것을 나타냅니다(즉, Fail 메소드가 호출됨).

메인 화면으로

플러그인 콜백

Photon Server에는 9개의 미리 정의된 후크가 있습니다. 코드에 후크를 명시적으로 등록할 필요가 없습니다. 기본적으로 모든 플러그인 클래스는 9개의 이벤트를 모두 가로챌 수 있습니다. 하지만 필요한 것들은 직접 구현해야 합니다. PluginBase를 확장하고 필요한 콜백을 오버라이드 하는 것을 권장합니다.

모든 핵심 이벤트 콜백에는 특정 ICallInfo 계약이 있습니다. 대부분의 콜백은 클라이언트 액션에 의해 직접 트리거 됩니다. 클라이언트가 보낸 작업 요청은 사용 가능한 ICallInfo.Request에서 제공합니다.

메인 화면으로

OnCreateGame(ICreateGameCallInfo Info)

사전 조건: 클라이언트는 OpCreateRoom 또는 OpJoinOrCreateRoom 또는 OpJoinRoom을 호출하고 룸은 Photon 서버 메모리에는 없습니다.

처리 메소드 처리 결과
Continue
  • CreateGame 오퍼레이션 응답은 클라이언트에게 ReturnCode == ErrorCode.Ok로 전송됩니다.
  • Join 이벤트는 SuppressRoomEvents == false가 아니면 클라이언트에게 다시 보내집니다.
  • 만약 ICreateGameCallInfo.BroadcastActorProperties == true 이면 플레이어 사용자 지정 속성들이 있다면, 이벤트 파라미터내에 포함됩니다.
  • 최초로 초기화된경우, 룸 옵션과 초기 속성들은 룸 상태로 지정되고, ActorList는 기본 속성을 가진 첫 번째 액터가 있어야합니다 (UserId 그리고 NickName). 요청에 사용자 정의 액터 속성이 포함된 경우 목록의 액터 항목에도 추가해야 합니다.
  • 로드된 경우 룸 상태는 변경되지 않는 한 Photon 서버 메모리에서 마지막으로 제거하기 전과 동일해야 합니다.
Fail CreateGame 오퍼레이션 응답은 클라이언트에게 ReturnCode == ErrorCode.PluginReportedError로 전송됩니다.
Cancel 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 Server의 메모리에 존재합니다.

처리 메소드 처리 결과
Continue OnJoin 콜백을 트리거.
Fail JoinGame ReturnCode == ErrorCode.PluginReportedError를 가진 오퍼레이션 응답이 클라이언트로 전송됩니다.
Cancel N/A
  • 노트:
    • IBeforeJoinGameCallInfo를 처리하기 전, PluginHost.BroadcastEvent를 호출한다면 캐시를 하지 않는 한 이벤트 수신을 위해 참여 중인 액터는 없습니다.

메인 화면으로

OnJoin(IJoinGameCallInfo Info)

사전 조건: BeforeJoin에서 IBeforeJoinGameCallInfo.Continue()가 호출됨.

처리 메소드 처리 결과
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로 리턴됩니다.
  • Leave 이벤트는 SuppressRoomEvents == false가 아니면 다른 액터로 전송됩니다.
  • ILeaveGameCallInfo.IsInactive == true인 경우:
    • 액터는 비활성으로 표시됩니다.
    • DeactivationTime이 속성에 추가됩니다.
  • ILeaveGameCallInfo.IsInactive == false인 경우:
    • 액터와 액터의 속성은ActorList에서 제거됩니다.
    • DeleteCacheOnLeave == true인 경우 캐시된 상대 이벤트도 제거할 수 있습니다.
    • ActiveActorList이 비어지게 된다면, a BeforeCloseGame 호출이 EmptyRoomTTL 밀리초 이후에 호출됩니다.
Fail OpLeave 오퍼레이션에 의해서 트리거 된 경우, 이에 대한 응답은 ReturnCode == ErrorCode.PluginReportedError로 클라이언트에게 전송됩니다.
취소 N/A
  • 노트:
    • PlayerTTL은 룸을 생성하는 동안 설정할 수 있습니다.
    • If you call 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로 클라이언트에게 전송됩니다.
Cancel 조용히 처리를 스킵합니다.
  • 노트:
    • 변경할 속성이 룸에 속하는지 또는 액터인지 확인하려면 IBeforeSetPropertiesCallInfo.Request.ActorNumber 값을 확인하십시오. 값이 0 이면, 룸의 속성이 곧 업데이트되는 경우입니다. 그렇지 않으면 속성을 업데이트해야 하는 대상 액터 번호입니다.
    • 앞서 언급한 ActorNumberIBeforeSetPropertiesCallInfo.ActorNr를 혼용하지 않도록 주의하십시오. 후자는 액터가 오퍼레이션을 요청하는 것을 말합니다.

메인 화면으로

OnSetProperties(ISetPropertiesCallInfo Info)

사전 조건: BeforeSetProperties에서 IBeforeSetPropertiesCallInfo.Continue()이 호출됩니다.

처리 메소드 처리 결과
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 Servers 메모리에서 제거되고 플러그인 인스턴스가 언로드됩니다.
Fail 실패한 로그만.
Cancel N/A
  • 노트:
    • ICloseGameCallInfo를 처리하기 전에, 룸 상태를 저장하거나 영원히 없애버릴 수 있는 것 중에서 선택할 수 있습니다. 웹훅에서 이 작업은 아직 한 명 이상의 비활성 액터가 방에 있을 때 수행할 수 있습니다.

메인 화면으로

고급 개념

액터 생명 주기

피어 <-> 액터 <-> 룸

액터는 룸 안에 있는 플레이어입니다. 플레이어가 룸을 만들거나 룸에 참여함으로써 룸에 들어오면 액터가 그/그녀를 대표합니다. 그 액터는 먼저 ActorNr로 정의되고 그다음에 UserIdNickName을 사용합니다. 사용자 정의 속성도 가질 수 있습니다. 플레이어가 룸에 처음 들어오면 해당 룸 안에 다른 플레이어가 요청할 수 없는 액터 번호를 받게 됩니다. 또한 새로운 플레이어 한 명당 액터 한 명이 이 방의 ActorsList에 추가됩니다.

만약 플레이어가 영원히 그 룸을 떠난다면, 해당 액터는 그 리스트에서 삭제될 것입니다. 하지만, 룸 옵션이 허락한다면, 플레이어는 방을 나갔다가 나중에 돌아올 수 있습니다. 이 경우 해당 액터는 플레이어가 방에서 나갈 때 비활성으로 표시됩니다. 이벤트의 타임스탬프가 DeactivationTime 액터 속성에 저장됩니다.

액터들이 룸 안에서 활동하지 않을 수 있는 시간을 제한할 수 있습니다. PlayerTTL을 설정하여 룸을 만들 때 이 기간을 정의할 수 있으며 값의 단위는 밀리초입니다. 값이 음수이거나 int의 최댓값과 같으면 액터는 무기한 비활성 상태를 유지할 수 있습니다. 그렇지 않으면 DeactivationTime이 지난 이후 PlayerTTL이 지나면 활동하지 않는 액터들이 방에서 쫓겨납니다. 플레이어는 그때까지 룸에 다시 들어갈 수 있습니다. 그리고 만약 플레이어가 다시 일시적으로 방을 나가기로 결정한다면, 새로운 DeactivationTime이 되고, 카운트다운이 재설정됩니다. 그래서 재입장 횟수에 제한이 없습니다.

Photon 플러그인 SDK는 다음 속성 중 하나를 사용하여 주어진 순간에 모든 액터를 가져올 수 있는 방법을 제공합니다:

  • IPluginHost.GameActors에는 룸 안에 있는 모든 액터를 포함합니다 (활성 그리고 비활성).
  • IPluginHost.GameActorsActive에는 현재 룸에 참여한 모든 액터들이 들어 있습니다.
  • IPluginHost.GameActorsInActive에는 룸에서 나간 모든 액터들이 들어있습니다 (퇴출이 없는).

메인 화면으로

플러그인으로부터 이벤트 전송하기

Photon 플러그인 SDK를 사용하여 룸 내에서 사용자 정의 이벤트를 전송할 수 있습니다. 사용자 지정 이벤트의 유형과 내용은 해당 코드로 정의되어야 합니다. 이벤트 코드는 200 미만이어야 합니다.

여기에는 두 가지 오버로드 메소드가 있습니다. BroadcastEvent라는 이름이 이벤트를 브로드캐스트 할 것임을 암시하지만 필터를 기반으로 멀티캐스트를 수행하거나 한 명의 액터에게 보낼 수도 있습니다:

  • 액터 그룹에 보내기:
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 열거형과 값들은 PUN에 포함되어 있는 Photon의 C# 클라이언트 SDK의 ReceiverGroup 열거형과 값들과 혼동해서는 안 됩니다.
  • 액터 번호를 사용하여 특정 액터 목록으로 보냅니다:
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 이벤트에는 이벤트(전송자)의 원본으로 액터 번호가 필요하므로 두 가지 옵션이 있습니다:

  • 액터 사칭: senderActor 아규먼트를 룸에 가입한 배우의 액터 번호로 설정합니다(활동 중인 액터이여야함).
  • "권한 있는" 또는 "글로벌" 룸 이벤트를 보냅니다: senderActor 아규먼트를 0으로 설정합니다. 0번 액터는 절대로 플레이어에게 배정되지 않기 때문입니다. 따라서 이벤트 발생지가 클라이언트가 아님을 나타내는 데 사용될 수 있습니다.
0으로 설정된 senderActor를 0 또는 6이 아닌 cacheOp 값과 결합할 수 없습니다.

플러그인 클래스를 PluginBase에서 확장하도록 선택할 경우 다음 헬퍼 메소드를 사용하여 룸에 가입한 모든 액터에게 이벤트를 브로드캐스트할 수 있습니다:

protected void BroadcastEvent(byte code, Dictionary<byte, object> data)
클라이언트 코드를 변경하지 않고 플러그인에서 보낸 이벤트 데이터와 클라이언트에서 보낸 사람 액터 번호를 검색할 수 있으려면 (Dictionary<byte, object>)eventData 대신에 다음과 같이 예상 이벤트 구조로 데이터를 전송합니다. new Dictionary<byte, object>(){ { 245, eventData },{ 254,senderActorNr } }. 다음의 헬퍼 또는 랩퍼 메소드 중 하나를 사용할 수 있습니다:

메인 화면으로

아웃바운드 HTTP 호출

HttpRequest는 HTTP 요청을 구성하기 위한 헬퍼 클래스입니다. 이 클래스를 사용하여, URL과 HTTP 메소드를 설정할 수 있으며(기본은 "GET"입니다) AcceptContentType 헤더가 필요합니다. 이 속성들의 값은 HttpWebRequest에 의해 제공됩니다. 추가적으로 다른 사용자 지정 HTTP 헤더를 IDictionary<string, string>로 지정할 수 있으며, HttpRequest.CustomHeaders에 할당할 수 있습니다. 먼저 MemoryStream 객체로 변환한 다음 HttpRequest.DataStream에 할당하여 요청 데이터를 추가할 수도 있습니다. 자세한 내용은 JSON 포스트 예제를 참조하세요.

주목할 만한 점은 Photon에 배타적이고 플러그인 로직에 중요한 두 가지 속성이 있다는 것입니다:
  • Async: 응답이 수신될 때까지 룸 로직의 정상적인 처리를 중단해야 하는지를 나타내는 플래그입니다. 룸 로직이 HTTP 응답에 따라 달라지는 경우 이 값은 false로 설정해야 합니다.
  • UserState: 각 요청에 대해 Photon Server에 의해 저장되고 응답의 콜백으로 다시 전송되는 객체.

또한 HttpRequest 클래스는 다음 서명을 가져야 하는 응답 콜백에 대한 참조를 포함해야 합니다: public delegate void HttpRequestCallback(IHttpResponse response, object userState).

요청 객체가 설정되면, IPluginHost.HttpRequest(HttpRequest request, ICallInfo info)를 호출하여 요청 객체를 전송할 수 있습니다.

동기 모드에서 HTTP 요청을 보내는 경우 HTTP 응답을 기다리지 않고 ICallInfo 객체를 바로 처리하는 것은 의미가 없습니다. 아무런 소득도 없이 룸 파이버를 차단하는 것입니다. 응답 콜백이 트리거 될 때까지 플러그인 호출만 처리하는 것이 좋습니다.

메인 화면으로

비동기 HTTP 요청

HTTP 응답이 수신될 때까지 후크 처리를 지연할 수 있습니다.:

HttpRequest request = new HttpRequest()
{
  Callback = OnHttpResponse,
  Url = yourCustomUrl,
  Async = true,
  UserObject = yourOptionalUserObject
};
PluginHost.HttpRequest(request, info);

응답을 받으면 필요한 경우 ICallInfo를 어떻게 처리할 것인지 결정해야 합니다.

private void OnHttpResponse(IHttpResponse response, object userState)
{
  ICallInfo info = response.CallInfo;
  ProcessCallInfoIfNeeded(info);

메인 화면으로

HTTP 응답 처리

응답 콜백에서, 첫 번째로 확인해야할 것은 IHttpResponse.Status입니다. 다음 HttpRequestQueueResult 값중 하나를 가질 수 있습니다:

  • Success (0): 엔드 포인트가 성공적인 HTTP 응답 코드를 리턴(즉, 2xx codes).
  • 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: WebExceptionStatus의 코드가 들어있습니다. 연속적인 WebException을 나타냅니다.
  • HttpCode: 리턴된 HTTP 상태 코드가 들어있습니다.

코드에서 어떻게 다루는지에 대한 예제입니다:

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 응답으로부터 데이터를 얻는 두 가지 방법을 제공합니다. IHttpResponse를 구현하는 클래스에는 두 가지 속성이 노출되어 있습니다.

  • ResponseData: 응답 바디의 바이트 배열입니다. 수신된 데이터가 텍스트가 아닌 경우 유용할 수 있습니다.
  • ResponseText: 응답 바디의 UTF8 문자열 버전입니다. 수신된 데이터가 텍스트인 경우 유용할 수 있습니다.

응답 클래스에는 나중에 필요할 경우를 대비하여 해당하는 HttpRequest에 대한 참조도 있습니다. IHttpResponse.Request에서 사용할 수 있습니다.

메인 화면으로

타이머

타이머는 특정 기간이 지난 후 메소드를 호출할 목적으로 설정할 수 있는 개체입니다. 타이머가 생성되면 자동으로 카운트다운이 시작됩니다. 플러그인에서 코드 실행을 예약하거나 지연시키는 가장 좋은 방법입니다.

Photon 플러그인 SDK는 사용 사례에 따라 두 가지 유형의 타이머를 제공합니다:

메인 화면으로

일회성 타이머

일회성 타이머는 적절한 시간 후에 한 번 메소드를 트리거 합니다. 이러한 타이머를 생성하려면 다음 방법을 사용해야 합니다:
object CreateOneTimeTimer(ICallInfo callInfo, Action callback, int dueTimeMs);
예약된 작업을 취소하기 전에 취소하려는 경우가 아니면 이러한 종류의 타이머를 중지할 필요가 없습니다. 작업을 취소하려면 void IPluginHost.StopTimer(object timer)를 사용합니다.

예제: 지연 SetProperties

public override void BeforeSetProperties(IBeforeSetPropertiesCallInfo info)
{
  PluginHost.CreateOneTimeTimer(
                info,
                () => info.Continue(),
                1000);
}

메인 화면으로

반복 타이머

반복 타이머는 주기적으로 메소드를 호출합니다. 첫 번째 콜백의 실행 시간과 다음 연속 실행 사이의 간격을 정의할 수 있습니다. 이런 타이머를 생성하기 위해서는 다음의 메소드를 사용해야 합니다: object CreateTimer(Action callback, int dueTimeMs, int intervalMs); 이러한 종류의 타이머는 실행 중이고 플러그인이 로드되어 있는 한(룸이 닫히지 않음) 해당 메소드를 계속 호출합니다. void IPluginHost.StopTimer(object timer)를 사용하여 언제든지 중지할 수 있습니다.

예제: 예정된 이벤트

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>() { { 245, "Time is up" } });
}
public override void BeforeCloseGame(IBeforeCloseGameCallInfo info)
{
  PluginHost.StopTimer(timer);
  info.Continue();
}

메인 화면으로

사용자 지정 유형

Photon이 사용자 지정 클래스의 직렬화를 지원하도록 하려면 해당 클래스의 유형을 등록해야 합니다. 각 유형에 대해 코드(바이트)를 수동으로 할당하고 클래스의 필드 및 속성의 직렬화 및 역 직렬화 방법을 제공해야 합니다. 신규 타입 등록 시 동일한 코드를 클라이언트에서도 사용해야 합니다. 그런 다음 등록을 완료하려면 다음 메소드를 호출해야 합니다:

bool IPluginHost.TryRegisterType(Type type, byte typeCode, Func<object, byte[]> serializeFunction, Func<byte[], object> deserializeFunction);

예제: CustomPluginType 등록하기

다음은 사용자 지정 클래스를 등록하는 예제입니다:

class CustomPluginType
{
  public int intField;
  public byte byteField;
  public string stringField;
}
사용자 지정 타입 등록은 양쪽 모두 완료되어야 합니다. 즉, Photon 클라이언트는 동일한 코드 및 직렬화 방법으로 사용자 지정 유형도 등록해야 합니다.

직렬화 메소드는 사용자 지정 형식의 개체를 바이트 배열로 변환해야 합니다. 먼저 예측되는 타입으로 객체를 캐스팅해야 한다는 것에 주의하세요(CustomPluginType).

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();
    }
  }
}

역직렬화 메소드는 반대로 해야 합니다. 사용자 지정 유형 개체를 바이트 배열에서 다시 생성합니다.

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에서 플러그인이 초기화되는 즉시 수행할 수 있습니다:

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 객체를 생성하고 플러그인에서 발생하는 모든 것을 기록하는 데 사용합니다:

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 고객들은 당사로 연락하여 원하는 로깅 서비스로 프라이빗 클라우드를 구성할 것을 요청드립니다. Logentries를 사용하려면 구성된 로그 토큰을 제공하십시오. Papertrail 사용하려는 경우사용자 지정 URL과 포트 번호를 제공해 주십시오.

메인 화면으로

자체 호스팅 된 서버 로깅 환경 구성

기본값으로, 로그 출력은 GameServer 로그 항목과 더불어 "GSGame.log" 파일에서 확인할 수 있습니다. 분리된 로그 파일을 사용하기 위해서는, 다음 코드를 GameServer의 log4net 환경 구성 파일에 추가하십시오:

<!-- "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>

메인 화면으로

Enterprise Cloud에서의 플러그인 버전 관리

Currently Photon Plugins support only side-by-side assembly versioning: one plugin DLL version per AppId.

Here are two methods we recommend for rolling out new plugins versions:

A. "Compatible" plugins deploy: does not require new client version

  1. Upload new version of plugins assembly.
  2. On a staging AppId: test to verify that new version works as expected. (recommended)
  3. Update production AppId configuration to use new plugins assembly version.

B. "Incompatible" plugins deploy: requires new client version

  1. Upload new version of plugins assembly.
  2. Setup a new production AppId.
  3. Configure the new production AppId to use new plugins assembly version.

메인 화면으로

PUN 특정 플러그인

클라이언트 SDK로 PUN을 사용하고 PUN과 함께 작동하는 서버 측 플러그인을 구현하려면 다음을 알아야 합니다:

  • PUN은 유니티 기본 클래스를 중심으로 일부 추가적인 사용자 지정 유형을 등록합니다. 플러그인에서 이러한 사용자 지정 유형을 처리하려면 플러그인에서도 동일한 사용자 지정 유형을 등록해야 합니다. 이러한 모든 사용자 지정 유형과 등록 방법은 PUN 패키지의 "CustomTypes.cs" 클래스에서 확인할 수 있습니다. 일부 사용자 지정 유형을 등록하지 않으면 오류 또는 예기치 않은 동작이 발생할 수 있습니다. 이러한 문제점들에 대해서 알림을 받기 위해서는 IGamePlugin.OnUnknownType 또는 IGamePlugin.ReportError을 구현해야 합니다.

  • 모든 PUN의 높은 수준의 고유한 기능은 자세하게 살펴보면 RaiseEvent를 사용합니다. 각 기능은 하나 이상의 이벤트 코드와 특수 이벤트 데이터 구조를 사용합니다. PUN에서 예약한 이벤트 목록을 가져오려면 PUN 패키지 내의 "PunClasses.cs" 파일에서 "PunEvent" 클래스를 확인하십시오. 예를 들어 OnSerializeView 호출을 인터셉트하려면 OnRaiseEvent 콜백을 구현하고 해당 유형의 이벤트 코드(이 경우 "SendSerialize = 201")를 캐치해야 합니다. 각 이벤트의 예상 콘텐츠를 확인하려면 PUN 코드에서 이벤트 데이터가 어떻게 구성되는지 살펴보거나 플러그인 내부의 수신 이벤트에서 해당 데이터를 검사하십시오.

  • Photon 플러그인 내의 PhotonNetwork.ServerTimestamp를 받기 위해서는 Environment.TickCount를 사용하세요.

메인 화면으로

AuthCookie

보안 데이터라고도 하는 AuthCookie는 인증 공급자로 설정된 웹 서비스에서 리턴하는 선택적 JSON 개체입니다. 클라이언트 측에서 이 개체에 접근할 수 없습니다. 자세한 내용은 사용자 지정 인증 문서 페이지를 참조하십시오.

플러그인에서 다음과 같이 AuthCookie에 접근할 수 있습니다:

  • ICallInfo.AuthCookie: 훅을 작동시키는 현재 배우의 AuthCookie를 얻기 위해. 하지만 사용자 컨텍스트의 외부에서 트리거 되기 때문에 OnBeforeCloseGame 그리고 OnCloseGame, IBeforeCloseGameCallInfo.AuthCookie 그리고 ICloseGameCallInfo.AuthCookie 내에서는 각각 아무런 값을 갖고 있지 않습니다. 예,

        public void OnCreateGame(ICreateGameCallInfo info)
    {
        Dictionary<string, object> authCookie = info.AuthCookie;
  • IActor.Secure: 활성 액터에서 AuthCookie의 취득 예,

        foreach (var actor in this.PluginHost.GameActorsActive)
    {
        var authCookie = actor.Secure as Dictionary<string, object>;
    }
  • 액터 별로 보안된 인증 쿠키를 업데이트를 하기 위해서 void IActor.UpdateSecure(string key, object value)를 사용하세요.

기술문서 TOP으로 돌아가기