유니티(Unity) 플러그인

아이펀 엔진을 서버로 사용하는 유니티 게임 클라이언트 개발을 돕는 플러그인 입니다.

유니티 플러그인에서는 아래와 같은 기능들을 제공합니다.

  • TCP, UDP, HTTP, Websocket 프로토콜 사용 가능

  • JSON, Protocol Buffers 형식의 메시지 타입 지원

  • ChaCha20, AES-128 을 포함한 4종류의 암호화 타입 지원

  • 멀티캐스트, 채팅, 게임내 리소스 다운로드 등 다양한 기능 지원

  • 페이스북, 트위터의 로그인, 친구 정보 요청 등의 기능 지원

이 문서에서는 유니티 플러그인의 사용방법, 주의할 사항, 사용팁 등을 설명합니다.

유니티 3D 지원 버전에 대해서

유니티 3D 버전

Windows

macOS

Android

iOS

5.x

o

o

o

o

2017.x

o

o

o

o

2018.x

o

o

o

o

2019.x

o

o

o

o

2020.x

o

o

o

o

2021.x

o

o

o

o

  • Android 의 경우 arm 만 지원합니다.

  • Android 5 (API Level 22) 이하 버전에서는 deflate 압축 기능이 동작하지 않습니다.

시작하기

유니티 플러그인을 사용하기 위해서는 유니티 프로젝트에 플러그인 파일을 포함시켜야 합니다. 유니티 플러그인은 여기 에서 받으실 수 있습니다.

Important

stable, experimental 버전 모두 암호화 기능 중 ife1, ife2 암호화 방식은 사용할 수 없는 상태로 배포됩니다. ife1, ife2 암호화 방식을 사용하기 희망하시는 분은 iFun Engine support 로 요청해 주시기 바랍니다.

git clone 으로 다운 받거나 zip 파일을 받아 압축을 풀면 아래와 같은 폴더 목록을 확인할 수 있습니다.

|- additional-Plugins
|- csharp-samples
|- funapi-plugin-unity
|- dedicated-server-plugin

각 폴더 내용은 다음과 같습니다.

  • funapi-plugin-unity : 클라이언트 플러그인 코드

  • dedicated-server-plugin : 데디케이티드 서버와 게임 서버 간의 통신을 할 수 있게 해주는 RPC 플러그인

  • additional-plugins : 페이스북과 트위터용 플러그인

  • csharp-samples: macOS/LinuxWindowsC# Runtime 샘플 코드

다운받은 폴더 중에 funapi-plugin-unity/Assets 폴더로 들어가면 아래와 같은 목록이 나타납니다.

|- Editor
|- Funapi
|- JsonDotNet
|- Plugins
|- Resources
|- Sample
|- FunMessageSerializer.dll
|- messages.dll
|- protobuf-net.dll
|- ...

위의 폴더 중 Funapi, Plugins, JsonDotNet 폴더를 플러그인을 사용할 프로젝트의 Assets 폴더로 복사하면 됩니다. 암호화 기능이나 압축 기능을 사용하려면 Resources 폴더도 함께 복사해 주세요. HTTPS 를 사용하려면 Resources 폴더와 함께 Editor 폴더도 복사해 주세요. 웹소켓을 사용하고 싶다면 websocket-sharp.dll 파일과 WebSocket.jslib 파일을 프로젝트의 Assets 폴더로 복사해야 합니다.

마지막으로 메시지 인코딩 형식으로 Protocol Buffers 를 사용하는 경우에는 서버와 동일한 .proto 파일을 가지고 빌드한 DLL 도 복사해야 합니다.

플러그인 기본 프로젝트에는 아이펀 엔진 서버의 기본 .proto 로 빌드한 DLL 이 포함되어 있지만, 개발 과정에서 메시지 정의를 변경한 경우는 클라이언트 쪽 DLL 도 함께 업데이트 해야 하는 점에 유의하시기 바랍니다.

자세한 빌드 방법은 다음 절에서 설명하겠습니다.

Protocol Buffers 메시지 용 DLL 빌드하기

iOS / Android의 제약사항 때문에 protobuf-net 으로 생성한 C# 코드를 DLL 대신 사용할 수 없습니다. protobuf-net 이 생성하는 C# 파일을 바로 사용하면 메시지를 읽는 some_protobuf_message.GetExtension(...) 형태의 코드에서, 아래와 유사한 오류 메시지를 남기면서 libmono 의 JIT 컴파일러 안에서 크래시 합니다.

F/mono  (1021): * Assertion at mini-arm.c:2595, condition 'pdata.found == 1' not met
F/libc  (1021): Fatal signal 11 (SIGSEGV) at 0x0000600d (code=-6), thread 1033 (UnityMain)

따라서 메시지 serialize/deserialize 하는 부분을 C# 코드가 아닌 AOT 빌드한 .dll 을 사용하도록 구현해야 합니다.

Protobuf 용 .dll 빌드하게 설정하기

아이펀 엔진 게임 서버의 CMakeLists.txt 파일의 GENERATE_UNITY_PROTOBUF_DLL 라는 설정을 true 로 변경하면 게임 서버를 빌드할 때, C# 에서 사용할 수 있는 .dll 파일을 함께 생성합니다.

# Source managed by git can append a build number from git commit id.
set(PACKAGE_WITH_BUILD_NUMBER_FROM_GIT false)

# Source managed by svn can append a build number from svn info.
set(PACKAGE_WITH_BUILD_NUMBER_FROM_SVN false)

# Generate .DLL for Unity Engine
set(GENERATE_UNITY_PROTOBUF_DLL true)

생성한 .dll 파일 복사하기

게임 서버 빌드 디렉터리, 예를 들어 hello 란 프로젝트의 디버그 설정을 빌드한 경우에는 hello-build/debug/unity_dll 디렉터리에 다음 .dll 파일이 생성됩니다.

  • protobuf-net.dll : Unity 엔진 용의 protobuf-net 파일

  • messages.dll: 엔진에서 생성한 protobuf 메시지 정의

  • FunMessageSerializer.dll: 엔진에서 생성한 protobuf 읽고/쓰는 루틴

이 파일을 복사해서 클라이언트의 Assets 디렉터리에 복사하고, Unity 에디터에서 확인하시면 됩니다.

Tip

리눅스 서버에 생성된 .dll 파일을 가져 올 때는 여기 에서 제공하는 프로그램을 사용하면 편리합니다.

Tip

개별 메시지와 메시지들의 enum 에 대한 소스 코드는 서버 빌드 디렉터리 밑의 unity_cs 디렉터리에서 messages.cs, messages_enum.cs 파일로 생성됩니다.

위 설정까지 완료했다면 이제 유니티 프로젝트에서 클라이언트 플러그인 기능을 사용할 수 있습니다.

서버에 연결하기

플러그인을 활용하여 구현하는 방법을 코드 예제와 함께 간략히 소개합니다.

Tip

전체 코드는 플러그인에 포함된 예제 프로젝트에서 확인하실 수 있습니다. (Assets/Sample/Test.cs 파일 참고) Test.unity Scene 을 실행하면 서버 주소, 포트, 프로토콜, 인코딩 타입을 입력할 수 있습니다. 이 값들을 info 객체에 저장해서 사용합니다.

FunapiSession 클래스

하나의 Session 을 관리하는 클래스입니다. Tranpsort 를 관리하고 메시지를 송수신하는 등 하나의 Session 으로 할 수 모든 기능을 담고 있습니다.

하나의 세션으로 연결할 수 있는 서버는 하나입니다. 처음 세션을 생성할 때 서버 주소가 정해지면 이 후 서버 주소는 변경할 수 없습니다. 그 외 Tranpsort 옵션이나 포트 번호 등은 변경이 가능합니다.

사용이 끝난 FunapiSession 객체는 FunapiSession.Destroy() 함수를 호출해 명시적으로 객체를 해제해 주어야 더 이상 업데이트가 이루어지지 않습니다. 앱 전체에서 시작부터 끝날 때까지 FunapiSession 객체를 하나만 사용할 경우에는 굳이 호출하지 않아도 괜찮습니다. OnApplicationQuit 이 호출되면 자동으로 해제됩니다.

Note

버전 247 이전의 플러그인 에서는 Session 마다 MonoBehaviour 객체를 생성/삭제했었는데 플러그인 종료처리가 완료되기 전에 MonoBehaviour 객체가 삭제되어 이후 처리가 안되는 경우가 있어 플러그인용 MonoBehaviour 객체를 하나만 두고 공용으로 사용하는 방식으로 변경되었습니다. 이로 인해 사용자가 사용이 끝난 FunapiSession 객체에 대해 FunapiSession.Destroy() 함수를 호출해 명시적으로 객체를 해제해 주어야 더 이상 업데이트가 이루어지지 않습니다.

Tip

플러그인 버전 158 이후 플러그인을 더 쉽고 편하게 사용할 수 있도록, 기존 FunapiNetwork 클래스를 대체하는 FunapiSession 클래스가 추가되었습니다.

FunapiNetwork는 더 이상 업데이트되지 않습니다. (버전 247에서 파일도 삭제됨) 기존의 FunapiNetwork에 대한 도움말은 이전 클라이언트 플러그인 설명 문서를 참고해주세요.

Session Option

FunapiSession 을 생성할 때 SessionOption 을 전달하는데 아래는 옵션에 대한 설명입니다. 세션 옵션을 전달하지 않을 경우 기본 값을 사용하게 됩니다.

public class SessionOption
{
    // 메시지 순서를 보장합니다. 재연결시에도 이전 메시지와의 순서를 동기화합니다.
    public bool sessionReliability = false;

    // sessionReliability 옵션을 사용할 경우 서버와 ack 메시지를 주고 받는데
    // 이 옵션을 사용할 경우 piggybacking, delayed sending 등을 통해 네트워크 트래픽을 줄여줍니다.
    // 옵션 값은 ack를 보내는 간격에 대한 시간 값이며 초단위 값을 사용합니다.
    // 시간 값이 0f 보다 클 경우 piggybacking 은 자동으로 이루어집니다.
    public float delayedAckInterval = 0f;

    // 기본적으로 모든 메시지에 session id 값이 포함되며 이 옵션을 true 로 변경할 경우
    // 처음 연결시에만 session id 를 보내고 그 이후 메시지에는 session id 를 포함하지 않습니다.
    public bool sendSessionIdOnlyOnce = false;

    // 서버이동 기능을 사용할 경우 이동에 대한 타임아웃 시간 값입니다. 초단위 값을 사용합니다.
    // 기본 값은 10초이며 해당 시간 이내에 서버 이동이 완료되지 못할 경우 서버이동 실패로 처리합니다.
    public int redirectTimeout = 10;

    // 서버이동시에 이동 도중에 보낸 메시지들은 기본적으로 버려집니다.
    // 이 값을 true 로 변경할 경우 서버이동 도중 버려지는 메시지들에 대해 이동한 서버로의 메시지 전송을 보장합니다.
    // 메시지를 전달하기 전에 RedirectQueueCallback 에 목록을 전달하므로 이 콜백에서 메시지를 확인할 수 있습니다.
    public bool useRedirectQueue = false;

    // 세션 별로 암호화에 사용하는 public key 를 지정할 수 있습니다.
    // 이 값이 null 일 경우 FunapiEncryptor 에 있는 public key 를 사용합니다.
    public string encryptionPublicKey = null;
}

FunapiSession 객체 생성

FunapiSession 객체를 생성하고 콜백을 등록하는 방법입니다.

// Session 옵션
SessionOption option = new SessionOption();
option.sessionReliability = true;
option.sendSessionIdOnlyOnce = true;

// FunapiSession 객체를 생성합니다.
// 전달하는 파라미터는 서버 주소와 SessionOption 입니다.
// option 값을 전달하지 않으면 기본 값을 사용합니다.
session = FunapiSession.Create(info.address, option);

// 이벤트 콜백을 등록합니다.
session.SessionEventCallback += onSessionEvent;
session.TransportEventCallback += onTransportEvent;
session.TransportErrorCallback += onTransportError;

// 받은 메시지를 처리하기 위한 콜백을 등록합니다.
session.ReceivedMessageCallback += onReceivedMessage;

플러그인에서 사용하는 콜백은 유니티의 event 객체를 사용하고 있습니다. 이벤트로 콜백을 등록하는 방법은 예제와 같이 ‘+=’ 연산자를 사용합니다. 하나의 이벤트에 여러 개의 콜백을 등록할 수도 있습니다.

Transport 연결

위에서 만든 Session 객체로 TCP 연결을 시작합니다.

// AutoReconnect 옵션을 사용하기 위해 옵션 객체를 생성합니다.
// 특정 옵션을 사용하려는 것이 아니라면 Connect의 옵션 파라미터는 생략할 수 있습니다.
TcpTransportOption tcp = new TcpTransportOption();
tcp.AutoReconnect = false;    // 기본값이 false 입니다.

// 서버에 연결하기 위해서는 프로토콜 타입, 인코딩 타입, 포트 번호가 필요합니다.
// 옵션 값은 필요할 경우 사용하면 되고 지정하지 않으면 기본 값을 사용합니다.
session.Connect(info.protocol, info.encoding, info.port, option);

Connect 함수는 지정한 파라미터를 사용하여 Transport를 생성하고 연결을 시도합니다.

같은 프로토콜의 Transport가 연결된 적이 있고 옵션 값이 같다면 Transport를 다시 생성하지 않고 해당 Trnasport를 재연결합니다. 옵션 값이 다를 경우에는 Transport를 새로 생성하게 됩니다.

기존 연결로 재연결하는 경우에는 파라미터로 프로토콜만 받는 Connect 함수를 사용하는 것이 좋습니다.

프로토콜과 인코딩 타입

아래는 Connect 함수에서 사용할 수 있는 protocolencoding 의 종류를 선언한 코드입니다.

// 프로토콜은 Tcp, Udp, Http, Websocket 4가지 타입을 지원합니다.
public enum TransportProtocol
{
  kDefault = 0,
  kTcp,
  kUdp,
  kHttp,
  kWebsocket
};

// 메시지 인코딩 방식은 JSON과 Protocol Buffers 2가지 방법이 있습니다.
public enum FunEncoding
{
  kNone,
  kJson,
  kProtobuf
}

