클라이언트 지원 Part 1: 플러그인

클라이언트 플러그인을 사용하면 네트워크 통신, 공지사항 확인, 리소스 다운로드 등의 기능을 편리하게 사용할 수 있습니다. 현재 Unity, Unreal Engine 4, Cocos2d-x, JavaScript (beta) 클라이언트 플러그인을 제공하고 있습니다. 클라이언트 플러그인은 GitHub (Unity, Unreal Engine 4, Cocos2d-x) 와 아이펀 엔진 웹사이트 (JavaScript (beta)) 에서 받으실 수 있습니다.

이 문서에서는 클라이언트 플러그인의 사용방법, 주의할 사항, 사용팁 등을 설명합니다. 아래의 설명은 Unity 플러그인을 기준으로 작성되었습니다.

개요

Session이란?

서버와 클라이언트의 네트워크 연결은 Session으로 관리합니다. Session은 직접 Close하거나, 설정한 시간 이상 네트워크 통신이 이루어지지 않아 Timeout이 발생하지 않는한 유지됩니다. 즉, 네트워크 연결이 끊기더라도 서버에서는 Session을 유지하며, 재연결시 동일한 Id의 Session이 있다면 연결이 끊어지지 않은 것처럼 계속 메시지를 주고 받을 수 있습니다.

하나의 Session은 TCP, UDP, HTTP 하나씩 최대 3개의 Transport 연결을 가질 수 있습니다. 같은 프로토콜 연결을 여러 개 만들고 싶다면 Session도 여러 개 만들어야 합니다.

Tip

Session의 내부 구현 방식과 관련된 자세한 내용은 (고급) 아이펀 엔진의 네트워크 스택 에서 설명하고 있습니다.

Session Id

서버에 접속하면 제일 먼저 서버로부터 Session Id 를 발급 받게 되고 이 후 서버와 주고 받는 모든 메시지에는 이 Session Id 가 포함됩니다.

JSON을 사용하는 경우, Session Id는 36 bytes 문자열입니다. Protobuf를 사용하는 경우에는 16 bytes Array, 혹은 JSON과 동일한 36 bytes 문자열로 사용할 수 있습니다. 서버와 주고 받는 모든 메세지에 Session Id가 포함되므로, 16 bytes Array를 사용하면 네트워크 사용량을 줄일 수 있습니다.

Protobuf에서 Session Id를 Byte Array로 사용하려면, 서버 MANIFEST 파일의 send_session_id_as_string 값을 false 로 설정하면 됩니다. 이 옵션의 기본 값은 true 입니다. JSON을 사용하는 경우, 이 옵션은 아무 동작도 하지 않고, 계속 36 bytes 문자열을 사용합니다.

Tip

개발 편의를 위하여 플러그인의 초기 설정은 연결할 때마다 새로운 Session Id 를 요청하는 것입니다. 클라이언트에서도 동일한 Session Id 를 계속 사용하고 싶다면 Session Reliability 옵션을 사용해 주세요.

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 Reliability 옵션

Session reliability 는 FunapiSession 클래스를 생성할 때 전달하는 옵션 중의 하나입니다.

이 옵션을 true로 설정하면 서버와 연결이 끊겨도 재연결시 동일한 세션으로 계속해서 통신할 수 있습니다. 서버에 재접속 후 거쳐야 하는 인증 과정을 생략할 수 있고, 서버에서도 유저의 데이터 값을 그대로 유지할 수 있어 모바일처럼 연결이 자주 끊길 수 있는 환경에서는 이 옵션을 사용하는 것을 추천합니다.

Important

Session reliability 기능은 TCP 프로토콜에서만 사용 할 수 있습니다.

Session reliability는 서버와 클라이언트가 항상 동일한 설정을 사용해야 합니다. 서버 MANIFEST 파일의 use_session_reliability 옵션을 true로 설정했다면, 클라이언트에서도 FunapiSession 클래스를 생성할 때, session_reliability 파라미터에 true를 넘겨줘야 합니다. 이 값이 서로 다른 경우, 서버에 아래와 같은 로그가 출력됩니다.

// 서버는 Session reliability 옵션이 꺼져 있고 클라이언트는 켜져 있을 경우...
message with seq number. but seq number validation disabled: sid=..., transport_protocol=Tcp

// 서버는 Session reliability 옵션이 켜져 있고 클라이언트는 꺼져 있을 경우...
message without seq number: session_id=...

서버에 연결하기

플러그인을 활용하여 구현하는 방법을 코드 예제와 함께 간략히 소개합니다. 전체 코드는 플러그인에 포함된 예제 프로젝트에서 확인하실 수 있습니다. (Assets/Sample/TestSession.cs 파일 참고)

Test.unity Scene을 실행하면 서버 주소, 포트, 프로토콜, 인코딩 타입을 입력할 수 있습니다. 이 값들을 info 객체에 저장해서 사용합니다.

FunapiSession 사용

서버와 통신하기 위해서 FunapiSession 클래스를 생성하고, 설정하는 방법입니다.

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과 Protobuf 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 클래스 를 참고해 주세요.

Protobuf 메시지 보내기

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 기반의 메시지를 사용하려면 추가적인 빌드 과정을 따라야 합니다. Unity에서 protobuf 사용하기 설명을 참고해주세요.

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 에 배포된 클라이언트 플러그인은 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 만 지원합니다.

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();
// 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;
typedef std::function<std::shared_ptr<FunapiSessionOption>(
    const fun::string& /*flavor*/)> SessionOptionHandler;
void SetSessionOptionCallback(const SessionOptionHandler &handler);

typedef std::function<std::shared_ptr<FunapiTransportOption>(
    const TransportProtocol,
    const fun::string& /*flavor*/ )> TransportOptionHandler;