이벤트 콜백 함수

FunapiSession에는 Session의 상태 변화를 알려주는 콜백과 Transport의 상태 변화를 알려주는 콜백 함수, Transport의 에러에 대해서 알려주는 콜백 함수가 있습니다. Transport에서 오류가 발생했을 경우, 에러 콜백과 함께 Transport의 이벤트 콜백이 함께 호출될 수도 있습니다.

Session Event

SessionEventCallback 에 콜백 함수를 등록하면 세션의 상태가 변경될 때마다 아래와 같은 세션 관련 이벤트 알림을 받을 수 있습니다.

public enum SessionEventType
{
  kOpened,             // 세션이 처음 연결되면 호출됩니다. 같은 세션으로 재연결시에는 호출되지 않습니다.
  kConnected,          // 모든 Transport의 연결이 완료되면 호출됩니다.
  kStopped,            // 세션에 연결된 모든 Transport의 연결이 끊기면 호출됩니다.
  kClosed,             // 세션이 닫히면 호출됩니다. Transport의 연결이 끊겼다고 세션이 닫히는 것은 아닙니다.
                       // 세션은 서버에서 명시적으로 Close가 호출되거나 세션 타임아웃이 되었을 때 종료됩니다.

  kRedirectStarted,    // Redirect 관련 이벤트는 아래 '서버간 이동' 항목을 참고해주세요.
  kRedirectSucceeded,
  kRedirectFailed
};

FunapiSession 에 있는 SessionEventCallback 은 다음과 같이 선언되어 있습니다.

void SessionEventHandler (SessionEventType type, string session_id);

Transport Event

Transport와 관련된 이벤트는 아래와 같습니다. kStarted 를 제외하고는 모두 연결에 실패하거나 연결이 끊겼을 때 발생하는 이벤트들입니다. kStopped 는 정상적으로 연결이 종료되었거나 오류로 인해 연결이 끊겼거나 Transport의 연결이 끊기면 항상 발생하는 이벤트입니다.

public enum TransportEventType
{
  kStarted,              // 서버와 연결이 완료되면 호출됩니다.
  kStopped,              // 서버와 연결이 종료되거나 연결에 실패하면 호출됩니다.
  kReconnecting          // 재연결을 시작할 때 호출됩니다.
};

이전에는 Disconnect 등 연결 종료에 대한 여러가지 타입의 이벤트가 있었으나 플러그인 버전 246 이후부터는 kStopped 이벤트 하나로 통일되었습니다. 어떤 이유로 연결이 끊겼던지 Transport 이벤트는 kStopped 하나만 호출됩니다. 자세한 종료 사유에 대해서는 로그로 확인하거나 FunapiSession.GetLastError 로 확인할 수 있습니다.

FunapiSession 에 있는 TransportEventCallback 은 다음과 같이 선언되어 있습니다.

void TransportEventHandler (TransportProtocol protocol, TransportEventType type);

Transport Error

Tranpsort의 에러 콜백의 경우에는 에러 타입과 함께 에러 메시지도 함께 전달됩니다.

public class TransportError
{
  public enum Type
  {
    kNone,
    kStartingFailed,      // Transport 초기화에 실패했을 때 호출됩니다.
    kConnectionTimeout,   // 연결 제한시간을 초과했을 경우에 호출됩니다.
    kEncryptionFailed,    // 메시지 암호화에 실패했을 때 호출됩니다.
    kSendingFailed,       // 메시지를 보내는 과정에서 오류가 발생했을 때 호출됩니다.
    kReceivingFailed,     // 메시지를 받는 과정에서 오류가 발생했을 때 호출됩니다.
    kRequestFailed,       // HTTP 요청에 실패했을 때 호출됩니다.
    kWebsocketError,      // Websocket에서 오류가 발생했을 때 호출됩니다.
    kDisconnected         // 예기치 않게 서버와의 연결이 끊겼을 때 호출됩니다.
  }

  public Type type = Type.kNone;
  public string message = null;
}

FunapiSession 에 있는 TransportErrorCallback 은 다음과 같이 선언되어 있습니다.

void TransportErrorHandler (TransportProtocol protocol, TransportError type);

Transport 에서 오류가 발생했을 때는 해당 Transport 의 연결이 중단됩니다.

Tip

Tranpsort에서 오류는 대부분 연결이 불안정하거나 망 변경 등의 이유로 재연결이 필요한 상태에서 발생합니다. Tranpsort 오류가 발생하면 FunapiSession 의 Stop 을 호출해서 모든 연결을 끊고 SessionEventType 의 kStopped 이벤트가 호출되면 FunapiSession 의 Reconnect 로 재연결을 시도하는 것이 좋습니다.

Transport 옵션

FunapiSession의 Connect 함수를 호출할 때 파라미터로 Transport의 옵션을 전달할 수 있습니다. 이 값이 null인 경우에는 기본 값을 사용하게 됩니다. 사용할 프로토콜 (TCP, UDP,HTTP)에 따라 그에 맞는 TransportOption 클래스를 생성하여 지정해야 합니다. 예를 들어 TCP의 경우, TcpTransportOption 입니다. UDP의 경우에만 기본 클래스인 TransportOption 을 사용하면 됩니다.

아래는 TransportOption 클래스가 정의된 코드입니다.

Base 클래스, UDP 옵션

Transport 옵션의 기본 클래스입니다. UDP 연결에만 이 기본 클래스를 사용할 수 있습니다.

public class TransportOption
{
    // 암호화 타입을 지정합니다. 암호화를 사용하지 않을 경우 이 값을 변경하지 마세요.
    // 서버에서 암호화를 사용할 경우 이 값을 서버와 같은 타입 값으로 입력해야 합니다.
    // TCP의 경우 값을 입력하지 않아도 서버에서 사용중이라면 클라이언트도 암호화를 사용하게 됩니다.
    public EncryptionType Encryption = EncryptionType.kDefaultEncryption;

    // 서버와 주고 받는 메시지를 압축 할 수 있습니다.
    // 값을 지정하지 않을 경우 압축 기능을 사용하지 않습니다.
    // 압축 타입은 서버와 같은 값을 입력해야 합니다.
    public FunCompressionType CompressionType = FunCompressionType.kNone;

    // 메시지에 sequence number를 붙여서 메시지의 유효성을 보장해주는 옵션입니다.
    // Session reliability 옵션을 사용하지 않고 메시지의 유효성만 보장하고 싶을 때 사용할 수 있습니다.
    // Session reliability 옵션을 사용하면 이 옵션은 무시됩니다.
    // TCP, HTTP 프로토콜에서만 사용 가능합니다.
    public bool SequenceValidation = false;

    // 서버와 연결할 때 Timeout 될 시간을 지정합니다.
    // 기본 값은 10초이며, 0을 입력할 경우 Timeout 처리를 하지 않고 계속 연결을 시도합니다.
    // 이 경우, 서버로부터 응답이 오지 않으면 무한히 대기하는 상황이 발생할 수 있기 때문에
    // 디버깅 목적으로만 사용하기를 권장합니다.
    // Timeout에 설정한 시간을 초과하면 연결 시도를 중단하고, kStopped 이벤트를 발생시킵니다.
    public float ConnectionTimeout = 10f;
}

TCP 옵션

TCP Transport 옵션 클래스입니다. TransportOption 값도 포함하면서 TCP에만 적용되는 옵션이 추가되어 있습니다.

public class TcpTransportOption : TransportOption
{
    // 이 값이 true일 경우 연결에 실패하거나 연결이 끊겼을 경우 재연결을 시도합니다.
    // 재시도 Timeout을 정하고 싶다면 ConnectionTimeout 값을 입력하면 됩니다.
    // ConnectionTimeout 시간을 초과하면 재시도를 중단하고 kStopped 이벤트가 호출됩니다.
    public bool AutoReconnect = false;

    // 네이글 알고리즘은 효율적인 네트워크 사용을 위해 작은 크기의 패킷을 모아서 전송하는 기능입니다.
    // 이 값을 true로 하면 네이글 알고리즘을 사용하지 않습니다.
    // 기본 값은 false로 네이글 알고리즘을 사용합니다.
    public bool DisableNagle = false;

    // 클라이언트 Ping 사용 옵션입니다.
    public bool EnablePing = false;
    // Ping 값을 로그로 표시할지 여부를 결정합니다.
    public bool EnablePingLog = false;
    // Ping 메시지를 보내는 간격에 대한 시간 값입니다.
    public int PingIntervalSeconds = 0;
    // 서버로부터 Ping에 대한 응답을 기다리는 최대 시간 값입니다.
    // 이 시간 내에 Ping 응답이 오지 않는다면 서버와의 연결이 끊긴 것으로 보고 Disconnect 처리됩니다.
    // 인터벌 내에 핑 응답을 받지 못하면 핑을 재전송 하지 않고 핑 응답을 받은 후에 핑을 전송합니다.
    public float PingTimeoutSeconds = 0f;

    // TLS 사용 여부에 대한 옵션입니다.
    public bool UseTLS = false;

    // 위의 Ping 관련 옵션 값을 한 번에 설정할 수 있는 함수입니다.
    public void SetPing (int interval, float timeout, bool enable_log = false);
}

HTTP 옵션

HTTP Transport의 옵션 클래스입니다.

public class HttpTransportOption : TransportOption
{
    // HTTPS 사용 여부에 대한 옵션입니다.
    // 서버가 HTTPS일 경우 이 값을 true로 입력해야 합니다.
    public bool HTTPS = false;

    // HTTP 통신에는 기본적으로 HttpWebRequest 클래스를 사용합니다.
    // 이 옵션이 true 일 경우에는 UnityEngine.WWW 클래스를 사용합니다.
    // Unity 2017 이후 버전부터는 WWW 대신 UnityWebRequest 를 사용합니다.
    public bool UseWWW = false;
}

플러그인에서는 HTTP 요청을 처리할 때 HttpWebRequest 클래스를 사용하고 있습니다. Windows에서 Unity Editor로 이 기능을 사용하는 경우, 간혹 에디터가 응답하지 않는 경우가 있습니다. (소켓을 명시적으로 닫아주지 않아서 발생하는 문제) 이 문제를 피하기 위해 UnityEngine 의 WWW 클래스를 사용할 수 있도록 UseWWW 옵션이 추가되었습니다. 에디터에서만 발생하는 현상이므로 기본적으로는 이 옵션을 false 로 두고 에디터에서 실행할 때 문제가 있을 경우에만 WWW 클래스를 사용하는 것이 좋습니다. 유니티 2017 이후 버전부터는 WWW 클래스 대신 UnityWebRequest 클래스를 사용합니다.

Websocket 옵션

Websocket Transport의 옵션 클래스입니다.

public class WebsocketTransportOption : TransportOption
{
    // WSS 사용 여부에 대한 옵션입니다.
    // 서버가 WSS일 경우 이 값을 true로 입력해야 합니다.
    public bool WSS = false;

    // 클라이언트 Ping 사용 옵션입니다.
    public bool EnablePing = false;
    // Ping 값을 로그로 표시할지 여부를 결정합니다.
    public bool EnablePingLog = false;
    // Ping 메시지를 보내는 간격에 대한 시간 값입니다.
    public int PingIntervalSeconds = 0;
    // 서버로부터 Ping에 대한 응답을 기다리는 최대 시간 값입니다.
    // 이 시간 내에 Ping 응답이 오지 않는다면 서버와의 연결이 끊긴 것으로 보고 Disconnect 처리됩니다.
    // 인터벌 내에 핑 응답을 받지 못하면 핑을 재전송 하지 않고 핑 응답을 받은 후에 핑을 전송합니다.
    public float PingTimeoutSeconds = 0f;

    // 위의 Ping 관련 옵션 값을 한 번에 설정할 수 있는 함수입니다.
    public void SetPing (int interval, float timeout, bool enable_log = false);
}

메시지 전송 및 수신

메시지 전송

메시지를 보내고 싶다면 FunapiSession 클래스의 SendMessage 함수를 호출하면 됩니다.

public void SendMessage (string msg_type, object message,
                         TransportProtocol protocol = TransportProtocol.kDefault,
                         EncryptionType enc_type = EncryptionType.kDefaultEncryption);
// Protobuf 용 인터페이스
public void SendMessage (MessageType msg_type, object message,
                         TransportProtocol protocol = TransportProtocol.kDefault,
                         EncryptionType enc_type = EncryptionType.kDefaultEncryption)
public void SendMessage (int msg_type, object message,
                         TransportProtocol protocol = TransportProtocol.kDefault,
                         EncryptionType enc_type = EncryptionType.kDefaultEncryption)

msg_type 은 메시지 타입을 나타내는 string 타입의 파라미터입니다. Protobuf로 메시지를 보내는 경우에는 string 타입 대신에 enum MessageType 타입 또는 정수 값을 사용할 수도 있습니다.

message 는 실제 데이터가 포함된 메시지로 object 타입입니다. JSON, Protobuf 메시지를 모두 보낼 수 있습니다.

protocol 은 지정하지 않을 경우 FunapiSession에 처음 등록(Connect) 된 프로토콜로 메시지가 전송됩니다. Transport가 여러 개 등록되어 있고 특정 프로토콜로 메시지를 보내고 싶다면 protocol 값을 지정하면 됩니다.

FunapiSession에 여러 개의 프로토콜이 연결되어 있고 메시지를 보낼 때 기본으로 사용되는 프로토콜을 별도로 지정하고 싶다면 FunapiSession의 DefaultProtocol 프로퍼티 값을 입력하면 됩니다.

session.DefaultProtocol = TransportProtocol.kHttp;

enc_type 은 암호화 타입 파라미터입니다. 암호화를 사용하는데 타입을 지정하지 않을 경우 기본 타입으로 암호화를 하게 되고 암호화를 사용하지 않을 경우에 이 값을 입력하면 오류가 발생합니다.

서버에서 하나의 프로토콜에 여러 종류의 암호화를 허용할 경우 메시지를 보낼 때 암호화 타입을 선택해서 보낼 수 있습니다. 암호화에 대한 좀 더 자세한 설명은 메시지 암호화 를 참고해 주세요.

JSON 메시지 보내기

Dictionary<string, object> message = new Dictionary<string, object>();
message["message"] = "hello world";
session.SendMessage("echo", message);

플러그인에서는 JSON 을 사용하기 위해 MiniJSON 라이브러리를 사용합니다. MiniJSON 의 데이터 타입은 Dictionary<string, object> 입니다. 필요한 경우 다른 JSON 라이브러리를 사용할 수도 있습니다. 자세한 설명은 JSON Helper 클래스 를 참고해 주세요.

Protocol Buffers 메시지 보내기

PbufEchoMessage echo = new PbufEchoMessage();
echo.msg = "hello proto";
FunMessage message = FunapiMessage.CreateFunMessage(echo, MessageType.pbuf_echo);
session.SendMessage("pbuf_echo", message);
//
// 메시지 타입으로 enum MessageType 타입을 사용할 수 있습니다.
// session.SendMessage(MessageType.pbuf_echo, message);
// 또는 해당 메시지의 정수값을 직접 사용해도 됩니다.
// session.SendMessage(16, message);
...

Protobuf 메시지의 기본형은 FunMessage 입니다. 사용자 메시지는 extend 형태로 되어있습니다. extend 메시지 중 0 ~ 15번까지는 예약된 메시지입니다. 사용자 메시지는 16번 필드부터 사용이 가능합니다.

MessageType 은 서버에서 .proto 파일들을 빌드해서 메시지 DLL을 만들 때 함께 생성되는 enum 리스트입니다.

예를 들어 서버에서 다음과 같이 메시지를 정의했다면,

message PbufEchoMessage {
  required string msg = 1;
}

extend FunMessage {
  optional PbufEchoMessage pbuf_echo = 16;
  ...
}

아래와 같이 MessageType 이 생성됩니다.

public enum MessageType
{
  ...
  pbuf_echo = 16,
}

이를 통해 extend 메시지들의 숫자 대신 메시지 이름으로 사용할 수 있습니다.

MessageType 은 기본적으로 int 타입 으로 처리됩니다. 다만 기존 인터페이스와의 호환을 위해 받은 메시지의 메시지 타입은 다시 string 으로 변환해서 전달하고 있습니다. 서버가 구버전이라 int 타입의 메시지를 처리하지 못한다면 PROTOBUF_ENUM_STRING_LEGACY define을 심볼에 추가하면 MessageType을 string 타입으로 처리할 수 있습니다.

Important

Unity에서 Protobuf-net 기반의 메시지를 사용하려면 추가적인 빌드 과정을 따라야 합니다. Protocol Buffers 메시지 용 DLL 빌드하기 설명을 참고해주세요.

Delayed ack, Piggy back 기능

Session Reliability 기능을 사용할 경우 메시지 동기화를 위해 서버로부터 메시지를 받을 때마다 ack (서버가 보낸 메시지를 받았다는 확인 메시지)를 보내는데 메시지를 받을 때마다 ack 메시지를 보내는 것이 부담스러울 수 있습니다. 이런 부담을 줄이기 위해 이후에 보내는 메시지에 ack를 실어 보내거나 일정 간격을 두고 한 번 씩만 보내도록 할 수 있습니다.

Session 옵션의 delayedAckInterval 값을 설정하면 Delayed ack, Piggy back 기능이 활성화됩니다.

public class SessionOption
{
    public float delayedAckInterval = 0f;    // seconds
    ...
}

delayedAckInterval 값이 설정되면 가능하면 보내는 메시지에 ack를 담아 보내고 그렇지 못 할 경우에는 정해진 간격마다 보내야 할 ack가 있는지 확인하고 ack 메시지를 보냅니다.

서버의 설정과는 별개로 동작하며 서버에서는 MANIFEST 파일에서 delayed_ack_interval_in_ms 값을 0보다 큰 값으로 설정해주면 됩니다.

Dropped 메시지

메시지를 보낼 수 없는 상태에서 메시지 전송 시 해당 메시지는 전송되지 않고 버려집니다. 이 때 DroppedMessageCallback 이 등록되어 있다면 이 콜백으로 버려진 메시지를 전달합니다.

메시지를 보낼 수 없는 상태는 아래와 같습니다.

  1. 리다이렉트 중이고 리다이렉트 큐를 사용하지 않을 때

  2. 메시지를 보내려는 프로토콜의 트랜스포트가 없을 때

  3. 메시지를 보내려는 프로토콜의 트랜스포트가 Reliable (Session Reliability + TCP) 하지 않고 연결된 상태가 아닐 때

  4. 메시지를 보내려는 트랜스포트에 설정된 인코딩(Protobuf)과 보내려는 메시지 형식(FunMessage)이 일치하지 않을 때

DroppedMessageCallback 은 다음과 같이 선언되어 있습니다.

public delegate void DroppedMessageCallback(string msg_type, object message);

데이터형이 object인 이유는 받는 메시지가 JSON일 수도 있고 Protobuf 메시지일 수도 있기 때문입니다. 등록한 핸들러에서 메시지 타입에 따라 message 객체를 JSON이나 Protobuf로 캐스팅해서 사용하면 됩니다.

메시지 수신

서버로부터 메시지를 받기 위해서는 메시지 콜백 함수를 등록해야 합니다. ReceivedMessageCallback 은 다음과 같이 선언되어 있습니다.

public delegate void (string msg_type, object message);

데이터형이 object인 이유는 받는 메시지가 JSON일 수도 있고 Protobuf 메시지일 수도 있기 때문입니다. 등록한 핸들러에서 메시지 타입에 따라 message 객체를 JSON이나 Protobuf로 캐스팅해서 사용하면 됩니다.

아래는 메시지 핸들링에 대한 예제입니다.

// 받은 메시지를 처리할 콜백을 등록합니다.
session.ReceivedMessageCallback += onReceivedMessage;

// 기다리는 메시지에 timeout을 정해서 알림을 받고 싶다면 이 콜백을 등록합니다.
// 메시지가 지정한 시간내에 오지 않으면 이 콜백 함수가 호출됩니다.
session.ResponseTimeoutCallback += onResponseTimedOut;

// 메시지를 처리하는 방식은 정해진 형식이 없으며 사용하기 편한 형태로 쓰시면 됩니다.
// 여기에서는 받은 메시지를 처리하기 위해 각각의 메시지를 Key-Value 방식으로 저장하겠습니다.
message_handler_["echo"] = onEcho;
message_handler_["pbuf_echo"] = onEchoWithProtobuf;

플러그인 내부에서 이미 Deserialize 된 메시지를 ReceivedMessageCallback 함수로 전달하기 때문에 메시지를 다시 Deserialize할 필요는 없습니다. 받은 object 메시지를 캐스팅만 해서 사용하면 됩니다.

아래는 message_handler_ 에 등록한 메시지 콜백 함수에 대한 구현입니다.

// JSON 메시지에 대한 처리 함수입니다.
void onEcho (object message)
{
    // 예제에서는 MiniJSON을 사용합니다.
    // 다른 JSON 라이브러리를 사용하고 싶다면 아래 JSON Helper 관련 설명을 참고해주세요.
    FunDebug.Assert(message is Dictionary<string, object>);
    Dictionary<string, object> json = message as Dictionary<string, object>;
    FunDebug.Log("Received an echo message: {0}", json["message"]);
}

// Protobuf 메시지에 대한 처리 함수입니다.
void onEchoWithProtobuf (object message)
{
    // iFun Engine에서 사용하는 protobuf 메시지는 FunMessage를 기본으로 하고
    // 사용자 메시지는 FunMessage 안에 extend 형태로 추가해서 사용합니다.
    FunDebug.Assert(message is FunMessage);
    FunMessage msg = message as FunMessage;
    // extend 된 echo 메시지를 가져옵니다.
    PbufEchoMessage echo = FunapiMessage.GetMessage<PbufEchoMessage>(msg, MessageType.pbuf_echo);
    if (echo == null)
        return;

    FunDebug.Log("Received an echo message: {0}", echo.msg);
}

Response Timeout

서버로부터 기다리는 메시지에 대해 타임아웃을 정하고 싶다면 FunapiSession 클래스에 있는 SetResponseTimeout 함수를 사용해 원하는 메시지 타입에 타임아웃 시간을 지정하면 됩니다.

아래는 Response Timeout 관련 함수들입니다. Json의 경우 string 타입을 사용해야 하고 Protobuf는 MessageType, int, string 타입 중 SendMessage 호출시에 전달했던 메시지 타입과 동일한 타입으로 등록해야 합니다.

// MessageType 타입으로 Response Timeout 메시지를 등록합니다.
// MessageType은 int 타입으로 변환되어 처리됩니다.
// 이미 등록된 메시지 타입일 경우 false가 반환됩니다.
public bool SetResponseTimeout (MessageType msg_type, float waiting_time)

// int 타입으로 Response Timeout 메시지를 등록합니다.
public bool SetResponseTimeout (int msg_type, float waiting_time)

// string 타입으로 Response Timeout 메시지를 등록합니다.
public bool SetResponseTimeout (string msg_type, float waiting_time)

// 등록된 MessageType 타입의 Response Timeout 메시지를 삭제합니다.
// 해당 타입의 메시지를 찾을 수 없을 경우 false가 반환됩니다.
public bool RemoveResponseTimeout (MessageType msg_type)

// 등록된 int 타입의 Response Timeout 메시지를 삭제합니다.
public bool RemoveResponseTimeout (int msg_type)

// 등록된 string 타입의 Response Timeout 메시지를 삭제합니다.
public bool RemoveResponseTimeout (string msg_type)

Tip

PROTOBUF_ENUM_STRING_LEGACY define을 선언하게 되면 MessageType의 메시지는 string 타입으로 처리되며 string 타입의 콜백 함수가 호출됩니다.

아래 코드는 string 타입의 sc_login 메시지를 10초 동안 기다리는 예제입니다. sc_login 메시지를 10초 내에 받지 못할 경우 ResponseTimeoutCallback 함수가 호출됩니다.

// 기다리는 메시지 타입과 시간을 지정합니다.
// 등록되는 순간부터 시간이 흘러갑니다.
session.SetResponseTimeout("sc_login", 10f);

아래는 ResponseTimeoutCallback 함수의 원형입니다. SetResponseTimeout 함수로 전달했던 메시지 타입을 파라미터로 받습니다. 타임아웃 시간내에 서버로부터 해당 메시지를 받는다면 타임아웃 설정이 해제되고 콜백 함수도 호출되지 않습니다.

// int 타입과 MessageType 타입은 이 콜백 함수가 호출됩니다.
public delegate void ResponseTimeoutIntCallback (int msg_type);

// string 타입으로 등록했을 경우 이 콜백 함수가 호출됩니다.
public delegate void ResponseTimeoutCallback (string msg_type);

Response Timeout 콜백 함수가 전달되는 파라미터의 타입에 따라 두 가지로 나뉘어 있으며 이름이 약간 다릅니다. 사용시 콜백 함수의 이름에 주의해주세요.

Tip

한 번 등록한 Response Timeout 은 서버로부터 응답을 받거나 타임아웃 이벤트가 발생하기 전까지 유지되며 타임아웃 이벤트가 만료된 후에는 다시 설정해야 합니다.

Tip

인자로 전달한 메시지 타입에 대한 타임아웃이 이미 설정되어 있는 경우 나중에 호출되는 타임아웃 값은 무시됩니다.

JSON Helper 클래스

기본적으로 플러그인은 MiniJSON을 사용합니다. 다른 JSON 라이브러리를 사용하고 싶다면 JsonAccessor 인터페이스 클래스를 상속받아 JSON 라이브러리를 핸들링하는 클래스를 만들어 FunapiMessage의 JsonHelper를 변경하면 됩니다.

JsonAccessor 클래스에는 없지만 필요한 함수가 있다면 JsonAccessor 파생 클래스에 추가해서 사용하시면 됩니다.

새로 만들어진 JsonAccessor 클래스는 아래와 같은 방법으로 등록하면 됩니다. 이후에 보내는 메시지와 받는 메시지는 모두 새로 등록된 JSON 클래스를 통해 Serialize나 Deserialize를 하게 됩니다.

NewJsonAccessor json_helper = NewJsonAccessor();
FunapiMessage.JsonHelper = json_helper;

Note

플러그인 버전 164에서 JsonAccessor 클래스의 인터페이스에 변경이 있었습니다. (2016년 8월 업데이트) 164 이전 버전의 JsonAccessor 클래스를 상속받아 JsonHelper를 구현하셨다면 플러그인 업데이트시 추가된 인터페이스에 대해 추가 구현을 해주셔야 합니다.

아래는 JsonAccessor 의 인터페이스입니다.

public abstract object Clone (object json);

public abstract string Serialize (object json);
public abstract object Deserialize (string json_str);

public abstract bool HasField (object json, string field_name);
public abstract void RemoveField (object json, string field_name);

public abstract int GetArrayCount (object json);
public abstract object GetArrayObject (object json, int index);

public abstract object GetObject (object json, string field_name);
public abstract void SetObject (object json, string field_name, object value);

public abstract string GetStringField (object json, string field_name);
public abstract void SetStringField (object json, string field_name, string value);

public abstract Int64 GetIntegerField (object json, string field_name);
public abstract void SetIntegerField (object json, string field_name, Int64 value);

public abstract bool GetBooleanField (object json, string field_name);
public abstract void SetBooleanField (object json, string field_name, bool value);

메시지 암호화

서버와 메시지를 주고 받을 때 메시지를 암호화할 수 있습니다.

암호화 타입

암호화 타입의 종류는 아래와 같습니다.

public enum EncryptionType
{
  // 암호화를 사용하지만 특정 메시지를 암호화하지 않은 상태로 보내고 싶을 때
  // SendMessage의 파라미터로 이 값을 전달하면 됩니다.
  kDummyEncryption,

  // iFun Engine에서 제공하는 암호화 타입입니다.
  // 메시지를 주고 받을 때마다 키 값이 변경되어 안정적인 암호화 방식입니다.
  // Tcp 프로토콜에서만 사용 가능합니다.
  kIFunEngine1Encryption,

  // iFun Engine에서 제공하는 암호화 타입입니다.
  // 고정된 암호화 키를 사용합니다. 프로토콜에 상관없이 사용 가능합니다.
  kIFunEngine2Encryption,