void SetTransportOptionCallback(const TransportOptionHandler &handler);

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;
}
// "lobby" flavor 를 사용하는 서버에 접속 할때 세션 신뢰성 옵션을 활성화 합니다.
session_->SetSessionOptionCallback([](const fun::string &flavor) -> std::shared_ptr<fun::FunapiSessionOption>{
  if (flavor.compare("lobby") == 0) {
    auto session_option = fun::FunapiSessionOption::Create();
    session_option->SetSessionReliability(true);
    return session_option;
  }
  // 서버 이동전 옵션을 그대로 사용합니다.
  return nullptr;
});


// 새로 만들어질 Tcp 프로토콜의 옵션을 설정합니다.
session_->SetTransportOptionCallback([](const fun::TransportProtocol protocol,
                                        const fun::string &flavor) -> std::shared_ptr<fun::FunapiTransportOption> {
  if (flavor == "lobby" &&
      protocol == fun::TransportProtocol::kTcp) {
    auto option = fun::FunapiTcpTransportOption::Create();
    option->SetDisableNagle(true);
    return option;
  }
  // 서버 이동전 옵션을 그대로 사용합니다.
  return nullptr;
});

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

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

서버 이동 중에 보내는 메시지는 기본적으로 무시됩니다. 이동 중 보내는 메시지의 전송을 보장받고 싶다면 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);
};

// 에러가 발생했을 때 알림을 받는 콜백입니다.
// 에러 종류는 enum FunMulticastMessage.ErrorCode 타입을 참고해주세요.
multicast_.ErrorCallback += delegate(FunMulticastMessage.ErrorCode code) {
    // error
};

채널에 입장하기 위해서 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 을 만들게 됩니다. 이에 대한 자세한 설명은 Unity에서 protobuf 사용하기 를 참고해주세요.

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 에 들어 있는 파일을 플랫폼 별로 다음과 같은 위치에 저장합니다.

Unity : FunapiUtils.GetLocalDataPath/ + "announce/" 경로 아래에 저장합니다.
Unreal Engine 4, Cocos2d-x : FunapiAnnouncement::Create(url, path) 함수의 path 인자로 지정된 경로 아래에 저장합니다.

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);
        }
    }
}
std::shared_ptr<fun::FunapiAnnouncement> announcement_ = nullptr;

// 공지사항은 웹 서비스입니다.
// 서버에서 공지사항 용도로 열어 둔 ip와 port를 주소로 요청합니다.
// 두번째 인자로 다운로드될 파일들의 저장위치를 지정합니다.
announcement_ = fun::FunapiAnnouncement::Create("http://127.0.0.1:8080",
                                                TCHAR_TO_UTF8(*(FPaths::ProjectSavedDir())));

// 완료 결과를 받을 콜백을 등록합니다.
announcement_->AddCompletionCallback([this](const std::shared_ptr<fun::FunapiAnnouncement> &announcement,
                                            const fun::vector<std::shared_ptr<fun::FunapiAnnouncementInfo>>&info,
                                            const fun::FunapiAnnouncement::ResultCode result){
  if (result == fun::FunapiAnnouncement::ResultCode::kSucceed)
  {
    // 각각의 목록에 대해 정보를 가져올 수 있습니다.
    for (auto i : info)
    {
      UE_LOG(LogFunapiExample,
             Log,
             TEXT("date=%s message=%s subject=%s file_path=%s"),
             *FString(i->GetDate().c_str()),
             *FString(i->GetMessageText().c_str()),
             *FString(i->GetSubject().c_str()),
             *FString(i->GetFilePath().c_str()));
    }
  }
});

// 서버로 부터 받을 공지사항 개수를 등록합니다.
announcement_->RequestList(5);

// 목록을 가져오거나 이미 있을경우 갱신해줍니다.
fun::FunapiAnnouncement::UpdateAll();

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

서버 점검 메시지

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

점검 안내 메시지 처리는 메시지 수신 콜백이 아닌, 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 폴더 안의 파일은 복사하지 않아도 됩니다.

Tip

소셜네트워크 플러그인은 현재는 유니티 버전만 제공됩니다.

인터페이스 클래스

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 Q&A 게시판 을 통해 요청해 주시기 바랍니다.

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

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

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

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 Q&A 게시판 을 통해 요청해 주시기 바랍니다.

  • 트위터 계정으로 로그인

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

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

  • 내 트위터에 글 올리기

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~");

Unity 플러그인

Unity client plugin은 GitHub 를 통해 받으실 수 있습니다. 전체 암호화가 포함된 버전이 필요하시면 iFun Engine support 로 요청 하셔야합니다.

GitHub 에서 받은 소스 코드에서 Assets 폴더의 파일들 중 Funapi, Plugins 폴더를 플러그인을 사용할 프로젝트의 Assets 폴더로 복사하면 됩니다. HTTPS나 WSS를 사용한다면 Editor와 Resources 폴더도 함께 복사해 주세요. Sample 폴더에는 플러그인의 예제 파일들이 있습니다.

Unity에서 protobuf 사용하기

iOS / Android의 제약사항 때문에 protobuf-net 으로 생성한 C# 코드를 바로 사용할 수는 없습니다. 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 로 변경하면 빌드 디렉터리에 필요한 .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 에디터에서 확인하시면 됩니다. Windows 환경이라면 https://winscp.net/eng 에서 제공하는 프로그램을 사용하면 편리합니다.

Tip

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

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_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 LOG_LEVEL_4
#define ENABLE_SAVE_LOG

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

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

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

Unreal Engine 4 플러그인

Unreal Engine 4 plugin은 GitHub 를 통해 받으실 수 있습니다. 암호화가 포함된 버전이 필요하시면 iFun Engine support 로 요청 메일을 보내주십시오.

멀티캐스팅과 채팅

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

Note

멀티캐스팅은 TCP 또는 WebSocket 을 사용하며 JSONProtobuf 를 사용할 수 있습니다.

Caution

멀티캐스팅은 하나의 세션마다 하나의 객체만 연결할 수 있습니다.

멀티캐스팅 인터페이스

멀티캐스팅 기능은 FunapiMulticast 클래스가 제공하며 다음과 같은 인터페이스가 존재합니다.

객체 생성 인터페이스

// 별도의 FunapiSession 객체 없이 FunapiMulticast 객체를 생성합니다.
// 내부적으로 FunapiSession 객체를 생성하며 이 함수로 생성된 FunapiMulticast 객체는
// 멀티캐스트 서버와 연결을 해야 합니다.
// Connect() 함수로 멀티캐스트 서버에 연결을 시도할 수 있습니다.
// TCP, WebSocket 이외의 프로토콜이 인자로 사용된 경우 nullptr 을 반환합니다.
static std::shared_ptr<FunapiMulticast> Create(
    const char* sender,  // 내 ID(name)
    const char* hostname_or_ip,
    const uint16_t port,
    const FunEncoding encoding,
    const bool reliability,
    const TransportProtocol protocol);

// 별도의 FunapiSession 객체 없이 FunapiMulticast 객체를 생성합니다.
// 내부적으로 FunapiSession 객체를 생성하며 생성된 FunapiSession 에
// 추가적인 옵션을 설정 할 수 있습니다.
// 이 함수로 생성된 FunapiMulticast 객체는 멀티캐스트 서버에 연결을 해야 합니다.
// Connect() 함수로 멀티캐스트 서버에 연결을 시도할 수 있습니다.
// TCP, WebSocket 이외의 프로토콜이 인자로 사용된 경우 nullptr 을 반환합니다.
static std::shared_ptr<FunapiMulticast> Create(
    const char* sender,  // 내 ID(name)
    const char* hostname_or_ip,
    const uint16_t port,
    const FunEncoding encoding,
    const TransportProtocol protocl,
    const std::shared_ptr<FunapiTransportOption> &transport_opt,
    const std::shared_ptr<FunapiSessionOption> &session_opt);

// 멀티캐스트 서버에 연결된 FunapiSession 객체를 활용해 멀티캐스트 객체를 생성합니다.
// 다음과 같은 상황에서 nullptr 을 반환합니다.
// 1. FunapiSession 객체가 멀티캐스트 서버와 연결되어있지 않음.
// 2. FunapiSession 객체에 함수 인자와 일치하는 Protocol 이 없음.
// 3. TCP 또는 WebSocket 이외의 프로토콜이 인자로 사용된 경우.
static std::shared_ptr<FunapiMulticast> Create(
    const char* sender,  // 내 ID(name)
    const std::shared_ptr<FunapiSession> &session,  // FunapiMulticast 가 사용할 세션입니다.
    const TransportProtocol protocol);  // FunapiMulticast 가 사용할 프로토콜

연결 관련 인터페이스

// 멀티캐스트 서버에 연결합니다.
// 별도의 FunapiSession 객체 없이 FunapiMulticast 객체를 생성한 경우 사용됩니다.
void Connect();

// 멀티캐스트 서버에 연결이 되어있는지 확인하는 함수입니다.
// 연결이 되어있다면 true 를 반환합니다.
bool IsConnected() const;

// 멀티캐스트 서버와 연결을 종료합니다.
void Close();

콜백 등록 인터페이스

// 세션 이벤트가 발생하면 호출되는 콜백 함수를 추가합니다.
// 서버와 연결된 FunapiSession 객체를 활용해 멀티캐스트 객체를 생성한 경우
// FunapiSession 객체의 AddSessionEventCallback() 함수를 사용해주세요.
void AddSessionEventCallback(const FunapiMulticast::SessionEventHandler &handler);

// 트랜스포트 이벤트가 발생하면 호출되는 콜백 함수를 추가합니다.
// 서버와 연결된 FunapiSession 객체를 활용해 멀티캐스트 객체를 생성한 경우
// FunapiSession 객체의 AddTransportEventCallback() 함수를 사용해주세요.
void AddTransportEventCallback(const FunapiMulticast::TransportEventHandler &handler);

// 채널 입장을 알려주는 콜백 함수를 추가합니다.
void AddJoinedCallback(const ChannelNotify &handler);

// 채널 입장을 알려주는 콜백 함수를 추가합니다.
void AddJoinedCallback(const ChannelNotify &handler);

// 채널 퇴장을 알려주는 콜백 함수를 추가합니다.
void AddLeftCallback(const ChannelNotify &handler);

// 멀티캐스트 서버로부터 에러 메세지를 받으면 호출되는 콜백 함수를 추가합니다.
void AddErrorCallback(const ErrorNotify &handler);

// 멀티캐스트 서버로부터 채널 목록을 받으면 호출되는 콜백 함수를 추가합니다.
void AddChannelListCallback(const ChannelListNotify &handler);

채널 관련 인터페이스

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

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

// 채널에 입장할 때 사용하는 함수입니다.
// channel_id 는 입장할 채널 이름입니다. 존재하지 않는 채널이면 서버에서 생성합니다.
// handler 는 메시지를 전달받으면 호출되는 콜백 함수입니다.
// token 은 채널 입장에 사용되는 인증 token 이며 채널의 token 과 인자로 사용된 token 이
// 일치하지 않다면 채널 입장에 실패합니다.
// token 인증을 사용하지 않는다면 빈 문자열 혹은 디폴트 인자를 사용해주세요.
// 다음과 같은 상황에서 false 를 반환합니다.
// 이미 channel_id 에 해당하는 채널에 입장, 서버와 연결되어 있지 않음, JSON 인코딩을 사용하지 않음.
bool JoinChannel(const fun::string &channel_id,
                 const JsonChannelMessageHandler &handler,
                 const fun::string &token);