  // ChaCha20 암호화 타입입니다.
  // Tcp 프로토콜에서만 사용 가능합니다.
  kChaCha20Encryption,

  // Aes 128 암호화 타입입니다.
  // Tcp 프로토콜에서만 사용 가능합니다.
  kAes128Encryption
}

kIFunEngine1Encryption 타입은 메시지를 주고 받을 때마다 암호화 키가 변경됩니다. 동일한 메시지를 보내도 암호화 키 값이 매번 달라지므로 고정된 암호화 키를 사용하는 kIFunEngine2Encryption 보다 상대적으로 보안에 안정적인 암호화 타입입니다.

ChaCha20Aes128 은 외부 라이브러리(sodium)를 사용하고 있습니다. 해당 라이브러리 파일은 Plugins 폴더에 있습니다. 이 두 암호화 타입을 사용하려면 공개 키 를 설정해야 합니다.

FunapiEncryptor.public_key = "0b8504a9c1108584f4f0a631ead8dd548c0101287b91736566e13ead3f...";

public_key 는 FunapiEncryptor의 public static string 타입이며 Connect 함수가 호출되기 전에 먼저 이 값이 세팅되어 있어야 합니다. 공개 암호화 키는 메시지 암호화 를 참고해주세요.

Tip

IFunEngine1, ChaCha20, Aes128 암호화 방식은 TCP 프로토콜에서만 사용이 가능합니다.

암호화 설정을 사용하는 상태에서 특정 메시지에 대해서 암호화를 적용하지 않고 그대로 보내고 싶다면 SendMessage 의 암호화 타입으로 kDummyEncryption 을 전달하면 됩니다. 이 기능을 사용하려면 서버 쪽 암호화 설정에 dummy 가 포함되어 있어야 합니다.

암호화 사용

TCP 의 경우 아무런 설정을 하지 않아도 서버에서 암호화 설정을 하면 클라이언트에서는 이를 따르도록 되어 있습니다. 처음 서버에 접속하면 암호화에 대한 동기화가 이루어지는데 서버에서 해당 프로토콜의 암호화 타입 목록을 전달하면 클라이언트는 서버에서 정한 암호화 방식을 사용하게 됩니다.

만약 암호화 타입이 여러 개라면 첫 번째 암호화 타입을 기본 암호화 타입으로 사용하게 됩니다. 메시지를 보낼 때 특정 암호화 타입을 지정하지 않는다면 이 때 지정된 기본 타입으로 암호화를 하게 됩니다. 특정 암호화 타입을 별도로 지정하고 싶다면 FunapiSession.Connect 함수를 호출할 때 TransportOption 의 Encryption 값을 정해주면 됩니다.

TcpTransportOption option = new TcpTransportOption();
option.Encryption = EncryptionType.kIFunEngine1Encryption;

만약 클라이언트의 암호화 타입이 서버에서 사용하지 않는 타입일 경우 오류가 발생하고 메시지를 주고 받을 수 없게 됩니다.

UDP 와 HTTP 의 경우에는 접속 후 서버와 암호화 타입을 동기화하는 과정이 없으므로 클라이언트에서 사용할 암호화 타입을 지정해주어야 합니다. 아이펀 엔진에서 제공하는 암호화 타입 중 UDP 와 HTTP 가 사용할 수 있는 암호화 타입은 IFunEngine2Encryption 밖에 없습니다. UDP 와 HTTP 에서 암호화를 사용하고 싶다면 이 값을 TransportOption의 Encryption 에 입력하면 됩니다.

TransportOption option = new TransportOption();
option.Encryption = EncryptionType.kIFunEngine2Encryption;

Note

GitHub Unity 에 배포된 클라이언트 플러그인은 IFunEngine1Encryption과 IFunEngine2Encryption 타입의 암호화 기능이 포함되지 않은 무료 버전입니다. ChaCha20과 Aes128 타입은 사용 가능합니다. 유료 고객 의 경우 슬랙이나 iFun Engine support 로 요청 메일을 보내주시면 암호화 타입이 모두 포함된 플러그인 소스를 보내드립니다.

메시지 압축

서버와 주고 받는 메시지를 압축할 수 있습니다.

현재 다음과 같은 압축 알고리즘을 지원합니다.

public enum FunCompressionType
{
  kNone,      // 기본 값. 압축을 사용하지 않습니다.
  kZstd,      // Zstdandard. 실시간 전송에 적합한 압축 알고리즘입니다.
  kDeflate    // Deflate. 지연 시간이 크고, 더 큰 메시지에 적당한 압축 알고리즘입니다.
}

FunCompressionType 은 TransportOption 에 있습니다. 기본 값은 kNone 입니다. 압축 관련 옵션은 서버와 클라이언트에서 같은 값을 사용해야 합니다. 같은 Session 이라 해도 프로토콜 별로 압축 타입을 다르게 사용할 수 있습니다.

압축 옵션

압축 옵션은 필요할 경우 지정할 수 있고 지정하지 않을 경우 기본 값을 사용합니다. 현재 사용할 수 있는 옵션은 압축 최소 크기 지정, 딕셔너리 등록 (Zstdandard) 이 있습니다.

압축 클래스의 Base Class 는 FunapiCompressor 이며 이 클래스를 상속받아 만들어진 Zstdandard 압축을 수행하는 FunapiZstdCompressor 클래스가 있고 Deflate 압축을 수행하는 FunapiDeflateCompressor 가 있습니다.

압축 옵션을 지정하기 위해서는 타입에 맞는 FunapiCompressor 객체를 직접 생성해서 세션으로 넘겨줘야 하는데 이를 위한 콜백 함수가 CreateCompressorCallback 입니다. 이 콜백 함수 안에서 타입에 맞는 FunapiCompressor 객체를 생성하고 옵션을 지정해서 반환하면 됩니다.

CreateCompressorCallback 함수의 원형은 아래와 같습니다.

public delegate FunapiCompressor (TransportProtocol protocol);

압축 최소 크기 지정

compression_threshold 는 압축 최소 크기를 지정하는 옵션입니다. 기본 값은 128 이며 메시지 길이가 128 이상일 경우 메시지를 압축하게 됩니다.

아래는 압축할 메시지의 최소 크기를 256으로 지정하는 예제입니다.

session.CreateCompressorCallback += delegate (TransportProtocol protocol)
{
    var compressor = new FunapiDeflateCompressor();
    compressor.compression_threshold = 256;
    return compressor;
};

Zstd 압축 사전 지정

Zstdandard 의 경우 자주 사용되는 메시지를 모아서 사전을 만들 수 있습니다. 이 데이터를 사전으로 등록해 두면 압축에 소요되는 시간을 단축시킬 수 있습니다. 사전은 서버와 클라이언트가 동일한 데이터를 사용해야 합니다.

아래는 base64 인코딩으로 되어 있는 파일을 사전 데이터로 설정하는 예제입니다. (사전 데이터를 파일로 읽어들일 경우 파일의 확장자는 bytes 여야 유니티에서 바르게 읽어들일 수 있습니다)

session.CreateCompressorCallback += delegate (TransportProtocol protocol)
{
    var compressor = new FunapiZstdCompressor();

    // 파일로부터 사전 데이터를 읽어옵니다.
    TextAsset text = Resources.Load<TextAsset>("zstd_dict");
    if (text != null)
    {
        byte[] zstd_dic = Convert.FromBase64String(text.text);
        compressor.Create(zstd_dic);
    }

    compressor_ = compressor;
    return compressor;
};

...

// 리소스를 해제하는 시점에 사전도 삭제해야 합니다.
if (compressor_ != null)
  compressor_.Destroy();

연결 종료 및 재연결

연결 종료

서버와의 연결을 종료하기 위해서는 FunapiSession 의 Stop 함수를 호출하면 됩니다.

session.Stop();

파라미터가 없는 Stop 함수는 연결된 모든 Transport 의 연결을 끊습니다. 특정 Transport 의 연결을 끊고 싶다면 파라미터로 프로토콜 타입을 전달하면 됩니다.

session.Stop(TransportProtocol.kTcp);

Stop 함수는 비동기 방식으로 Transport 의 연결을 끊습니다. Stop 함수가 호출됐을 때 연결을 바로 종료하지 않는 이유는 Transport 가 서버에 연결을 시도하는 중일 때 이를 기다리거나 보내지 못한 메시지가 있을 때 버퍼에 남아 있는 메시지를 보내고 연결을 끊기 위해서입니다.

Tip

연결 종료 전 미전송 메시지를 보내고 연결을 종료하는 기능은 TCP 프로토콜과 Websocket 프로토콜만 지원합니다.

TCP 연결의 경우 SendMessage는 호출되었으나 아직 전송하지 못하고 메시지가 남아 있는 경우, 남은 메시지를 모두 보낸 후 연결을 끊습니다. 이를 통해 Stop 을 호출하기 직전에 로그아웃 등의 메시지를 보내도 메시지의 전송을 보장합니다.

일부 기다림 없이 종료되는 경우도 있는데 서버에서 세션을 닫았을 경우, 서버간 이동을 위해 연결을 종료해야 할 경우, 앱이 종료되는 경우에는 대기 없이 바로 연결을 끊습니다.

Note

Connect 도중에 소켓이 닫힐 경우 IL2CPP 관련 버그로 인해 iOS 에서 크래쉬가 발생하기 때문에 서버에 연결을 시도하는 중간에 Stop이 호출됐을 경우에는 연결이 완료되기를 기다린 후 종료합니다.

FunapiSession 의 CloseRequest 함수를 호출하여 서버가 세션을 닫도록 요청할 수도 있습니다.

session.CloseRequest();

서버가 클라이언트의 세션 종료 요청을 받으면 세션을 종료하고 세션이 닫혔다는 메시지를 클라이언트로 전송합니다.

Stop 함수는 서버와의 연결을 종료하지만 서버의 세션에는 영향을 미치지 않기 때문에 서버에는 세션이 남아있는 상태로 유지됩니다. 서버와 연결을 종료하면서 서버의 세션도 닫고 싶다면 CloseRequest 함수를 사용해야 합니다.

연결 종료 알림

Transport Event

Transport 의 연결이 종료되면 Transport Event 로 kStopped 가 전달됩니다. 이 이벤트는 오류로 인해 연결이 끊겼거나 정상적으로 연결이 종료되었거나 상관없이 Transport의 연결이 끊기면 항상 발생합니다.

Transport 의 이벤트는 FunapiSession 의 TransportEventCallback 을 등록해서 받을 수 있습니다.

Session Event

Session 에 연결된 모든 Transport 의 연결이 끊기면 Session Event 로 kStopped 이벤트가 전달됩니다.

Session kClosed 이벤트는 Transport 의 연결이 끊겼다고 발생하는 것이 아니라 Session Timeout 이 되거나 서버에서 명시적으로 Session 을 닫았을 경우에만 발생합니다. 서버로부터 Session 이 닫혔다는 메시지를 받으면 모든 Transport 의 연결을 끊고 kClosed 이벤트를 전달하게 됩니다.

Session 이벤트는 FunapiSession 의 SessionEventCallback 을 등록해서 받을 수 있습니다.

재연결

서버와의 연결을 종료한 후 재연결을 할 때에는 FunapiSession.Connect 함수를 사용합니다. 연결할 Transport 의 프로토콜 타입만 파라미터로 전달하면 됩니다.

public void Connect (TransportProtocol protocol)

TransportOption 파라미터가 포함 된 Connect 함수를 사용해서 재연결을 할 수도 있지만 한 번 등록된 Transport 의 옵션은 변경할 수 없으며 새로운 TransportOption 을 전달할 경우에는 기존의 Transport 를 삭제하고 새로운 Transport 를 생성해서 연결하게 됩니다. 동일한 프로토콜에 같은 옵션으로 재연결시에는 프로토콜만 전달하는 Connect 함수를 호출하는 것이 좋습니다.

void onTransportEvent (TransportProtocol protocol, TransportEventType type)
{
    if (type == TransportEventType.kStopped)
    {
        // 연결이 끊겼을 경우
        if (hadDisconnect)
        {
            // 동일한 프로토콜에 같은 옵션으로 재연결 시도
            session.Connect(protocol);
        }
    }
}

void onTransportError (TransportProtocol protocol, TransportError error)
{
    if (error.type == TransportError.Type.kDisconnected)
    {
        hadDisconnect = true;
    }
    else
    {
        // 연결 끊김 이외의 오류가 발생했을 경우 연결을 종료함
        session.Stop();
    }
}

AutoReconnect 옵션

TCP 의 경우 연결이 끊겼을 때 자동으로 지속해서 재연결을 시도하게 할 수 있는데 TcpTransportOption 의 AutoReconnect 옵션 값을 true로 입력하면 됩니다. AutoReconnect 옵션이 true일 경우 연결이 종료된 것을 감지했을 때 항상 재연결을 시도합니다.

재연결을 시작할 때 Transport 의 이벤트인 kReconnecting 이벤트가 호출되며 연결에 실패할 경우 kStopped 이벤트가 호출됩니다.

void onTransportEvent (TransportProtocol protocol, TransportEventType type)
{
    if (type == TransportEventType.kReconnecting)
    {
        // 재연결 시작시 호출됩니다.
        // 연결이 끊겨서 직접 Connect를 호출할 때에는 발생하지 않습니다.
        // AutoReconnect 옵션으로 인해 재연결을 시도할 때에만 호출됩니다.
    }
}

재연결 시도에 타임아웃을 설정하고 싶다면 Transport 옵션의 ConnectionTimeout 값 (초단위) 을 지정하면 됩니다. 재연결 시도 도중 ConnectionTimeout 으로 지정한 시간을 초과하면 재연결 시도가 중단되고 kStopped 이벤트가 호출됩니다.

재연결 시도 도중 연결을 종료하고 싶다면 아무때나 원하는 시점에 Stop() 함수를 호출하면 됩니다.

Note

버전 247 이전의 플러그인 에서 AutoReconnect 옵션은 첫 연결시에만 연결 실패시 20초 동안 4-5회 가량 재연결을 시도하는 기능이었으나 효용성이 없어 해당 옵션이 true일 경우 연결이 종료된 것을 감지하면 항상 재연결을 시도 하는 것으로 변경되었습니다.

재연결을 시작할 때 Transport 이벤트인 kReconnecting 이벤트가 발생 하며 기존에 연결 종료시 발생하던 다양한 타입의 Transport 이벤트는 kStopped 하나로 통일 되었습니다.

Ping 사용하기

네트워크의 연결 상태를 확인하기 위해 터미널에서 사용하는 ping 이라는 명령어가 있습니다. 플러그인에도 이와 비슷하게 동작하는 Ping 기능이 있습니다. 서버와의 연결이 유지되고 있는지 확인하는 용도로 사용할 수 있습니다.

Ping 기능을 사용할 경우 주기적으로 서버와의 연결을 체크해서 일정 시간 이상 서버에서 응답이 없을 경우 연결이 끊기고 kStopped 이벤트가 발생합니다.

예측하기 힘든 모바일 네트워크 환경에서 Ping을 사용하여 지속적으로 연결 상태를 검사할 수 있습니다. 이를 통하여 연결이 끊겼는지를 좀 더 빠르게 판단하고 대응할 수 있습니다.

Tip

이 기능은 TCP 프로토콜과 Websocket 프로토콜에서 사용 가능합니다.

Ping 설정

Ping은 FunapiSession.Connect 함수를 호출하기 전에 Transport 옵션으로 설정할 수 있습니다. Ping 기능을 사용하려면 아래와 같이 SetPing 함수를 호출하거나 각각의 값을 입력해주면 됩니다.

TcpTransportOption option = new TcpTransportOption();
// Websocket 핑을 설정할 경우에는 WebsocketTransportOption 을 설정합니다.
// WebsocketTransportOption option = new WebsocketTransportOption();

// 이 함수만 호출하면 Ping 설정이 완료됩니다.
// 파라미터는 Ping 간격, Timeout, 로그 출력 여부 입니다.
option.SetPing(3, 20, true);

...

// 각각의 값을 따로 입력하고 싶다면 아래와 같이 하면 됩니다.

// 이 값을 true로 주면 Ping 기능을 사용할 수 있습니다.
// EnablePing의 기본 값은 false 입니다.
option.EnablePing = true;
// Ping 값을 로그로 보고 싶다면 이 값을 true로 주면 됩니다.
option.EnablePingLog = true;
// Ping 메시지를 보내는 간격을 지정합니다. (초단위)
option.PingIntervalSeconds = 3;
// 서버로부터 Ping 응답을 기다리는 최대 시간을 지정합니다. (초단위)
// 이 시간 이내에 응답이 없을 경우 연결을 끊고 Disconnect 처리를 하게 됩니다.
option.PingTimeoutSeconds = 20;

Ping 타임아웃 시간 내에 서버로부터 응답이 하나도 없을 경우 연결을 끊고 Disconnect 처리를 하게 됩니다. Disconnect 되면 FunapiSession의 TransportEventCallback으로 kStopped가 전달됩니다.

서버에서 Ping 기능을 활성화 하는 법은 세션 Ping(RTT) 에서 확인할 수 있습니다.

서버간 이동

서버의 요청에따라 접속 중인 서버의 연결을 끊고 새로운 서버로 접속해야 하는 경우가 있을 수 있습니다. 이 과정은 플러그인 내부에서 이루어지며 서버 쪽의 요청으로 이루어지는 동작이므로 클라이언트에서 서버에 이동을 요청할 수는 없습니다.

서버간 이동 상태 확인

서버간 이동이 시작되면 SessionEventCallback 을 통해서 서버간 이동에 관한 진행 상태가 전달됩니다. 아래는 콜백을 통해서 받게 되는 이벤트 타입의 종류입니다.

public enum SessionEventType
{
    ...
    kRedirectStarted,       // 서버간 이동을 시작합니다.
    kRedirectSucceeded,     // 서버간 이동을 완료했습니다.
    kRedirectFailed         // 서버간 이동에 실패했습니다.
};

서버간 이동이 시작되면 kRedirectStarted 이벤트를 전달한 후 현재 연결된 서버와의 접속을 종료하고 새로운 서버로 연결을 시작합니다. 서버간 이동이 완료될 때까지는 Redirect와 관련된 이벤트를 제외한 Session이나 Transport 관련 이벤트는 발생되지 않습니다. 연결 도중 오류가 발생해도 디버그 로그만 출력할 뿐 어떠한 이벤트도 발생되지 않습니다. 서버간 이동이 완료되면 kRedirectSucceededkRedirectFailed 이벤트가 전달됩니다.

서버 이동 후 사용할 Transport 의 옵션

서버를 이동할 때 접속 중인 서버와 이동하는 서버 간의 설정이 다를 수 있습니다. 이럴 경우 클라이언트에서 Session / Transport 의 옵션을 직접 설정해야 합니다. 옵션 값을 설정하지 않은 경우 이동 전 Session 과 Transport 의 옵션을 그대로 사용하게 됩니다. 옵션 값을 전달하는 콜백 함수의 원형은 아래와 같습니다.

public delegate SessionOption SessionOptionHandler (string flavor);
public event SessionOptionHandler SessionOptionCallback;

public delegate TransportOption TransportOptionHandler (string flavor, TransportProtocol protocol);
public event TransportOptionHandler TransportOptionCallback;

flavor 는 서버의 종류를 나타냅니다. 이 타입은 서버에서 정해주는 문자열 값입니다. 서버로부터 이동 메시지를 받으면 이동할 서버의 Transport를 새로 만들기 전에 이 콜백 함수들을 호출합니다. 해당 서버의 종류와 프로토콜 타입에 따라 아래와 같이 옵션을 구성해서 반환하면 됩니다.

Note

이동 전 서버에 동일한 프로토콜의 Transport 가 없을 경우에는 기본 TransportOption 을 사용합니다.

session_.SessionOptionCallback += onSessionOption;
session_.TransportOptionCallback += onTransportOption;
SessionOption onSessionOption (string flavor)
{
    if (flavor == "lobby")
    {
        SessionOption option = new SessionOption();
        option.sessionReliability = true;
        return option;
    }
    // 서버 이동전 옵션을 그대로 사용합니다.
    return null;
}


TransportOption onTransportOption (string flavor, TransportProtocol protocol)
{
    if (flavor == "lobby" && protocol == TransportProtocol.kTcp)
    {
        TcpTransportOption tcp_option = new TcpTransportOption();
        tcp_option.DisableNagle = true;
        return tcp_option;
    }
    // 서버 이동전 옵션을 그대로 사용합니다.
    return null;
}

이미 사용중이던 옵션과 동일한 옵션을 사용하거나 기본 옵션으로 연결해도 되는 경우에는 굳이 이 이벤트 콜백을 등록할 필요는 없습니다.

서버 이동 중 보내는 메시지의 전송 보장

서버 이동 중에 보내는 메시지는 기본적으로 무시됩니다. 이동 중 보내는 메시지의 전송을 보장받고 싶다면 SessionOption 의 useRedirectQueue 값을 true 로 설정하면 됩니다.

public class SessionOption
{
    public bool useRedirectQueue = false;
}

useRedirectQueue 값을 true 로 설정하면 서버 이동 중 보내는 메시지를 큐에 넣어두었다가 이동이 완료된 후 순차적으로 메시지를 전송합니다.

저장된 메시지를 확인하고 싶다면 FunapiSession 의 RedirectQueueCallback 에 콜백 함수를 등록하면 됩니다. 이 콜백을 등록해두면 서버 이동이 완료된 후 저장된 메시지를 전송하기 전에 이 콜백이 호출됩니다.

public delegate void RedirectQueueCallback (TransportProtocol protocol,
                                            List<string> current_tags,
                                            List<string> target_tags,
                                            Queue<UnsentMessage> queue);

저장된 메시지의 프로토콜 별로 콜백 함수가 호출됩니다. current_tags 는 이전에 접속중이던 서버의 태그 목록이며 target_tags 는 이동한 서버의 태그 목록입니다. 이 목록에 들어 있는 값으로 서버의 종류를 구분할 수 있습니다.

콜백이 호출되었을 때 큐잉된 메시지 중 보내고 싶지 않은 메시지를 선별할 수 있습니다. discard 값을 true 로 설정해두면 해당 메시지는 전송하지 않고 버려집니다.

foreach (UnsentMessage msg in queue)
{
    if (조건)
    {
        msg.discard = true;
    }
}

서버 이동 중에 전송을 보장할 메시지를 구분해야할 경우 이 콜백을 등록해주세요.

멀티캐스팅과 채팅

플러그인의 멀티캐스팅 기능을 사용하면 원하는 채널에 접속해서 채널의 모든 유저들과 메시지를 주고 받을 수 있습니다. FunapiSession 객체를 이용해 멀티캐스팅 역할을 하는 서버와 연결을 하고 메시지를 전송합니다. FunapiMulticastClient 객체를 만들면서 FunapiSession 객체를 전달하여 멀티캐스팅 기능을 사용할 수 있습니다.

FunapiMulticastClient 는 같은 공간(채널)에 있는 모든 유저에게 메시지를 보내고자 할 때 사용할 수 있습니다. _bounce 옵션을 true 로 주면 보내는 주체인 ‘나’도 그 메시지를 받을 수 있습니다.

채팅에 특화된 클래스인 FunapiChatClient 는 FunapiMulticastClient 를 상속받아 만들어진 파생 클래스입니다. FunapiChatClient 는 텍스트만 주고 받는 클래스로 플레이어간 채팅에 사용하기 적합합니다.

멀티캐스팅은 TCP 또는 Websocket 을 사용 하며, Transport 를 따로 설정하지 않을 경우 디폴트 값을 사용합니다. Encoding 은 연결된 FunapiSession에서 사용하는 방식을 따릅니다. JSON과 Protobuf 를 사용할 수 있습니다.

Important

멀티캐스팅은 하나의 Session마다 하나의 객체만 연결할 수 있습니다. 두 개 이상의 멀티캐스팅 객체를 연결하게 되면 Session에서 멀티캐스트 메시지를 받았을 때 어느 객체에게 메시지를 전달해야 하는지 알 수 없습니다. FunapiMulticastClient 클래스와 FunapiChatClient 클래스도 역시 동시에 두 개를 같은 Session으로 연결할 수 없습니다.

멀티캐스팅 인터페이스

FunapiMulticastClient 에는 다음과 같은 인터페이스가 존재합니다.

// FunapiMulticastClient 생성자입니다.
// FunapiSession와 주고 받을 메시지의 Encoding을 입력받습니다.
public FunapiMulticastClient (FunapiSession session,
                              FunEncoding encoding,
                              TransportProtocol protocol = TransportProtocol.kDefault);

// FunapiMulticastClient에서 사용하던 리소스를 반환합니다.
public void Clear ();

// 내 id(name)입니다.
// sender를 지정하면 채널 입/퇴장이나 메시지를 보낼 때 sender를 함께 전송합니다.
// 나를 포함한 채널의 모든 유저가 해당 메시지를 보낸 이를 확인할 수 있습니다.
public string sender { set; }

// 사용 중인 인코딩 타입입니다.
public FunEncoding encoding;

// 서버와 연결되어 있는지 확인하기 위한 property 입니다.
// 서버와 연결이 되어 있다면 true를 반환합니다.
public bool Connected;

// 채널 목록을 요청하는 함수입니다.
// 채널 목록과 함께 채널에 있는 유저 수도 함께 전달됩니다.
// 요청에 성공하면 ChannelListCallback에 등록한 함수가 호출됩니다.
public void RequestChannelList ();

// channel_id 가 입장한 채널의 id 인지 확인하는 property 입니다.
// 입장한 채널이면 true를 반환합니다.
public bool InChannel (string channel_id);

// 채널에 입장할 때 사용하는 함수입니다.
// channel_id 는 입장할 channel id 입니다. 존재하지 않는 채널이면 서버에서 생성합니다.
// handler는 메시지를 전달받으면 호출되는 함수입니다.
// 이미 channel_id에 해당하는 채널에 입장했거나 서버와 연결되어 있지 않으면 false 를 반환합니다.
public bool JoinChannel (string channel_id, ChannelMessage handler);

// 기능은 위의 JoinChannel 함수와 동일합니다. 토큰을 포함해서 보낼 때 사용합니다.
public bool JoinChannel (string channel_id, string token, ChannelMessage handler);

// 채널을 나갈 때 사용하는 함수입니다.
// channel_id는 나갈 channel id 입니다.
// 입장한 채널이 없거나 서버와 연결되어 있지 않으면 false 를 반환합니다.
public bool LeaveChannel (string channel_id);

// 입장해 있는 모든 채널에서 나갈 때 사용하는 함수입니다.
public void LeaveAllChannels ();

// 채널에 메시지를 전송할 때 사용하는 함수입니다.
// 이 함수는 protobuf로 메시지를 전송할 때 사용합니다.
public bool SendToChannel (FunMulticastMessage mcast_msg);

// 이 함수는 json으로 메시지를 전송할 때 사용합니다.
public bool SendToChannel (object json_msg);


// 채널 목록을 받으면 호출되는 Handler의 delegate 입니다.
public delegate void ChannelList(object channel_list);

// 채널에 입/퇴장을 알려주는 Handler의 delegate 입니다.
// 채널의 입/퇴장 알림을 받고 싶다면 ChannelNotify의 event 객체인
// JoinedCallback이나 LeftCallback을 등록하면 됩니다.
public delegate void ChannelNotify(string channel_id, string sender);

// 채널로부터 메시지를 받으면 호출되는 Handler의 delegate 입니다.
// 이 delegate와 동일한 타입으로 함수를 만들고 JoinChannel() 함수의 두 번째 인자로 전달합니다.
// channel_id 는 메시지를 전달받은 채널 id 입니다.
// body는 전달받은 메시지이며 Encoding에 따라서 JSON이나 Protobuf인 FunMulticastMessage가 전달됩니다.
public delegate void ChannelMessage(string channel_id, string sender, object body);

멀티캐스팅 예제

FunapiMulticastClient 로 멀티캐스팅을 처리하기 위한 사전 준비로서, 먼저 FunapiSession 을 만들고 Tcp로 Connect 합니다.

string kServerIp = "127.0.0.1";

// 우선 FunapiSession 을 만듭니다.
// 멀티캐스팅은 session reliability 와 상관없이 동작합니다.
// 예제의 단순함을 위해 여기서는 이 기능을 사용하지 않겠습니다.
session = FunapiSession.Create(kServerIp, false);