// 채널에 입장할 때 사용하는 함수입니다.
// channel_id 는 입장할 채널 이름입니다. 존재하지 않는 채널이면 서버에서 생성합니다.
// handler 는 메시지를 전달받으면 호출되는 콜백 함수입니다.
// token 은 채널 입장에 사용되는 인증 token 이며 체날의 token 과 인자로 사용된 token 이
// 일치하지 않다면 채널 입장에 실패합니다.
// token 인증을 사용하지 않는다면 빈 문자열 혹은 디폴트 인자를 사용해주세요.
// 다음과 같은 상황에서 false 를 반환합니다.
// 이미 channel_id 에 해당하는 채널에 입장, 서버와 연결되어 있지 않음, Protobuf 인코딩을 사용하지 않음.
bool JoinChannel(const fun::string &channel_id,
                 const ProtobufChannelMessageHandler &handler,
                 const fun::string &token);

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

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

메세지 전송 인터페이스

// 채널에 JSON 메시지를 전송할 때 사용하는 함수입니다.
// channel_id 는 메세지를 전송할 채널 이름입니다.
// json_string 은 전송할 JSON 메세지 입니다.
// bounce 는 내가 전송한 메세지를 받는 옵션으로 디폴트는 true 입니다.
// 만약 내가 전송한 메세지를 받지 않으려면 false 로 비활성화 해주세요.
bool SendToChannel(const fun::string &channel_id,
                   fun::string &json_string,
                   const bool bounce);  // 내가 전송한 메세지를 받는 옵션.

// 채널에 메시지를 전송할 때 사용하는 함수입니다.
// 이 함수는 Protobuf 메시지를 전송할 때 사용합니다.
// channel_id 는 메세지를 전송할 채널 이름입니다.
// msg 은 전송할 Protobuf 메세지 입니다.
// bounce 는 내가 전송한 메세지를 받는 옵션으로 디폴트는 true 입니다.
// 만약 내가 전송한 메세지를 받지 않으려면 false 로 비활성화 해주세요.
bool SendToChannel(const fun::string &channel_id,
                   FunMessage &msg,
                   const bool bounce);  // 내가 전송한 메세지를 받는 옵션.

멀티캐스팅 예제

FunapiMulticast 객체 생성

가장 먼저 멀티캐스팅 기능을 사용하기 위해 FunapiMulticast 객체를 생성하겠습니다.

FunapiMulticast 객체를 생성하는 인터페이스는 총 3 개가 있으며 여기서는 대표적인 2 개의 생성 방법을 설명하겠습니다.

1. FunapiSession 객체의 생성을 FunapiMulticast 에 맡기는 방법

FunapiMulticast 의 내부에서 FunapiSession 을 생성해 사용합니다. 내부에서 생성된 FunapiSessionFunapiMulticast 객체가 소멸할 때 같이 소멸됩니다.

// FunapiMulticast 객체가 FunapiSession 객체를 내부적으로 생성합니다.
multicast_ = fun::FunapiMulticast::Create("my name",  /* 내 ID(name) */
                                          "127.0.0.1",  /* hostname or ip */
                                          port,
                                          encoding,
                                          false,  /* session reliability */
                                          TransportProtocol::kTcp);

if (multicast_ == nullptr)
{
  // multicast 가 지원하는 프로토콜은 TCP, WebSocket 입니다
  // 프로토콜을 확인해 주세요.
}

// AddSessionEventCallback 메써드를 호출해 세션 이벤트를 확인하는 콜백 함수를 등록합니다.
multicast_->AddSessionEventCallback(
    [](const std::shared_ptr<fun::FunapiMulticast>& funapi_multicast,
       const fun::SessionEventType event_type,
       const fun::string &session_id,
       const std::shared_ptr<fun::FunapiError> &error)
    {
      if (event_type == fun::SessionEventType::kOpened)
      {
        // 연결에 성공했습니다. 채널에 접속을 시도합니다.
        // JoinChannel() 함수는 "채널 입장" 목차에서 설명하겠습니다.
        funapi_multicast->JoinChannel("test channel id",
                                      on_multicast_channel_received)
      }
      else if (event_type == fun::SessionEventType::kClosed)
      {
        // 명시적으로 서버가 연결을 끊었습니다.
        // 만약 재연결이 필요한 경우 아래 메써드를 통해 재연결을 시도합니다.
        // funapi_multicast->Connect();
      }
      ...
    }
);

// AddTransportEventCallback 메써드를 호출해 트랜스포트 이벤트를 확인하는 콜백 함수를 등록합니다.
multicast_->AddTransportEventCallback(
    [](const std::shared_ptr<FunapiMulticast> &funapi_multicast,
       const fun::TransportEventType type,
       const std::shared_ptr<fun::FunapiError> &error)
    {
      if (type == fun::TransportEventType::kConnectionFailed)
      {
         UE_LOG(LogTemp, Log, TEXT("Error code : %d, Error message : %s"),
                error->GetErrorCode(), error->GetErrorString().c_str());

         // 연결을 다시 시도합니다.
         funapi_multicast->Connect();
      }
      ...
    }
);

// 멀티캐스트 서버와 연결을 시도합니다.
multicast_->Connect("my name",  /* 내 ID(name) */
                    "127.0.0.1"  /* hostname or ip */);

// FunapiMulticast 객체를 소멸시키는 방법으로 내부에서 생성된 FunapiSession 객체도 소멸됩니다.
// multicast_ = nullptr;

2. 직접 생성한 FunapiSession 객체로 FunapiMulticast 객체를 생성하는 방법.

FunapiMuticast 에 사용하는 FunapiSession 객체를 직접 관리하기 위해 사용됩니다. 이미 생성되어 있던 FunapiSession 객체를 등록해서 사용할 수 있습니다.

session_ = FunapiSession::Create("127.0.0.1",  /* hostname or ip */
                                 false  /* session reliability */);

// AddSessionEventCallback() 메써드를 호출해 세션 이벤트를 확인하는 콜백 함수를 등록합니다.
// 이후 정상적으로 세션이 연결되면 FunapiMulticast 객체를 생성합니다.
session_->AddSessionEventCallback(
    [](const std::shared_ptr<fun::FunapiSession> &session,
       const fun::TransportProtocol transport_protocol,
       const fun::SessionEventType type,
       const fun::string &session_id,
       const std::shared_ptr<FunapiError> &error)
    {
      if (type == fun::SessionEventType::kOpened)
      {
        // 연결된 세션을 이용해 FunapiMulticast 객체를 생성합니다.
        // 이미 연결된 FunapiSession 객체를 활용하기 때문에 "FunapiSession 객체의 생성을 FunapiMulticast 에 맡기는 방법"
        // 예제와 다르게 별도의 connect 과정이 필요하지 않습니다.
        multicast_ = FunapiMulticast::Create("my name",  /* 내 ID(name) */,
                                             session_,
                                             fun::TransportProtocol::kTcp);

        if (multicast_ == nullptr)
        {
          // FunapiMulticast 객체 생성에 실패했습니다.
          // 실패하는 경우는 아래와 같습니다.
          // FunapiSession 객체가 서버에 연결되어있지 않다.
          // FunapiSession 객체에 함수 인자와 일치하는 Protocol 이 없다.
          // Tcp, WebSocket 이외의 프로토콜을 사용.
        }

        // 필요한 콜백 함수 추가 후 채널에 접속을 시도합니다.
        // 콜백 함수는 "콜백 함수 추가" 목차에서 설명하겠습니다.

        // 채널에 접속을 시도합니다.
        // JoinChannel() 함수는 "채널 입장" 목차에서 설명하겠습니다.
        auto on_multicast_channel_received =
            [](const std::shared_ptr<fun::FunapiMulticast> &funapi_multicast,
                const fun::string &channel_id,
                const fun::string &sender_string,  // User ID(name)
                const FunMessage& message)
            {
              // 메세지를 받았습니다.
              // 받은 메세지를 사용하는 방법은 "채널 입장" 목차에서 설명합니다.
            };

        multicast_->JoinChannel("test channel id",
                                on_multicast_channel_received)
      }
      if(event_type == fun::SessionEventType::kClosed)
      {
        // 명시적으로 서버가 연결을 끊었습니다.
        // 만약 재연결이 필요한 경우 아래 메써드를 통해 재연결을 시도합니다.
        // session->Connect(fun::TransportProtocol::kTcp, port, fun::FunEncoding::kJson);
      }
      ...
    }
);

// AddTransportEventCallback() 메써드를 호출해 트랜스포트 이벤트를 확인하는 콜백 함수를 등록합니다.
session_->AddTransportEventCallback(
    [](const std::shared_ptr<fun::FunapiSession> &session,
       const fun::TransportProtocol transport_protocol,
       const fun::TransportEventType type,
       const std::shared_ptr<fun::FunapiError> &error)
    {
      if (type == fun::TransportEventType::kConnectionFailed)
      {
         UE_LOG(LogTemp, Log, TEXT("Error code : %d, Error message : %s"),
                error->GetErrorCode(), error->GetErrorString().c_str());

        // 연결을 다시 시도합니다.
        // session_->Connect(...)
      }
      ...
    }
);

// 멀티캐스트 서버에 연결합니다.
session_->Connect(fun::TransportProtocol::kTcp, port, fun::FunEncoding::kJson);

콜백 함수 추가

FunapiMulticast 에는 부가적인 콜백 함수들이 있으며 이를 통해 채널 입/퇴장, 채널의 목록, 발생한 에러에 대한 정보를 받을 수 있습니다. 이 예제에서는 위에 나열된 콜백을 모두 추가하겠습니다.

1. 입장 콜백 함수 추가

유저가 채널에 입장하면 호출되는 콜백 함수로 나를 포함해서 채널에 입장하는 모든 유저를 알 수 있습니다.

// 유저가 채널에 입장하면 호출되는 콜백 함수를 추가합니다.
multicast_->AddJoinedCallback(
    [](const std::shared_ptr<fun::FunapiMulticast>& funapi_multicast,
       const fun::string &channel_id, const fun::string &multicast_sender /* User ID(name) */)
    {
      // 채널에 입장한 유저 를 알 수 있습니다.
      UE_LOG(LogTemp, Log, TEXT("Channel ID : %s, Joined user ID : %s"),
             *FString(channel_id.c_str()), *FString(multicast_sender.c_str()));
    }
);

2. 퇴장 콜백 함수 추가

유저가 채널에 퇴장하면 호출되는 콜백 함수로 나를 포함해서 채널에서 나가는 모든 유저를 알 수 있습니다.

// 유저가 채널에서 퇴장하면 호출되는 콜백 함수를 추가합니다.
multicast_->AddLeftCallback(
    [](const std::shared_ptr<fun::FunapiMulticast>& funapi_multicast,
       const fun::string &channel_id,
       const fun::string &multicast_sender /* User ID(name) */)
    {
      // 채널에서 퇴장한 유저 를 알 수 있습니다.
      UE_LOG(LogTemp, Log, TEXT("Channel ID : %s, Left user ID : %s"),
             *FString(channel_id.c_str()), *FString(multicast_sender.c_str()));
    }
);

3. 채널 목록 콜백 함수 추가

채널 목록 요청 함수 RequestChannelList() 에 대한 응답 콜백 함수로 서버에 있는 채널들과 채널에 접속한 유저의 수를 알 수 있습니다.

// 서버로 부터 채널 목록을 받았을 때 호출되는 콜백 함수를 추가합니다.
// 채널 목록을 요청하려면 RequestChannelList() 함수를 호출해 주세요.
multicast_->AddChannelListCallback(
    [](const std::shared_ptr<FunapiMulticast>& funapi_multicast,
       const fun::map<fun::string, int> &channels)
    {
      for (auto channel : channels)
      {
        // 채널 이름과 해당 채널에 접속한 유저 수를 알 수 있습니다.
        UE_LOG(LogTemp, Log, TEXT("Channel ID : %s, Number of users on channel : %d"),
               *FString(channel.first.c_str()), channel.second);
      }

      // 만약 접속을 원하는 채널이 있거나 새 채널을 만드시려면 JoinChannel() 메써드를
      // 사용해 주세요.
      // JoinChannel() 함수는 "채널 입장" 목차에서 설명하겠습니다.
      funapi_multicast->JoinChannel("test channel id",
                                    on_multicast_channel_received)
    }
);