// Transport의 이벤트를 받기 위한 콜백함수를 등록합니다.
session.TransportEventCallback += onTransportEvent;

// 서버에 접속합니다.
// Transport의 옵션 설정은 생략하겠습니다.
session.Connect(TransportProtocol.kTcp, FunEncoding.kJson, 8012);

이제 메시지를 전송하기 위해 FunapiMulticastClient 를 만들겠습니다.

채널 입/퇴장을 알려주는 콜백 함수를 등록할 수 있습니다. 나를 포함해서 채널에 들어오고 나가는 모든 유저들의 입/퇴장을 알려줍니다.

JoinedCallback

유저가 채널에 입장하면 호출되는 함수

LeftCallback

유저가 채널에서 나가면 호출되는 함수

ErrorCallback

오류가 발생하면 에러코드를 전달해주는 함수

// FunapiMulticastClient를 만듭니다.
// 위에서 만든 session과 session과 같은 encoding 타입을 전달합니다.
FunapiMulticastClient multicast = new FunapiMulticastClient(session, FunEncoding.kJson);

// 내 아이디를 입력합니다.
multicast_.sender = "my name";

// 채널 목록을 받았을 때 호출되는 콜백입니다.
multicast_.ChannelListCallback += delegate (object channel_list) {
    onMulticastChannelList(encoding, channel_list);
};

// Player가 채널에 입장하면 호출되는 콜백입니다.
multicast_.JoinedCallback += delegate(string channel_id, string sender) {
    DebugUtils.Log("JoinedCallback called. player:{0}", sender);
};

// Player가 채널에서 퇴장하면 호출되는 콜백입니다.
multicast_.LeftCallback += delegate(string channel_id, string sender) {
    DebugUtils.Log("LeftCallback called. player:{0}", sender);
};

// 에러가 발생했을 때 알림을 받는 콜백입니다.
multicast_.ErrorCallback += delegate(FunMulticastMessage.ErrorCode code) {
    // error
};

에러 콜백으로 전달되는 ErrorCodeenum FunMulticastMessage.ErrorCode 이며 내용은 다음과 같습니다.

EC_ALREADY_JOINED

1

이미 입장한 채널입니다

EC_ALREADY_LEFT

2

이미 퇴장한 채널입니다

EC_FULL_MEMBER

3

채널에 입장 가능한 멤버 수가 가득 찼습니다

EC_CLOSED

4

채널이 없거나 닫혔습니다

EC_INVALID_TOKEN

5

잘못된 토큰입니다

EC_CANNOT_CREATE_CHANNEL

6

채널을 생성할 수 없습니다

EC_INVALID_TRANSPORT

7

잘못된 트랜스포트를 사용했습니다

  • EC_CANNOT_CREATE_CHANNEL 은 서버에서 클라이언트가 채널을 생성하지 못하도록 막아놓은 상태에서 클라이언트가 채널을 생성하려한 경우 발생합니다.

  • EC_INVALID_TRANSPORT 은 멀티캐스트 서버에서 지정한 프로토콜과 클라이언트에서 사용한 프로토콜이 일치하지 않은 경우 발생합니다.

채널에 입장하기 위해서 JoinChannel() 함수를 호출합니다. 해당 채널로부터 메시지가 전송되면 onMulticastChannelReceived 함수가 호출됩니다.

동시에 여러 개의 채널에 입장하려면 입장을 원하는 채널마다 JoinChannel() 함수를 호출하면 됩니다.

// 입장할 채널 id 를 입력합니다.
// 채널이 존재하지 않으면 JoinChannel() 함수를 호출할 때 채널이 생성됩니다.
string channel_id = "test_channel";

// 채널에 입장하기 위해 JoinChannel() 함수를 호출합니다.
// 해당 채널로부터 메시지가 전송되면 onMulticastChannelReceived 함수가 호출됩니다.
multicast.JoinChannel(channel_id, onMulticastChannelReceived);

// 채널에 메시지 전송
PbufHelloMessage hello_msg = new PbufHelloMessage();
hello_msg.message = "multicasting message";

PbufHelloMessage 는 서버의 {project}_messages.proto 파일에 존재합니다. funapi_initiator 로 처음 프로젝트를 생성하면 기본적으로 추가되어 있습니다. PbufHelloMessage는 예제를 위해 기본 생성된 것이며 MulticastServer 에서는 proto message 의 이름이나 field 를 제한하지 않습니다. 따라서 필요한 이름으로 proto message 를 생성하고 사용할 field 들을 자유롭게 정의하시면 아이펀 엔진에서는 이를 알아서 클라이언트에게 전송합니다. 이것은 JSON 도 마찬가지입니다.

아래는 서버의 {project}_messages.proto 파일에 있는 기본 내용입니다.

message PbufEchoMessage {
  required string msg = 1;
}

message PbufAnotherMessage {
  optional string msg = 1;
}

extend FunMessage {
  optional PbufEchoMessage pbuf_echo = 16;
  optional PbufAnotherMessage pbuf_another = 17;
}


import "funapi/service/multicast_message.proto";

message PbufHelloMessage
{
  optional string message = 1;
}
extend FunMulticastMessage
{
  optional PbufHelloMessage pbuf_hello = 16;
}

아래 onMulticastChannelReceived 의 protobuf 예제 코드에서 MulticastMessageType.pbuf_hello 라는 메세지는, 플러그인에 기본적으로 추가되어 있지는 않습니다. 서버를 빌드할 때 proto 파일도 함께 빌드할 수 있는데 이때 extend FunMulticastMessage 에 입력한 PBufHelloMessage 의 field 이름과 extend 번호를 자동으로 인식하여 enum 을 만들게 됩니다. 이에 대한 자세한 설명은 Protocol Buffers 메시지 용 DLL 빌드하기 를 참고해주세요.

private void onMulticastChannelReceived (string channel_id, string sender, object body)
{
    if (multicast_.encoding == FunEncoding.kJson)
    {
        // 채널이 맞는지 확인합니다.
        string channel = FunapiMessage.JsonHelper.GetStringField(body, "_channel");
        FunDebug.Assert(channel != null && channel == channel_id);

        // 메시지를 전송할 때 "message" 필드에 전송할 메시지를 담았습니다.
        // 따라서 수신한 메시지는 mcast_msg["message"] 에 있습니다.
        string message = FunapiMessage.JsonHelper.GetStringField(body, "_message");

        // 여기서는 간단하게 로그를 출력하겠습니다.
        DebugUtils.Log("Received a multicast message from the '{0}' channel.\nMessage: {1}",
                       channel_id, message);
    }
    else
    {
        // 수신한 메시지를 Protobuf인 FunMulticastMessage로 처리합니다.
        FunMulticastMessage mcast_msg = body as FunMulticastMessage;

        // MulticastMessageType 은 서버를 빌드할 때 proto 파일도 함께 빌드하면 자동 생성됩니다.
        // 이 예제에서는 PbufHelloMessage를 사용하므로 이것의 field 이름인 pbuf_hello를 사용합니다.
        PbufHelloMessage hello_msg =
            FunapiMessage.GetMulticastMessage<PbufHelloMessage>(mcast_msg, MulticastMessageType.pbuf_hello);
        if (hello_msg == null)
            return;

        // 메시지를 전송할 때 PbufHelloMessage의 message field에 메시지를 저장합니다.
        // 따라서 hello_msg.message 에 전달된 메시지가 담겨 있습니다.
        DebugUtils.Log("Received a multicast message from the '{0}' channel.\nMessage: {1}",
                       channel_id, hello_msg.message);
    }
}

메시지를 전송하려면 SendToChannel() 함수를 호출합니다.

// channel id를 입력합니다.
string channel_id = "test_channel";

if (multicast_.encoding == FunEncoding.kJson)
{
    // JSON으로 메시지를 전송합니다.
    Dictionary<string, object> mcast_msg = new Dictionary<string, object>();

    // _channel 필드에 channel id를 입력합니다.
    mcast_msg["_channel"] = channel_id;

    // 보낸 메시지를 나도 받고 싶다면 _bounce 필드값을 true로 설정합니다.
    mcast_msg["_bounce"] = true;

    // 메시지를 mcast_msg["message"] 에 담겠습니다.
    // 이는 자유롭게 변경하셔도 되며 필요하다면 다른 필드를 추가하셔도 됩니다.
    mcast_msg["message"] = "multicast test message";

    // 다른 필드 추가
    // mcast_msg["something"] = "...";

    // 멀티캐스팅 역할을 하는 서버에 메시지를 전송합니다.
    multicast.SendToChannel(mcast_msg);
}
else
{
    // protobuf로 메시지를 전송합니다.
    // PbufHelloMessage 파일은 서버의 {project}_messages.proto 파일에 정의 되어 있습니다.
    // 기본적으로 포함된 proto message인데 자유롭게 이름과 field 등을 정의하셔도 됩니다.
    PbufHelloMessage hello_msg = new PbufHelloMessage();

    // message field에 전송할 메시지를 담겠습니다.
    hello_msg.message = "multicast test message";

    // 멀티캐스팅 메시지를 전송하기 위해서 FunMulticastMessage 를 생성합니다.
    // 이 부분은 멀티캐스팅 역할을 하는 서버에서 필요한 proto message 이므로 반드시 필요한 부분입니다.
    FunMulticastMessage mcast_msg =
        FunapiMessage.CreateMulticastMessage(hello_msg, MulticastMessageType.pbuf_hello);

    // channel field 에 channel id 를 입력합니다.
    mcast_msg.channel = channel_id;

    // 보낸 메시지를 나도 받고 싶다면 bounce 필드 값을 true로 설정합니다.
    mcast_msg.bounce = true;

    // 멀티캐스팅 역할을 하는 서버에 메시지를 전송합니다.
    multicast_.SendToChannel(mcast_msg);
}

채널에서 나갈 때는 LeaveChannel() 함수를 호출합니다.

// 채널 나가기
string channel_id = "test_channel";
multicast.LeaveChannel(channel_id);

채팅 인터페이스

FunapiChatClient 에는 다음과 같은 인터페이스가 존재하며, FunapiMulticastClient 와의 차이점은 메시지를 object 객체로 받지 않고 string 만 받는다는 것입니다.

// FunapiChatClient 생성자입니다.
// FunapiSession과 주고 받을 메시지의 Encoding을 입력받습니다.
public FunapiChatClient (FunapiSession session,
                         FunEncoding encoding,
                         TransportProtocol protocol = TransportProtocol.kDefault);

// 채널에 입장할 때 사용하는 함수입니다.
// 이 함수를 호출하기 전에 sender에 유저 이름을 입력해야 합니다.
// chat_channel은 입장할 channel id 입니다. 존재하지 않는 채널이면 서버에서 생성합니다.
// handler는 메시지를 전달받으면 호출되는 함수입니다.
// 이미 channel_id 에 해당하는 채널에 입장했거나 서버와 연결되어 있지 않으면 false를 반환합니다.
public bool JoinChannel (string chat_channel, OnChatMessage handler);

// 기능은 위의 JoinChannel 함수와 동일합니다. 토큰을 포함해서 보낼 때 사용합니다.
public bool JoinChannel (string chat_channel, string token, OnChatMessage handler);

// 채널을 나갈 때 사용하는 함수입니다.
// 채널에서 나갈 때는 FunapiMulticastClient의 LeaveChannel 함수를 호출하면 됩니다.
public bool LeaveChannel (string channel_id);

// 채널에 메시지를 전송할 때 사용하는 함수입니다.
// chat_channel은 메시지를 전송할 channel 의 id 입니다.
// text는 전송할 메시지입니다.
public bool SendText (string chat_channel, string text);

// 그 외 FunapiMulticastClient에 public으로 선언되어 있는 함수들을 사용할 수 있습니다.
// 해당 함수들에 대한 설명은 FunapiMulticastClient 인터페이스 설명을 참고해주세요.


// 채널로부터 메시지를 받으면 호출되는 Handler의 delegate 입니다.
// 이 delegate와 동일한 타입으로 함수를 만들고 JoinChannel() 함수의 인자로 전달합니다.
public delegate void OnChatMessage(string channel_id, string sender, string text);

채팅 예제

FunapiChatClient 는 멀티캐스팅 기능을 활용하여 만든 클래스로써 사용법은 FunapiMulticastClient 와 매우 유사합니다. 따라서 이곳에서는 간단하게 FunapiChatClient 의 사용법을 익히도록 하겠습니다.

예제에서는 Encoding 을 Json 으로 하겠습니다.

// FunapiSession을 생성합니다.
FunapiSession session = FunapiSession.Create(kServerIp, false);
session.TransportEventCallback += onTransportEvent;

// 서버에 연결합니다.
session.Connect(TransportProtocol.kTcp, FunEncoding.kJson, 8012);

...

// FunapiChatClient 객체를 생성합니다.
FunapiChatClient chat = new FunapiChatClient(session, FunEncoding.kJson);

// 입장하는 플레이어의 이름을 입력합니다.
chat_.sender = "my name";

// 채널 입장
string channel_id = "test_channel";
chat.JoinChannel(channel_id, onMulticastChannelReceived);

메시지를 전달받으면 onMulticastChannelReceived() 함수가 호출됩니다.

// 메시지를 전달받으면 호출됩니다.
// FunapiMulticastClient 와는 달리 encoding에 따라 message를 처리하지 않아도 됩니다.
// 메시지 보낸 사람인 sender와 전송된 메시지인 text를 처리하시면 됩니다.
private void onMulticastChannelReceived (string chat_channel, string sender, string text)
{
    // 여기서는 간단하게 로그를 출력하겠습니다.
    DebugUtils.Log("Received a chat channel message.\nChannel={0}, sender={1}, text={2}",
                   chat_channel, sender, text);
}

SendText() 함수를 이용하여 채널에 메시지를 전송합니다.

// 채널에 메시지 전송
string channel_id = "test_channel";
string message = "Hello World";
chat.SendText(channel_id, message);

채널에서 나갈 때는 LeaveChannel() 함수를 사용합니다.

// 채널 나가기
string channel_id = "test_channel";
chat.LeaveChannel(channel_id);

공지사항 확인하기