4. 에러 콜백 함수 추가

// 에러가 발생했을 때 알림을 받는 콜백 함수를 추가합니다.
// 에러 종류는 multicast_message.pb.h 파일의 FunMulticastMessage_ErrorCode 를 참고해주세요.
multicast_.AddErrorCallback(
    [](const std::shared_ptr<fun::FunapiMulticast>& funapi_multicast,
       int error_code)
    {
      // enum FunMulticastMessage_ErrorCode {
      //   FunMulticastMessage_ErrorCode_EC_ALREADY_JOINED = 1,
      //   FunMulticastMessage_ErrorCode_EC_ALREADY_LEFT = 2,
      //   FunMulticastMessage_ErrorCode_EC_FULL_MEMBER = 3,
      //   FunMulticastMessage_ErrorCode_EC_CLOSED = 4,
      //   FunMulticastMessage_ErrorCode_EC_INVALID_TOKEN = 5,
      //   FunMulticastMessage_ErrorCode_EC_CANNOT_CREATE_CHANNEL = 6
      // }

      if (error_code == 1)
      {
        // 이미 채널에 입장 했습니다.
      }
      else if (error_code == 2)
      {
        // 이미 채널에서 퇴장 했습니다.
        // 다시 채널에 접속하려면 JoinChannel() 메써드를 사용해주세요.
        // JoinChannel() 함수는 "채널 입장" 목차에서 설명하겠습니다.
        funapi_multicast->JoinChannel("test channel id",
                                      on_multicast_channel_received)
      }
    }
);

채널 입장

채널에 입장하기 위해서 JoinChannel() 함수를 호출합니다. 만약 존재하지 않는 채널이면 서버에서 생성합니다. 해당 채널로부터 메시지가 전송되면 JoinChannel() 에 인자로 사용된 콜백 함수가 호출됩니다.

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

이 예제에서는 채팅에 특화된 멀티캐스트 서버를 구축했고 FunChatMessage 를 사용해 메세지를 받는다고 가정합니다. FunChatMessage 클래스는 multicast_message.pb.h 에 정의되어 있으며 추가적인 .proto 파일 없이 사용 가능합니다.

// 입장할 채널 이름을 입력합니다.
// 채널이 존재하지 않으면 서버에 새 채널이 생성됩니다.
string channel_id = "protobuf_channel";

// 채널에서 전송된 메세지를 받을 콜백 함수입니다.
// 이 예제에서는 채팅에 특화된 멀티캐스팅 서버를 구축했고
// FunChatMessage 를 사용해 메세지를 받는다고 가정합니다.
auto on_multicast_channel_received =
   [](const std::shared_ptr<fun::FunapiMulticast> &funapi_multicast,
      const fun::string &channel_id,
      const fun::string &sender_string,  // User ID(name)
      const FunMessage& message)
   {
     if (message.HasExtension(multicast))
     {
       FunMulticastMessage mcast_msg = message.GetExtension(multicast);
       if (mcast_msg.HasExtension(chat))
       {
         FunChatMessage chat_msg = mcast_msg.GetExtension(chat);
         // 채팅 메세지를 가져옵니다.
         fun::string text = chat_msg.text();
         // 여기서는 간단하게 로그만 출력하겠습니다.
         UE_LOG(LogFunapiExample, Log, TEXT("%s"), *FString(text.c_str()));
       }
     }
   };

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

메세지 전송

메시지를 전송하려면 SendToChannel() 함수를 호출합니다. channel_id 에 해당하는 채널에 접속한 모든 유저에게 메세지를 전송할 수 있습니다.

// channel 이름을 입력합니다.
string channel_id = "test_channel";

if (multicast_->GetEncoding() == fun::FunEncoding.kJson)
{
  fun::string temp_messsage = "multicast test message";

  TSharedRef<FJsonObject> json_object = MakeShareable(new FJsonObject);
  json_object->SetStringField(FString("message"), FString(temp_messsage.c_str()));

  // Convert JSON document to fun::string
  FString ouput_fstring;
  TSharedRef<TJsonWriter<TCHAR>> writer = TJsonWriterFactory<TCHAR>::Create(&ouput_fstring);
  FJsonSerializer::Serialize(json_object, writer);
  fun::string json_string = TCHAR_TO_ANSI(*ouput_fstring);

  // 채널에 메세지를 전송합니다.
  multicast_->SendToChannel(channel_id, json_string);

  // 만약 내가 보낸 메세지를 받기 싫다면 아래와 같이 사용합니다.
  // multicast_->SendToChannel(channel_id, json_string, false /* bounce */);
}
else
{
  FunMessage msg;
  FunMulticastMessage* mcast_msg = msg.MutableExtension(multicast);
  FunChatMessage *chat_msg = mcast_msg->MutableExtension(chat);
  chat_msg->set_text("multicast test message");

  // 채널에 메세지를 전송합니다.
  multicast_->SendToChannel(channel_id, msg);

  // 만약 내가 보낸 메세지를 받기 싫다면 아래와 같이 사용합니다.
  // multicast_->SendToChannel(channel_id, msg, false /* bounce */);
}

채널 퇴장

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

// 채널 나가기
string channel_id = "test_channel";
multicast_->LeaveChannel(channel_id);

Editor에서 실행하기

플러그인에는 FunapiSession 테스트를 위한 간단한 샘플코드가 포함되어 있습니다. 이 액터 클래스의 객체가 언리얼 에디터에 표시가 되어야 tester 샘플맵이 오류없이 플레이가 됩니다. 처음 프로젝트를 실행하면 에디터에 funapi_tester 객체가 표시되지 않을 수 있습니다. 이 때 프로젝트를 한 번 컴파일하면 클래스 객체가 만들어집니다. 혹시 컴파일을 해도 에디터에 표시되지 않는다면, 에디터를 종료 후 프로젝트를 다시 로드해야합니다. funapi_tester 객체가 에디터의 콘텐츠 브라우저(C++ Class > funapi_plugin_ue4)에 보인다면 tester 맵을 로드한 후 플레이 버튼을 눌러 샘플 프로그램을 실행해 볼 수 있습니다.

외부 라이브러리 컴파일하기

언리얼 플러그인에서는 다음과 같은 외부 라이브러리를 사용하고 있습니다.

libcurl

HTTP 통신에 필요한 기능을 사용하고 있습니다.

libcrypto

Openssl 라이브러리에 포함된 암호화 라이브러리. MD5를 사용하고 있습니다.

libprotobuf

Google protocol buffer 라이브러리입니다.

libsodium

ChaCha20 AES-128 암호화를 위한 라이브러리입니다.

위의 라이브러리를 위한 컴파일용 스크립트와 컴파일된 라이브러리 파일을 함께 배포하고 있습니다. 플러그인 폴더에 ThirdParty 폴더 안에 빌드 스크립트와 라이브러리 파일들이 플랫폼 별로 구분되어 있습니다.

ThirdParty
  ㄴ build     // 라이브러리 빌드 스크립트 파일들
  ㄴ include   // 헤더 파일들
  ㄴ lib       // 라이브러리 파일들
  ㄴ proto     // .proto 파일과 proto 파일용 컴파일 스크립트

배포된 컴파일용 스크립트로 라이브러리를 직접 빌드해서 사용해도 되지만 특별히 라이브러리를 새로 빌드해야 할 이유가 있는 것이 아니라면 배포된 라이브러리 파일을 그대로 사용할 것을 권장합니다.

Protobuf 파일 빌드하기

.proto 파일 빌드를 위해 플랫폼 별로 두 개의 protobuf 빌드 스크립트를 제공하고 있습니다.
  • ThirdParty/proto/{build-protobuf-win.bat} : Windows

  • ThirdParty/proto/{build-protobuf-mac.sh} : Mac OSX

각 빌드 스크립트를 실행하면 다음과 같은 작업을 진행합니다.
  1. Funapi 플러그인 내부에서 사용하는 .proto 파일의 컴파일.

  2. test_messages.proto 와 같이 어플리케이션에서 사용하는 .proto 파일의 컴파일.

  3. protobuf 컴파일 결과물을 어플리케이션에서 참조할 수 있는 위치로 이동.

사용자 정의 .proto 파일 빌드 경로 설정

사용자가 직접 정의한 .proto 파일을 컴파일하기 위해서는 컴파일 스크립트에 파일을 읽어들일 경로와 결과물을 저장할 경로를 설정해야 합니다. 플러그인과 함께 제공하는 test_messages.proto 파일도 사용자 정의 .proto 의 예시입니다.

  1. USER_PROTO_FILE_INPUT_PATH

    USER_PROTO_FILE_INPUT_PATH 는 사용자 proto 파일이 있는 디렉터리의 경로입니다. 경로는 절대 경로 또는 protobuf 컴파일 스크립트의 실행 경로로 부터의 상대경로입니다. 초기값은 {ProjectRootPath}\Plugins\Funapi\ThirdParty\proto\ 입니다.

  2. Protobuf 빌드 스크립트의 USER_PROTO_FILE_OUT_PATH.

    USER_PROTO_FILE_OUT_PATH 는 사용자 proto 파일의 컴파일 결과물이 생성되는 디렉터리 경로입니다. 경로는 절대 경로 또는 protobuf 컴파일 스크립트의 실행 경로로 부터의 상대경로입니다. 초기값은 {ProjectRootPath}\Source\funapi_plugin_ue4 입니다.

iOS 플랫폼 빌드 시 추가 작업

Important

iOS 에서 protobuf 컴파일 결과물을 추가작업 없이 사용하면 iPhone XS 기종에서 앱이 정상적으로 동작하지 않는 문제가 있습니다. iOS 타겟 빌드 시에는 다음 절차를 따라서 추가 작업을 진행 해 주시기 바랍니다.

언리얼 엔진 4.22 이하 버전을 사용해서 제작한 iOS 빌드를 iPhone XS에서 실행하면 std::{container} 코드를 수행할 때, 언리얼 엔진의 동적 메모리 할당자와 충돌을 일으키는 문제 ( 링크 )를 우회하기 위해 iOS 타겟 빌드를 진행하기에 앞서 std::{container} 인터페이스를 fun 네임스페이스를 사용하도록 변경해야 합니다.

아이펀 플러그인에서 기본적으로 사용하는 protobuf 컴파일 결과물, 예를 들면 fun_message.pb.h 파일이나 ping_message.pb.cc 파일 등에 대해서는 protobuf 컴파일 스크립트의 실행 과정에서 코드 중 std::{container} 를 사용하는 코드를 fun 네임스페이스를 사용하도록 자동으로 변환합니다.

다만, 사용자 정의 protobuf 파일들은 치환 정규식을 적용하기에 예외로 볼 수 있는 경우가 너무도 다양하여 의도하지 않은 코드까지 변경이 오류를 유발하는 경우가 많습니다.

따라서 Unreal Engine의 문제가 수정되기 전까지는 번거로우시더라도 test_messages.pb.h, test_messages.pb.cc 파일을 포함하여 사용자 어플리케이션 수준에서 정의한 protobuf 컴파일 결과물에 대해서는 직접 코드 변경을 진행 해 주시기 바랍니다.

Note

fun::{container} 란?

fun::{container} 는 iOS 플랫폼에서는 std::allocator 를 사용하지 않도록 만든 래핑 인터페이스입니다. 이 인터페이스를 사용하면 UE4이 iOS 환경에서 사용하는 Memory allocator 관련 문제를 우회할 수 있습니다.