엔진에서 제공하는 공지서버를 사용하고 있다면 클라이언트 플러그인을 통해 공지서버로부터 공지사항 목록을 받을 수 있고 언제든지 원하는 시점에 공지사항을 업데이트할 수 있습니다.

FunapiAnnouncement 클래스로 서버에 공지사항 목록을 요청할 수 있습니다.

public class FunapiAnnouncement
{
    // 공지사항 서버의 ip와 port 번호를 주소로 초기화 합니다. ex)"http://127.0.0.1:8080"
    public void Init (string url);
    // 공지사항 목록을 가져옵니다. 카테고리와 페이지를 지정할 수 있습니다.
    public void UpdateList (int max_count, int page = 0, string cateogry = "");
    // 받아온 공지사항의 개수를 출력합니다.
    public int ListCount;
    // 각 공지사항의 정보를 가져옵니다.
    public Dictionary<string, object> GetAnnouncement (int index);
    // 각 공지사항의 메인 이미지 경로를 가져옵니다.
    public string GetImagePath (int index);
    // 각 공지사항의 추가 이미지 경로들을 가져옵니다.
    public List<string> GetExtraImagePaths (int index);
    // 각 공지사항의 모든 이미지 경로들을 가져옵니다.
    public List<string> GetAllImagePaths (int index);
}

Note

한 페이지 당 공지사항 개수는 max_count 와 같습니다. 예를 들어 UpdateList(3, 0) 을 호출할 경우 최근 공지사항(첫 페이지)을 최대 3개 가져옵니다. UpdateList(3, 1) 을 호출할 경우 최근 공지사항 3개 이후의 공지사항(다음 페이지)부터 최대 3개를 가져옵니다.

Note

공지사항에 이미지가 포함되어 있을 경우 공지사항을 다 받은 후에 순차적으로 다운로드 하여 image_urlextra_images 에 들어 있는 파일을 FunapiUtils.GetLocalDataPath/ + "announce/" 경로 아래에 저장합니다.

Note

모든 다운로드가 끝나면 콜백 함수가 호출됩니다. 다운로드 할 이미지가 많을 경우 콜백 함수 호출까지 시간이 오래걸릴 수 있습니다.

아래는 FunapiAnnouncement 클래스를 사용해서 서버에 공지사항을 요청하고 콜백 함수에서 공지사항 목록을 파싱하는 샘플 코드입니다. 메시지는 타입은 JSON 입니다.

FunapiAnnouncement announcement = new FunapiAnnouncement();
void Init ()
{
    // 공지사항은 웹 서비스입니다.
    // 서버에서 공지사항 용도로 열어 둔 ip와 port를 주소로 요청합니다.
    announcement.Init("http://127.0.0.1:8080");
    announcement.ResultCallback += OnAnnouncementResult;
    // 이 함수를 호출하면 목록을 가져오거나 이미 있을경우 갱신해줍니다.
    // 가져올 개수와 페이지, 카테고리를 인자로 넘깁니다.
    announcement.UpdateList(5, 0, "category");
}
void OnAnnouncementResult (AnnounceResult result)
{
    // 결과가 Success가 아닐경우
    if (result != AnnounceResult.kSuccess)
        return;
    // 메시지는 JSON 타입이며 MiniJSON을 사용합니다.
    if (announcement.ListCount > 0)
    {
        for (int i = 0; i < announcement.ListCount; ++i)
        {
            // 각각의 목록에 대해 정보를 가져올 수 있습니다.
            Dictionary<string, object> list = announcement.GetAnnouncement(i);
            string buffer = "";
            foreach (var item in list)
                buffer += item.Key + ": " + item.Value + "\n";
            Debug.Log("announcement >> " + buffer);
        }
    }
}

공지사항에 들어가는 항목들은 정해져 있지 않습니다. 서버 관리자가 필요한 항목들을 정의해서 사용하면 됩니다. 단, 기존에 사용중인 필드들을 변경할 경우 플러그인 코드도 함께 수정해야 할 수도 있으므로 기존 필드를 변경해야 할 경우엔 iFun Engine support 로 문의해주시기 바랍니다.

서버 점검 메시지

서버가 점검 중이라면 클라이언트에서 보낸 메시지는 무시되고 서버는 클라이언트로부터 메시지를 받을 때마다 점검 안내 메시지를 보냅니다. 서버 연결 후 클라이언트가 아무런 메시지도 보내지 않는다면 서버도 점검 안내 메시지를 보내지 않습니다.

점검 안내 메시지 처리는 메시지 수신 콜백이 아닌, FunapiSession 의 MaintenanceCallback 함수로 전달됩니다. 서버 점검 메시지를 처리하고 싶다면 MaintenanceCallback 함수를 등록하고 콜백 함수에서 필요한 작업을 수행하면 됩니다.

// 서버로부터 점검 안내 메시지를 받았을 때 이 콜백함수가 호출됩니다.
session.MaintenanceCallback += onMaintenanceMessage;

서버 점검 메시지를 받으면 아래와 같은 방식으로 파싱해서 사용하면 됩니다. 메시지 내용은 JSON 타입으로 점검 시작 일시, 종료 일시, 메시지 로 이루어져 있습니다.

date_start

string

서버 점검 시작 일시

date_end

string

서버 점검 종료 일시

messages

string

메시지

if (encoding == FunEncoding.kJson)
{
  JsonAccessor json_helper = FunapiMessage.JsonHelper;
  FunDebug.Log("Maintenance message\nstart: {0}\nend: {1}\nmessage: {2}",
               json_helper.GetStringField(message, "date_start"),
               json_helper.GetStringField(message, "date_end"),
               json_helper.GetStringField(message, "messages"));
}
else if (encoding == FunEncoding.kProtobuf)
{
  FunMessage msg = message as FunMessage;
  MaintenanceMessage maintenance =
      FunapiMessage.GetMessage<MaintenanceMessage>(msg, MessageType.pbuf_maintenance);
  if (maintenance == null)
    return;

  FunDebug.Log("Maintenance message\nstart: {0}\nend: {1}\nmessage: {2}",
               maintenance.date_start, maintenance.date_end, maintenance.messages);
}

리소스 파일 다운로드

서버에서 클라이언트 리소스 서비스를 사용한다면 클라이언트에서 별도의 바이너리 업데이트 없이도 클라이언트의 리소스를 업데이트할 수 있습니다.

FunapiHttpDownloader 클래스

이 기능을 사용하려면 FunapiHttpDownloader 클래스를 사용해야 합니다. FunapiHttpDownloader 클래스가 갖고 있는 함수와 프로퍼티는 아래와 같습니다.

// 다운받을 리소스 파일들의 목록을 요청합니다.
// 파라미터는 서버 주소와 포트, HTTPS 여부, 저장될 폴더의 경로를 지정합니다.
// file_path는 특정 리소스 목록을 받을 때 사용합니다.
public void GetDownloadList (string hostname_or_ip, UInt16 port, bool https,
                             string target_path, string file_path = "");

// 다운받을 리소스 파일들의 목록을 요청합니다.
// 파라미터는 서버 URL과 파일이 저장될 폴더의 경로를 지정합니다.
public void GetDownloadList (string url, string target_path, string file_path = "");

// 다운로드를 시작합니다.
public void StartDownload ();

// 중지된 다운로드를 다시 시작합니다.
public void ContinueDownload ();

// 파일을 다운로드 중일 경우 다운로드를 중지합니다.
// 아직 다운로드 전 단계일 경우 실패 콜백이 호출되며 다운로드가 종료됩니다.
public void Stop ();

// 다운로드가 중지되었는지 여부
public bool IsPaused;

// 다운로드 중인지 여부
public bool IsDownloading;

// 다운로드된 파일이 저장되는 경로
public string DownloadPath;

// 다운로드를 시작하기 전에 몇 가지 확인할 수 있는 정보들이 있습니다.
// 아래의 Property들은 ReadyCallback 함수가 호출된 이후에 사용해야 합니다.
// 다운로드 받을 전체 파일 개수
public int TotalDownloadFileCount;

// 다운로드 받을 전체 파일의 크기 (bytes)
public UInt64 TotalDownloadFileSize;

// 다운로드가 완료된 파일 개수
public int CurrentDownloadFileCount;

// 다운로드가 완료된 파일의 크기 (bytes)
public UInt64 CurDownloadFileSize;

다운로드의 시작은 파일 목록을 받는 것부터 시작합니다. GetDownloadList 함수를 호출해서 리소스 목록을 받고 로컬 파일들을 확인합니다. GetDownloadList 함수를 호출하면 서버에 다운로드할 파일들의 목록을 요청합니다. 목록을 받으면 이전에 받은 파일과의 유효성을 검증하고 새로 받아야 할 파일이 무엇인지 확인합니다.

파일 확인 작업이 끝나면 ReadyCallback 함수가 호출됩니다. ReadyCallback 함수가 호출되면 새로 받아야할 총 파일의 개수와 데이터 크기를 확인할 수 있고 이 후 StartDownload 함수를 호출해야 다운로드가 시작됩니다.

새로 다운 받을 파일이 없다면 ReadyCallback 함수는 호출되지 않고 FinishedCallback 함수만 호출됩니다.

다운로드 도중의 진행상황을 확인하고 싶다면 UpdateCallback 이벤트에 콜백 함수를 등록하면 현재 다운로드 중인 파일의 정보를 주기적으로 알려줍니다. 해당 콜백으로 전달되는 정보는 파일 이름, 전체 파일 크기, 현재까지 받은 파일의 크기, 받은 파일의 Percentage 입니다.

다운로드가 완료되거나 실패했을 경우 FinishedCallback 함수가 호출됩니다.

VerifyCallback

파일마다 유효성 검사가 끝났을 때 호출됩니다.

ReadyCallback

리소스 목록을 받고 파일 유효성 검사를 마친 후 다운로드 준비가 완료됐을 때 호출됩니다.

UpdateCallback

다운로드 중인 파일의 진행상황을 알려줍니다. UI 업데이트용으로 사용하시면 좋습니다.

FinishedCallback

리소스 다운로드가 완료되면 호출됩니다.

콜백 함수 중 ReadyCallback 과 FinishedCallback 함수는 꼭 필요한 콜백이고 나머지는 필요에 따라 등록해서 사용하면 됩니다.

FunapiHttpDownloader 예제

아래는 FunapiHttpDownloader 를 생성하고 콜백 함수들을 등록하는 예제 코드입니다.

// FunapiHttpDownloader 객체를 생성합니다.
FunapiHttpDownloader downloader = new FunapiHttpDownloader();

// 필요한 콜백을 등록합니다.
// ReadyCallback과 FinishedCallback은 필수로 등록해야 합니다.

// 이전에 다운받은 파일들의 유효성을 검사할 때 검사가 완료된 파일의 정보를 알려줍니다.
downloader.VerifyCallback += delegate (string path)
{
    FunDebug.DebugLog2("Check file - {0}", path);
};

downloader.ReadyCallback += delegate (int total_count, UInt64 total_size)
{
    // 다운로드 준비가 완료되면 다운로드를 시작합니다.
    downloader.StartDownload();
};

downloader.UpdateCallback += delegate (string path, long bytes_received, long total_bytes, int percentage)
{
    // 다운로드 중인 각각의 파일에 대한 진행 상황을 확인할 수 있습니다.
    FunDebug.DebugLog2("Downloading - path:{0} / received:{1} / total:{2} / {3}%",
                       path, bytes_received, total_bytes, percentage);
};

downloader.FinishedCallback += delegate (DownloadResult code)
{
    if (code == DownloadResult.SUCCESS)
        // 다운로드가 완료되었습니다.
    else if (code == DownloadResult.PAUSED)
        // 다운로드가 중단되었습니다.
    else if (code == DownloadResult.FAILED)
        // 다운로드에 실패했습니다.
};

// 다운로드할 파일의 목록을 요청합니다.
// 파라미터로 서버 주소(포트를 포함한 URL)와 파일이 저장될 경로를 넘겨줍니다.
downloader.GetDownloadList("http://127.0.0.1:8020", "target/path");

리소스 폴더의 특정 폴더만 다운로드

Caution

현제 리눅스에서만 사용 가능합니다.

FunapiHttpDownloader의 기본 기능은 서버의 client_data 폴더에 있는 파일을 모두 다운로드 하는 것입니다. client_data 폴더 안의 특정 폴더만 다운로드 하고 싶을 경우 아래와 같은 방법으로 사용할 수 있습니다.

먼저 폴더 별로 리소스 목록을 JSON 파일로 만들어야 합니다. 아이펀 엔진이 설치되어 있고 리소스가 있는 머신에서 funapi_client_resource_generator 를 사용해서 목록을 생성합니다. 폴더가 여러 개일 경우 개수만큼 목록을 만들면 됩니다.

~$ /usr/bin/funapi_client_resource_generator --include_dirname /path/client_data/android android.json
~$ /usr/bin/funapi_client_resource_generator --include_dirname /path/client_data/windows windows.json

이 후 클라이언트에서 GetDownloadList 함수를 호출할 때 3번째 파라미터로 파일명을 전달하면 됩니다.

GetDownloadList(url, path/to/save, "windows.json");
GetDownloadList(url, path/to/save, "android.json");

Tip

클라이언트에서 다운받을 수 있는 json 파일은 한 개이며 2개 이상 중복해서 다운로드할 경우 기존 파일이 지워질 수도 있습니다.

에셋 번들 다운로드

유니티에서 사용하는 특정 에셋(모델, 텍스처, 프리팹, 오디오 클립, 씬 등)들을 묶어 하나의 파일로 만들고 실행시에 이 파일을 다운 받아 사용할 수 있습니다. 에셋 번들을 만들고 사용하는 방법은 유니티 공식 문서의 고급 개발 > 에셋 번들 항목에서 자세하게 설명하고 있습니다.

아래 링크는 Unity 2018.2 기준입니다. https://docs.unity3d.com/kr/2018.2/Manual/AssetBundlesIntro.html

에셋 번들 파일을 만든 후에는 다른 파일들처럼 다운받아 사용하시면 됩니다.

소셜네트워크 플러그인