이제부터 수정해야 하는 파일 목록과 수정해야 하는 인터페이스 목록에 대해 설명하겠습니다.

설명의 이해를 돕기 위해 test_messages.pb.h 파일과 test_messages.pb.cc 파일을 예로 설명하겠습니다.

1. 수정해야 하는 파일 목록
  • protobuf 컴파일 스크립트의 USER_PROTO_FILE_OUT_PATH 경로에 생성되는 모든 .pb.h 파일과 .pb.cc 파일

2. fun 네임스페이로를 사용하도록 변경해야 하는 인터페이스 목록

Tip

Editor의 문자열 치환 기능을 사용해서 확인 후 변경하시는 것을 권장합니다.

변경 전

변경 후

std::ostringstream

fun::ostringstream

ostringstream

fun::ostringstream

std::istringstream

fun::istringstream

istringstream

fun::istringstream

std::stringstream

fun::stringstream

stringstream

fun::stringstream

std::string

fun::string

string

fun::string

std::vector

fun::vector

vector

fun::vector

std::queue

fun::queue

queue

fun::queue

std::deque

fun::deque

deque

fun::deque

std::set

fun::set

set

fun::set

std::map

fun::map

map

fun::map

std::unordered_map

fun::unordered_map

unordered_map

fun::unordered_map

3. test_messages.pb.h 파일의 네임스페이스 변경 결과 예시

변경 전

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public:
::google::protobuf::Metadata GetMetadata() const;
// nested types ----------------------------------------------------
// accessors -------------------------------------------------------
// optional bytes sid = 1;
inline bool has_sid() const;
inline void clear_sid();
static const int kSidFieldNumber = 1;
inline const ::std::string& sid() const;
inline void set_sid(const ::std::string& value);
inline void set_sid(const char* value);
inline void set_sid(const void* value, size_t size);
inline ::std::string* mutable_sid();
inline ::std::string* release_sid();
inline void set_allocated_sid(::std::string* sid);
// optional string msgtype = 2;
inline bool has_msgtype() const;
inline void clear_msgtype();
static const int kMsgtypeFieldNumber = 2;
inline const ::std::string& msgtype() const;
inline void set_msgtype(const ::std::string& value);
inline void set_msgtype(const char* value);
inline void set_msgtype(const char* value, size_t size);
inline ::std::string* mutable_msgtype();
inline ::std::string* release_msgtype();
inline void set_allocated_msgtype(::std::string* msgtype);

변경 후

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public:
::google::protobuf::Metadata GetMetadata() const;
// nested types ----------------------------------------------------
// accessors -------------------------------------------------------
// optional bytes sid = 1;
inline bool has_sid() const;
inline void clear_sid();
static const int kSidFieldNumber = 1;
inline const ::fun::string& sid() const;
inline void set_sid(const ::fun::string& value);
inline void set_sid(const char* value);
inline void set_sid(const void* value, size_t size);
inline ::fun::string* mutable_sid();
inline ::fun::string* release_sid();
inline void set_allocated_sid(::fun::string* sid);
// optional fun::string msgtype = 2;
inline bool has_msgtype() const;
inline void clear_msgtype();
static const int kMsgtypeFieldNumber = 2;
inline const ::fun::string& msgtype() const;
inline void set_msgtype(const ::fun::string& value);
inline void set_msgtype(const char* value);
inline void set_msgtype(const char* value, size_t size);
inline ::fun::string* mutable_msgtype();
inline ::fun::string* release_msgtype();
inline void set_allocated_msgtype(::fun::string* msgtype);

언리얼 엔진과 관련한 알려진 문제

언리얼 엔진의 제약 사항이나 아직 수정되지 않은 문제로 인해서 Funapi 플러그인 사용 시 발생할 수 있는 문제들 대해서 해결방법을 알려드립니다.

아래 내용을 확인 해 보시고, 추가 문의사항은 iFun Engine Q&A 게시판 에 남겨 주시기 바랍니다.

버전 4.21 : .cc 파일 컴파일 오류

언리얼 엔진 4.21 버전에서 .cc 확장자를 갖는 파일을 포함해서 컴파일 하는 경우 아래와 같은 메시지를 출력하면서 컴파일 오류가 발생합니다. 언리얼 이슈 링크

UnrealBuildTool : error : Was only expecting C++ files to have CachedCPPEnvironments!

Funapi UE4 플러그인을 사용하면 플러그인이 포함하는 .cc 확장자 때문에 문제가 발생하며, Unreal Engine 4.22 이후 버전에서는 발생하지 않습니다.

  1. funapi-plugin-ue4 프로젝트와 함께 제공하는 ChangeExtensions.{bat|sh} 스크립트를 운영체제에 맞게 실행하면 .cc 파일의 확장자를 .cpp 파일로 변경할 수 있습니다. 또는 직접 찾아서 수정해도 됩니다.

  2. 각 빌드 시스템 타깃 파일에 bUseUnityBuild 옵션을 비활성화 합니다.

File name : ${Project_root}/funapi_plugin_ue4Editor.Target.cs
Description : 샘플 프로젝트의 Editor 빌드 시스템 설정파일입니다.

public funapi_plugin_ue4EditorTarget(TargetInfo Target) : base(Target)
{
  Type = TargetType.Editor;
  // UnityBuild 옵션을 비활성화 합니다.
  bUseUnityBuild = false;
  ExtraModuleNames.Add("funapi_plugin_ue4");
}

Cocos2d-x 플러그인

Cocos2d-x client plugin은 GitHub 를 통해 받으실 수 있습니다. 암호화가 포함된 버전이 필요하시면 iFun Engine support 로 요청 메일을 보내주십시오.

버그 신고

Client plugin 에 대한 건의사항이나 버그 신고는 iFun Engine Q&A 게시판 에 남겨 주시기 바랍니다..