Funapi Social plugin을 사용하면 Facebook과 Twitter의 계정으로 로그인 및 포스팅 등을 손쉽게 할 수 있습니다. 설치 패키지 파일은 플러그인의 루트 폴더에 additional-plugins/Packages 폴더 안에 있습니다. Scripts 폴더는 패키지를 Import 했을 때 더해지는 파일들을 풀어 놓은 폴더입니다. 패키지로 설치했다면 Scripts 폴더 안의 파일은 복사하지 않아도 됩니다.

인터페이스 클래스

Funapi Social Plugin 에는 공통으로 사용하는 인터페이스 클래스가 있습니다. SocialNetwork 라는 클래스인데 아래와 같은 인터페이스를 갖고 있습니다.

public abstract class SocialNetwork : MonoBehaviour
{
  // 친구 정보를 담고 있는 클래스입니다.
  public class UserInfo
  {
      public string id;         // 계정 아이디
      public string name;       // 계정 이름
      public string url;        // 프로필 사진 url
      public Texture2D picture; // 프로필 사진 이미지
  }

  // 메시지를 포스팅하는 함수입니다.
  public virtual void Post (string message)

  // 메시지와 이미지를 함께 포스팅하는 함수입니다.
  public virtual void PostWithImage (string message, byte[] image)

  // 메시지와 함수가 호출되는 시점의 화면을 함께 포스팅하는 함수입니다.
  public virtual void PostWithScreenshot (string message)

  // 친구 목록에서 해당하는 아이디의 친구 정보를 반환합니다.
  public UserInfo FindFriend (string id)

  // 친구 목록에서 해당하는 인덱스의 친구 정보를 반환합니다. (인덱스는 0부터 시작)
  public UserInfo FindFriend (int index)

  // 초대 목록에서 해당하는 아이디의 친구 정보를 반환합니다.
  public UserInfo FindFriendToInvite (string id)

  // 초대 목록에서 해당하는 인덱스의 친구 정보를 반환합니다. (인덱스는 0부터 시작)
  public UserInfo FindFriendToInvite (int index)

  // 내 계정의 아이디를 반환합니다.
  public string MyId;

  // 내 계정의 이름을 반환합니다.
  public string MyName;

  // 내 계정의 프로필 사진 Texture2D를 반환합니다.
  public Texture2D MyPicture;

  // 친구 목록의 전체 개수를 반환합니다.
  public int FriendListCount;

  // 초대 목록의 전체 개수를 반환합니다.
  public int InviteListCount;

  // 이벤트 함수 원형
  public delegate void EventHandler (SNResultCode code);
  public delegate void PictureDownloaded (UserInfo user);

  // 초기화, 로그인, 친구 목록 가져오기, 포스팅 등의 응답에 대한 이벤트 함수입니다.
  // 콜백 호출시 파라미터로 enum SNResultCode 값이 전달됩니다.
  // 요청에 실패할 경우 SNResultCode.kError가 전달됩니다.
  public event EventHandler OnEventCallback;

  // 프로필 사진을 받으면 호출되는 이벤트 함수입니다.
  // 해당 프로필 사진의 UserInfo가 호출과 함께 전달됩니다.
  public event PictureDownloaded OnPictureDownloaded;
}

이 SocialNetwork 클래스를 상속받아 만들어진 FacebookConnector 클래스와 TwitterConnector 클래스를 사용하면 됩니다.

Note

플러그인 연동을 시작하기 전에 해당 소셜 앱을 만들어야 합니다. 페이스북은 페이스북 앱, 트위터는 트위터 앱 을 만들어 주세요. 페이스북의 경우에는 저희가 제공하는 facebook-plugin 을 설치하기 전에 Facebook SDK for Unity 도 설치해야 합니다.

페이스북

플러그인 기능

Facebook SDK 의 기능을 Wrapping 해서 몇 가지 기능만을 제한적으로 제공하고 있습니다. 아래에 제공하는 기능 외에 사용하고 싶은 기능이 있다면 iFun Engine support 로 요청하실 수 있습니다.

  • 페이스북 계정으로 로그인

  • 내 정보와 프로필 사진 요청

  • 친구 목록 및 프로필 사진 요청

FacebookConnector 는 MonoBehaviour 객체이므로 Scene 에 미리 등록되어 있어야 합니다. 다음은 등록된 Object 에서 FacebookConnector 객체를 가져와서 초기화하는 코드입니다.

// FacebookConnector 콤포넌트를 가져옵니다.
FacebookConnector facebook = GameObject.Find("object name").GetComponent<FacebookConnector>();

// 초기화, 로그인 등의 요청에 대한 응답을 알려주는 이벤트 핸들러를 등록합니다.
facebook.OnEventCallback += OnEventHandler;

// 프로필 사진을 다운받으면 호출되는 이벤트 핸들러를 등록합니다.
facebook.OnPictureDownloaded += delegate(SocialNetwork.UserInfo user) {
    // do something with profile picture
};

// 초기화 함수를 호출합니다.
facebook.Init();

페이스북 로그인

로그인 함수는 두 종류가 있는데 읽기 권한만 필요할 때는 LogInWithRead 함수로 로그인하고 쓰기 권한까지 필요할 때는 LogInWithPublish 함수로 로그인해야 합니다.

로그인 함수의 파라미터로는 권한 목록을 전달해야 합니다. 이 권한은 페이스북 앱의 Status & Review 페이지에서 확인할 수 있는데 기본적으로 제공하는 기능이 몇 가지 있고 포스팅 등의 대부분의 기능은 권한 요청을 하고 페이스북에서 승인을 해준 뒤에 앱에서 사용이 가능합니다. 유니티 에디터에서 테스트할 때는 Graph API Explorer 를 통해 테스트용 Access Token 을 얻을 수 있습니다.

// 사용자 정보와 친구 목록을 읽어올 수 있도록 LogInWithRead 함수로 로그인합니다.
facebook.LogInWithRead(new List<string>() {
    "public_profile", "email", "user_friends"});

// 친구 목록을 요청합니다. 최대 100명의 친구 목록을 가져옵니다.
facebook.RequestFriendList(100);

친구 목록을 요청할 때 자동으로 사진도 함께 요청하는데 모든 친구의 사진이 당장 필요하지 않다면 선택적으로 사진을 다운받을 수도 있습니다.

// 이 옵션을 false로 주면 친구 목록을 요청할 때 프로필 사진을 같이 요청하지 않습니다.
// 이 값은 친구 목록을 요청하기 전에 설정해야 합니다. FacebookConnector 객체를 초기화 할 때 설정하는 것이 좋습니다.
facebook.auto_request_picture = false;

// 친구 목록에 있는 유저들의 프로필 사진을 요청합니다.
// 시작 인덱스 값과 최대 개수를 전달합니다.
facebook.RequestFriendPictures(0, 20);

사진을 요청하면 순차적 비동기 방식으로 사진을 다운로드합니다. 프로필 사진의 다운로드가 완료되면 PictureCallback 이벤트 함수가 호출됩니다. 사진의 다운로드가 완료됐을 때 알림을 받고 싶다면 FacebookConnector 객체를 초기화 할 때 PictureCallback 이벤트에 콜백 함수를 등록해야합니다.

트위터

플러그인 기능

트위터 플러그인에서 제공하는 기능은 아래와 같습니다. 아래 기능 외에 사용하고 싶은 기능이 있다면 iFun Engine support 로 요청하실 수 있습니다.

  • 트위터 계정으로 로그인

  • 내 정보와 프로필 사진 요청

  • 친구 목록 및 프로필 사진 요청

  • 내 트위터에 글 올리기

TwitterConnector 는 MonoBehaviour 객체이므로 Scene 에 미리 등록되어 있어야 합니다. 다음은 등록된 Object 에서 TwitterConnector 객체를 가져와서 초기화하는 코드입니다.

// TwitterConnector 콤포넌트를 가져옵니다.
TwitterConnector twitter = GameObject.Find("object name").GetComponent<TwitterConnector>();

// 초기화, 로그인, 포스팅 등의 요청에 대한 응답을 알려주는 이벤트 핸들러를 등록합니다.
twitter.OnEventCallback += OnEventHandler;

// 프로필 사진을 다운받으면 호출되는 이벤트 핸들러를 등록합니다.
twitter.OnPictureDownloaded += delegate(SocialNetwork.UserInfo user) {
    // do something with profile picture
};

// 초기화 함수를 호출합니다.
// 트위터 앱의 Consumer Key와 Consumer Secret 값을 전달합니다.
twitter.Init("4RnU4YDXmu8vmwKW5Lgpej3Xc",
             "voDDAoaTNXj8VjuWRDhfrnCpa9pnVgpRhBJuKwjJpkg62dtEhd");

트위터 초기화는 Consumer KeyConsumer Secret 값을 전달인자로 넣어서 Init() 함수를 호출해야 합니다. 이 값은 트위터 앱 의 [Keys and Access Tokens] 탭에서 확인할 수 있습니다.

트위터 로그인

트위터는 로그인하는 방법이 상대적으로 복잡하지만 한번 로그인하고 앱을 승인한 뒤에는 처음 로그인할 때 받은 Access Token 을 계속 사용하게 되므로 이와 같은 복잡한 과정은 한번만 거치면 됩니다.

// 로그인 함수를 호출해 앱을 승인하고 PIN Code를 얻습니다.
// 웹을 통해서 얻은 PIN Code를 입력받는 UI는 클라이언트에서 구현해야 합니다.
twitter.Login();

// Access Token을 요청합니다.
// 로그인 함수를 통해 얻은 PIN Code를 파라미터로 전달합니다.
twitter.RequestAccess("pin code");

RequestAccess() 함수를 호출해 얻은 Access Token 은 영구적으로 사용 가능한 인증 값으로 이 값만 있으면 언제든지 트위터에 포스팅을 할 수 있습니다. 한 번 로그인을 한 뒤에는 이 값을 플러그인에서 암호화해서 저장해두었다가 다음 Init() 함수 호출시에 확인하고 인증과정 없이 콜백함수를 통해 로그인이 완료되었음을 알려줍니다.

로그인이 되었다면 메시지를 포스팅 할 수 있습니다. 아래와 같이 메시지를 보내면 내 트위터에 글을 올려줍니다.

twitter.Post("Funapi plugin test message~");

C# Runtime Test Code

플러그인 폴더 내에 플러그인 봇 테스트를 위한 C# Runtime 테스트 코드가 있습니다. 유니티에서는 동시에 열 수 있는 소켓의 최대 수에 제한이 있기 때문에, 테스트를 위한 봇 프로그램을 직접 작성하는 것을 추천합니다. 좀 더 자세한 설명은 방법2: 유니티 플러그인을 이용 문서에서 확인할 수 있습니다.

Unity Plugin 로그 보기

플러그인 관련 로그는 유니티 에디터나 C# Runtime 실행시에만 표시됩니다. 로그를 남기는 것 자체가 게임의 퍼포먼스에 영향을 줄 수도 있기 때문에 에디터로 실행할 때 이외에는 로그를 남기지 않도록 하고 있습니다.

// 유니티 에디터 or C# Runtime일 경우 로그 표시
#if UNITY_EDITOR || NO_UNITY

  // 기본적인 로그 출력
  #define ENABLE_LOG

  // 디버깅에 도움이 되는 로그 출력. ENABLE_LOG 가 선언되어 있어야 사용 가능합니다.
  #define ENABLE_DEBUG

#endif

테스트를 위해 플러그인의 로그를 항상 보고 싶다면 Funapi/DebugUtils.cs 파일의 ENABLE_LOG 가 항상 정의되도록 수정하거나 빌드 세팅에서 define symbol 을 추가하면 됩니다. 빌드 세팅의 Other Settings 탭에서 Scripting Define Symbols 항목에 ENABLE_LOG 를 추가하면 됩니다. 추가로 ENABLE_DEBUG 심볼을 정의해서 디버깅에 도움이 되는 로그 출력 할 수 있습니다. ENABLE_DEBUGENABLE_LOG 가 선언되어 있어야 사용 가능합니다.

로그 저장하기

플러그인의 로그를 문자열이나 파일로 저장할 수 있습니다. 이 기능을 사용하려면 Funapi/DebugUtils.cs 파일에서 ENABLE_SAVE_LOG define 을 활성화하면 됩니다.

아래는 FunDebug 클래스에 있는 로그 저장과 관련된 함수와 프로퍼티들입니다.

// 이 옵션을 활성화하면 로그가 버퍼에 저장됩니다.
#define ENABLE_SAVE_LOG

public class FunDebug
{
  ...

  // 버퍼에 저장된 내용을 파일로 저장합니다. 버퍼의 크기는 1MBytes 입니다.
  // 버퍼가 꽉 차면 자동으로 파일로 저장되고 버퍼가 초기화됩니다.
  // 파일은 로컬 저장 경로의 'Data/Logs/' 폴더에 저장됩니다.
  public static void SaveLogs();

  // 저장된 로그의 크기를 반환합니다.
  public static int GetLogLength();

  // 저장된 전체 로그를 반환합니다.
  public static string GetLogString();

  // 버퍼를 초기화합니다.
  public static void ClearLogBuffer();

  // 저장된 모든 로그 파일을 삭제합니다.
  public static void RemoveAllLogFiles();
}

디버그 로그 파일 만들기

유니티 플러그인을 사용해서 개발 도중 플러그인에 문제가 있다고 판단될 경우 아래와 같은 방법으로 로그를 저장해서 파일과 함께 재현 스텝을 알려주시면 감사하겠습니다.

Funapi/DebugUtils.cs 파일에서 아래 define 들을 활성화합니다.

#define ENABLE_LOG
#define ENABLE_DEBUG
#define ENABLE_SAVE_LOG

게임 종료 시점이나 재현 상황이 발생한 직후 아래 함수를 호출해서 로그를 저장합니다.

// 로그를 파일로 저장
FunDebug.SaveLogs();

에디터에서 실행할 경우 이렇게 저장된 로그는 프로젝트 폴더의 Assets 이 있는 폴더와 같은 경로에 Data/Logs 폴더에서 확인할 수 있습니다. 모바일 기기에서 테스트할 경우에는 디바이스에서 앱의 저장 폴더에 접근하는 것을 제한하고 있으므로 파일에 접근이 어려울 수 있습니다.

버그 신고

Client plugin 에 대한 건의사항이나 버그 신고는 iFun Engine support 로 메일을 보내주십시오.