24. 콘텐츠 지원 Part 1: 리더보드(랭킹)

Leaderboard 서비스에는 아래와 같이 경쟁 집단 축경쟁 기간 축 이라는 개념이 존재합니다.

  • 경쟁 집단 축
    • 소셜: 친구 목록, 길드 등의 특정 집단
    • 전체 플레이어
  • 경쟁 기간 축
    • 일간
    • 주간
    • 전체

이 두 개의 축을 조합하면 하나의 게임 안에서 여러 종류의 랭킹을 구성할 수 있습니다.

경쟁 집단 축 과 경쟁 기간 축의 조합 예
  • 친구간 주간 최고 점수 랭킹
  • 모든 유저간 역대 최고 점수 랭킹
  • 길드간 일간 점수 랭킹
  • 길드원간 일일 활동 점수 랭킹

24.1. iFun Leaderboard

아이펀 엔진은 leaderboard 를 전담하는 agent 로서 iFun Leaderboard 라는 프로그램을 사용합니다. 게임 서버는 iFun Leaderboard 에 점수 갱신, 랭킹 조회등의 요청을 전달하고, iFun Leaderboard 가 실제 이 작업을 수행하게 됩니다.

Note

아이펀 엔진이 별도의 leaderboard agent 를 사용하는 것은 게임 서버에 영향을 주지 않고 새로운 결제의 추가나 변경을 가능하게 하기 위해서입니다.

또한 agent 를 이용하면 게임 서버가 아닌 다른 서버 역시 agent 의 REST API 를 통해 ranking 관련된 서비스를 이용할 수 있어 편리합니다.

24.1.1. 설치 방법

Tip

게임 서버 측 Leaderboard 설정 파라미터use_leaderboardfalse 로 설정하면 게임 서버는 모든 leaderboard 요청을 dummy 로 처리하는 테스트 모드로 동작합니다. 개발 초기단계에 유용한 설정이며, 이 경우에는 iFun Leaderboard 를 설치할 필요가 없습니다.

Note

leaderbaord 는 캐시 처리를 위해 Redis 를 사용하며 랭킹을 저장하기 위해 MySQL Server 를 사용합니다. Redis 는 2.8.4 이상, MySQL server 는 5.5 이상이 필요합니다.

24.1.1.1. Ubuntu 인 경우

$ sudo apt-get update
$ sudo apt-get install funapi-leaderboard1 redis-server mysql-server

24.1.1.2. CentOS 인 경우

$ sudo yum install funapi-leaderboard1 redis mysql-server
$ sudo systemctl enable funapi-leaderboard

24.1.2. 실행 방법

24.1.2.1. Ubuntu 14.04 혹은 Centos 6 인 경우

/etc/default/funapi-leaderboard 파일을 열어 enabled=1 로 수정합니다.

그리고 다음 명령을 실행합니다.

$ sudo service funapi-leaderboard start

24.1.2.2. Ubuntu 16.04 혹은 CentOS 7 인 경우

다음 명령을 실행합니다.

$ sudo systemctl enable funapi-leaderboard
$ sudo systemctl start funapi-leaderboard

24.1.3. 동작 확인

24.1.3.1. Ubuntu 14.04 혹은 Centos 6 인 경우

$ sudo service funapi-leaderboard status

24.1.3.2. Ubuntu 16.04 혹은 CentOS 7 인 경우

$ sudo systemctl status funapi-leaderboard

24.1.3.3. Log 파일

/var/log/funapi/funapi-leaderboard/ 에 로그를 생성합니다.

24.1.4. iFun Leaderboard 설정 방법 (MANIFEST.json)

Note

이 항목은 iFun Leaderboard 자체의 설정에 대한 내용입니다. iFun Leaderboard 를 이용하는 게임 서버의 설정은 게임 서버 측 Leaderboard 설정 파라미터 을 참고하세요.

iFun Leaderboard 역시 아이펀 엔진으로 작성된 프로그램입니다. 따라서 iFun Leaderboard 역시 MANIFEST.json 을 통해 설정값을 바꿀 수 있습니다. iFun Leaderboard 의 MANIFEST.json 은 /usr/share/funapi-leaderboard/default/manifests/MANIFEST.json 에 있습니다.

다음과 같은 설정 값을 지정할 수 있습니다.

네트워크 설정

  • server_tcp_port: 아이펀 엔진 게임 서버가 iFun Leaderboard 와 통신하는 TCP port 번호를 지정합니다. (type=uint16, default=12820)

랭킹 저장용 MySQL 설정

  • mysql_server_url: 랭킹 정보를 저장하는 DB 서버의 IP 주소와 포트를 입력합니다. (type=string, default=”tcp://127.0.0.1:3306”)
  • mysql_id: 랭킹 정보를 저장할 DB 의 유저 ID 를 입력합니다. (type=string, default=”funapileaderboard1”)
  • mysql_pw: 랭킹 정보를 저장할 DB 의 유저 비밀번호를 입력합니다. (type=string, default=”qlffj1!!”)
  • mysql_db_name: 랭킹 정보를 저장할 DB 의 이름을 입력합니다. (type=string, default=”funapi_leaderboard1”)
  • mysql_db_connection_count: iFun Leaderboard 가 MySQL server 와 통신하기 위해 사용하는 connection pool size 입니다. (type=uint16, default=10)
  • mysql_db_character_set: 랭킹 정보가 저장되는 DB 에 적용할 character set 을 입력합니다. (type=string, default=”utf8”)
  • mysql_local_account_column_length: 랭킹 정보가 저장되는 DB 테이블의 local_account 컬럼의 길이를 입력합니다. (type=uint64, default=50)
  • leaderboard_use_db_stored_procedure: Mysql stored procedure 사용 여부를 입력합니다. (type=bool, default=true)
  • leaderboard_use_db_auto_schema_generation: 리더보드 서버를 실행할 때 DB 에 스키마를 자동으로 생성할 지 여부를 입력합니다. (type=bool, default=true)
  • export_db_schema_to_file: 지정된 경로에 DB 스키마 생성 스크립트를 저장하고 종료합니다. 단, leaderboard_use_db_auto_schema_generation 은 false 로 입력해야 합니다. (type=string, default=””)

랭킹 저장용 Redis 설정

iFun Leaderboard 는 랭킹을 caching 하기 위해서 Redis 를 사용합니다. Redis 설정은 아이펀 엔진의 Redis 섹션을 그대로 활용합니다. 따라서 Redis 기능 설정 파라미터 를 참고하여 Redis 정보를 입력해줘야됩니다.

Important

기존에 존재하는 Redis 설정을 재활용하기 때문에, 다른 설정과 달리 dependency 항목 아래 Redis 가 포함됩니다.

Leaderboard 설정

  • reset_schedules: 랭킹을 리셋하는 스케쥴을 조정할 수 있습니다. JSON object 의 array 타입이며 아래 포맷처럼 기술합니다.

    "reset_schedules": [
      {
        "leaderboard_id": String,
        "period": String
        "interval": Integer,
        "starts": String,
        "ends": String
      },
      ...
    ]
    
    • leaderboard_id: 랭킹을 구분하기 위한 이름을 입력합니다.
    • period: 랭킹을 리셋하는 주기의 단위를 입력합니다. 현재 "day""week" 이 가능합니다.
    • interval: 리셋 주기를 입력합니다. period 에 정의된 단위를 따릅니다. 예를 들어, period 가 day 이고, interval 이 1 이면 랭킹이 매일 리셋되는 일간 랭킹이 됩니다. 만일 period 가 week 이고 interval 이 2이면 2주마다 랭킹을 리셋하는 2주간 랭킹이 됩니다.
    • starts: 주기적인 랭킹 리셋이 처음 시작될 날짜와 시간을 “2015-04-30 13:00:00” 형태로 입력합니다.
    • ends: 주기적인 랭킹 리셋이 끝나는 날짜와 시간을 “2020-12-31 23:59:59” 형태로 입력합니다.

Tip

에이전트를 업데이트하게 되면 기존 MANIFEST.json 은 덮어씌여지게 됩니다. 이를 방지하기 위해서는 임시로 MANIFEST.json 오버라이드하기 에 언급된대로 override 파일을 사용할 수 있습니다.

/etc/funapi-leaderboard/MANIFEST.override.json:

{
  "override": {
    "FunapiLeaderboardServer": {
      "mysql_server_url": "tcp://10.10.10.10:36060",
      "mysql_id": "leaderboard",
      "mysql_pw": "leaderboard",
      ...
    },
    "Redis": {
      "enable_redis": true,
      "redis_mode": "redis",
      "redis_servers": {
        "": {
          "address": "127.0.0.1:6379",
          "auth_pass": ""
        }
      },
      ...
    }
    ...
  }
}

24.1.5. 랭킹 리셋 스케쥴 설정

iFun Leaderboard agent 는 다음과 같이 특정 주기로 랭킹을 리셋합니다.

주기 설명
day 매일 특정 시분초마다 랭킹을 리셋합니다.
week 매주 특정 요일, 시분초마다 랭킹을 리셋합니다.

랭킹을 리셋하는 스케쥴은 iFun Leaderboard 설정 방법 (MANIFEST.json)reset_schedules 를 통해 등록하거나 수정할 수 있습니다. 랭킹 리셋 스케쥴은 리더보드 에이전트가 MANIFEST 에 입력된 reset_schedules 를 토대로 자체적으로 스케쥴링하며 동작 방식은 다음과 같습니다.

  • 일간 랭킹 리셋 스케쥴만 등록되어 있으면 일간 랭킹만 리셋합니다.
  • 주간 랭킹 리셋 스케쥴만 등록되어 있으면 주간 랭킹만 리셋합니다.
  • 일간, 주간 랭킹 리셋 스케쥴이 모두 등록되어 있으면 각각 동작합니다.

24.1.5.1. 리셋 스케쥴 등록하기

예제:

사용자간 랭킹을 저장하는 leaderboard ID 로 player_game_score 이 있고, 길드간 랭킹을 저장하는 leaderboard ID 로 guild_game_score 이 있다고 가정했을 때, 사용자간 일간 랭킹, 사용자간 주간 랭킹, 길드간 2주간 랭킹 등 총 3개 랭킹에 대해서 리셋 스케쥴을 등록해보겠습니다.

다음은 iFun Leaderboard 의 MANIFEST.json 파일의 일부 내용입니다.

 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
27
28
29
{
  ...
  "arguments": {
    ...
    "reset_schedules": [
      {
        "leaderboard_id": "player_game_score",
        "period": "day",
        "interval": 1,
        "starts": "2015-01-01 00:00:00",
        "ends": "2020-12-31 23:59:59"
      },
      {
        "leaderboard_id": "player_game_score",
        "period": "week",
        "interval": 1,
        "starts": "2015-01-01 00:00:00",
        "ends": "2020-12-31 23:59:59"
      },
      {
        "leaderboard_id": "guild_game",
        "period": "week",
        "interval": 2,
        "starts": "2015-05-01 00:00:00",
        "ends": "2020-12-31 23:59:59"
      }
    ]
  }
}

Note

2015 년 1월 1일이 목요일이기 때문에, 두번째 리셋 스케쥴은 매주 목요일 랭킹을 리셋하는 스케쥴이 됩니다. 유사하게 2015년 5월 1일은 금요일이기 때문에 세번째 리셋 스케쥴은 매주 금요일 랭킹을 리셋합니다.

24.1.5.2. 리셋 스케쥴 수정하기

리셋 스케쥴을 등록하는 것과 마찬가지로 리셋 스케쥴의 수정도 MANIFEST.json 의 reset_schedules 를 통해 이루어집니다. 따라서 reset_schedules 에서 수정하려는 항목을 변경만 하면 됩니다.

24.1.6. (고급) 랭킹 데이터 직접 삭제/복구

24.1.6.1. iFun Leaderboard 가 MySQL 에 저장하는 데이터

MySQL 에 다음과 같은 패턴으로 table 을 생성하여 랭킹 데이터를 저장합니다.

{leaderboard_id}_{timespan}

예를 들어, leaderboard_id 를 player_game 이라고 지정하고 점수를 갱신한다면 다음과 같은 SQL table 이 생성됩니다.

  • player_game_alltime
  • player_game_daily
  • player_game_weekly
  • player_game_lastweek
  • player_game_yesterday

Note

lastweekyesterday 는 리셋 스케쥴이 동작하여 랭킹이 초기화되면 생성됩니다.

24.1.6.2. iFun Leaderboard 가 Redis 에 저장하는 데이터

Redis 에 다음과 같은 패턴으로 key 를 지정하여 랭킹 데이터를 저장합니다.

leaderboard:{leaderboard_id}:{timespan}
leaderboard:{leaderboard_id}:{timespan}:dense
leaderboard:{leaderboard_id}:{timespan}:timestamp
leaderboard:{leaderboard_id}:{player_id}:friends

Note

leaderboard:{leaderboard_id}:{player_id}:friends 의 경우 친구들간 랭킹 처리를 위해 생성된 후 처리가 완료되면 삭제됩니다.

예를 들어 leaderboard_id 를 player_game 이라고 지정하여 점수를 갱신한다면 Redis 상에 다음과 같은 key 들이 생성됩니다.

기본 keys:

  • leaderboard:player_game:alltime
  • leaderboard:player_game:daily
  • leaderboard:player_game:weekly
  • leaderboard:player_game:lastweek
  • leaderboard:player_game:yesterday

dense keys:

  • leaderboard:player_game:alltime:dense
  • leaderboard:player_game:daily:dense
  • leaderboard:player_game:weekly:dense
  • leaderboard:player_game:lastweek:dense
  • leaderboard:player_game:yesterday:dense

timestamp keys:

  • leaderboard:player_game:alltime:timestamp
  • leaderboard:player_game:daily:timestamp
  • leaderboard:player_game:weekly:timestamp
  • leaderboard:player_game:lastweek:timestamp
  • leaderboard:player_game:yesterday:timestamp

Note

lastweekyesterday 는 리셋 스케쥴이 동작하여 랭킹이 초기화되면 생성됩니다.

24.1.6.3. 랭킹 데이터 삭제하기

랭킹 데이터를 삭제하기 위해서는 MySQL 과 Redis 에 존재하는 관련된 데이터를 모두 삭제해야 합니다.

24.1.6.3.1. MySQL 에서 랭킹 데이터 삭제하기

truncate table 이나 drop table 명령을 이용해서 iFun Leaderboard 가 MySQL 에 저장하는 데이터 에 언급된 테이블들을 삭제합니다.

예를 들어 leaderboard_id 가 player_game 이라고 가정하면 다음과 같습니다.

mysql> truncate table player_game_alltime
mysql> truncate table player_game_daily
mysql> truncate table player_game_weekly
mysql> truncate table player_game_lastweek
mysql> truncate table player_game_yesterday

또는

mysql> drop table player_game_alltime
mysql> drop table player_game_daily
mysql> drop table player_game_weekly
mysql> drop table player_game_lastweek
mysql> drop table player_game_yesterday
24.1.6.3.2. Redis 에서 랭킹 데이터 삭제하기

del 명령을 이용해서 iFun Leaderboard 가 Redis 에 저장하는 데이터 에 언급된 key 들을 삭제합니다.

예를 들어 leaderboard_id 가 player_game 이라고 가정하면 다음과 같습니다.

127.0.0.1:6379> del leaderboard:player_game:alltime
127.0.0.1:6379> del leaderboard:player_game:daily
127.0.0.1:6379> del leaderboard:player_game:weekly
127.0.0.1:6379> del leaderboard:player_game:lastweek
127.0.0.1:6379> del leaderboard:player_game:yesterday

127.0.0.1:6379> del leaderboard:player_game:alltime:timestamp
127.0.0.1:6379> del leaderboard:player_game:daily:timestamp
127.0.0.1:6379> del leaderboard:player_game:weekly:timestamp
127.0.0.1:6379> del leaderboard:player_game:lastweek:timestamp
127.0.0.1:6379> del leaderboard:player_game:yesterday:timestamp

127.0.0.1:6379> del leaderboard:player_game:alltime:dense
127.0.0.1:6379> del leaderboard:player_game:daily:dense
127.0.0.1:6379> del leaderboard:player_game:weekly:dense
127.0.0.1:6379> del leaderboard:player_game:lastweek:dense
127.0.0.1:6379> del leaderboard:player_game:yesterday:dense

Tip

만약 Leaderboard 전용으로만 사용하는 Redis Server 가 존재한다면 Redis 명령인 flushall 을 이용하여 좀 더 간단하게 데이터를 삭제할 수 있습니다.

127.0.0.1:6379> flushall

24.1.6.4. MySQL 에서 Redis 로 랭킹 데이터 복구하기

Redis server 에서 데이터가 손실되었다면 iFun Leaderboard agent 를 복구 모드로 실행하여 MySQL 에 저장되어 있는 랭킹 데이터를 Redis 로 복구할 수 있습니다.

Tip

복구모드는 단순히 MySQL 에서 Redis 로 데이터를 복사하며, 기존에 Redis 에 존재하던 데이터를 삭제하지는 않습니다.

리더보드 에이전트의 복구모드 실행은 다음과 같이 입력합니다.

Ubuntu 14.04 혹은 Centos 6 의 경우

$ sudo service funapi-leaderboard stop
$ funapi-leaderboard-launcher --alsologtostderr --recover_leaderboard
$ sudo service funapi-leaderboard start

Ubuntu 16.04 혹은 Centos 7 의 경우

$ sudo systemctl stop funapi-leaderboard
$ funapi-leaderboard-launcher --alsologtostderr --recover_leaderboard
$ sudo systemctl start funapi-leaderboard

Important

복구 모드로 실행할 때에는 –alsologtostderr 옵션이 반드시 추가 되어야 합니다. 이 옵션이 생략되어 실행되면 옵션을 추가하라는 로그와 함께 종료됩니다.

위와 같이 복구모드로 실행하면 MySQL 에 존재하는 모든 leaderboard table 의 데이터를 가져와서 복구를 하되 복구 진행 여부를 묻습니다.

$ funapi-leaderboard-launcher --alsologtostderr --recover_leaderboard
...
I1221 11:19:49.893380  9231 leaderboard.cc:714] Start recovery mode
I1221 11:19:50.894167  9231 leaderboard.cc:742] The recovery target leaderboard: player_game
I1221 11:19:50.894194  9231 leaderboard.cc:745] The leaderboard will be recovered ranking data from DB to Redis. Do you want to do this? (y, n)

복구 진행 여부는 table 단위가 아닌 leaderboard ID 별로 확인합니다. 예를 들어 player_gameguild_game 이라는 leaderboard ID 를 사용하는 경우, MySQL 에는 다음과 같은 테이블들이 존재할 것이고, player_game 에 대한 복구 진행 여부를 확인 한 후에 guild_game 에 대한 복구 진행 여부를 확인하게 됩니다.

mysql> show tables;
+-------------------------------+
| Tables_in_funapi_leaderboard1 |
+-------------------------------+
| player_game_alltime           |
| player_game_daily             |
| player_game_lastweek          |
| player_game_weekly            |
| player_game_yesterday         |
| guild_game_alltime            |
| guild_game_daily              |
| guild_game_lastweek           |
| guild_game_weekly             |
| guild_game_yesterday          |
+-------------------------------+

Tip

복구 여부 확인 없이 무조건 복구를 진행하기 위해서는 --force_leaderboard_recovery 라는 옵션을 줄 수 있습니다. 이 옵션이 주어질 때에는 --alsologtostderr 를 누락 할 수 있으나, 복구 진행 상황을 보기 위해서는 추가하는 것이 좋습니다.

$ funapi-leaderboard-launcher --force_leaderboard_recovery --alsologtostderr

24.2. 예제: 랭킹 조회하기

예제를 통해 랭킹을 조회하는 방법을 살펴보겠습니다. 여기서는 경쟁집단 이름으로 player_game 을 사용하겠습니다.

Tip

아래 나오는 함수들의 보다 자세한 내용은 API 문서 를 참고하세요.

그 중, 조회 범위를 지정하는 LeaderboardRange::Type 은 설정 값에 따라 다음과 같이 사용해야됩니다.

Type begin, end 설정
kAll begin 과 end 를 0 으로 설정해야 합니다.
kFromTop begin 은 0 이상이고 end 는 begin 보다 크거나 같아야 합니다.
kNearby begin 은 0 보다 작을 수 있고 end 는 begin 보다 크거나 같아야 합니다.

24.2.1. 주간 전체 랭킹 조회하기

24.2.1.1. 동기 방식

 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
const char *kPlayerGameScore = "player_game";


void example() {
  LeaderboardQueryRequest request(
      kPlayerGameScore,
      kWeekly,
      LeaderboardRange(LeaderboardRange::kAll, 0, 0),  // 전체 랭킹을 조회
      LeaderboardQueryRequest::kOrdinal);  // rank 값으로 1, 2, 3, 4 를 사용

  LeaderboardQueryResponse response;
  if (not GetLeaderboardSync(request, &response)) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  LOG(INFO) << "total player count: " << response.total_player_count;

  for (int i = 0; i < response.records.size(); ++i) {
    LOG(INFO) << "# " << response.records[i].rank << " - "
              << response.records[i].percentage << " - "
              << response.records[i].score << " - "
              << response.records[i].player_account.id();
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static string kPlayerGameScore = "player_game";

public static void example()
{
  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
    kPlayerGameScore,
    Leaderboard.Timespan.kWeekly,
    new Leaderboard.Range (Leaderboard.RangeType.kAll, 0, 0),  // 전체 랭킹을 조회
    Leaderboard.RankingType.kOrdinal);  // rank 값으로 1, 2, 3, 4 를 사용

  Leaderboard.QueryResponse response;
  if (!Leaderboard.GetLeaderboardSync (request, out response))
  {
    Log.Error ("leaderboard system error");
    return;
  }

  foreach (Leaderboard.Record record in response.Records)
  {
    Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
              record.Rank, record.Percentage,
              record.Score, record.PlayerAccount.Id);
  }
}

24.2.1.2. 비동기 방식

 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
27
28
29
30
31
32
33
const char *kPlayerGameScore = "player_game";


void OnResponse(
    const LeaderboardQueryRequest &request,
    const LeaderboardQueryResponse &response,
    const bool &error) {
  if (error) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  LOG(INFO) << "total player count: " << response.total_player_count;

  for (int i = 0; i < response.records.size(); ++i) {
    LOG(INFO) << "# " << response.records[i].rank << " - "
              << response.records[i].percentage << " - "
              << response.records[i].score << " - "
              << response.records[i].player_account.id();
  }
}


void example() {
  LeaderboardQueryRequest request(
      kPlayerGameScore,
      kWeekly,
      LeaderboardRange(LeaderboardRange::kAll, 0, 0),  // 전체 랭킹 조회
      LeaderboardQueryRequest::kOrdinal);  // rank 값으로 1, 2, 3, 4 를 사용

  LeaderboardQueryResponseHandler handler = OnResponse;
  GetLeaderboard(request, handler);
}
 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
27
28
29
30
31
static string kPlayerGameScore = "player_game";

public static void OnResponse(Leaderboard.QueryRequest request,
                              Leaderboard.QueryResponse response,
                              bool error)
{
  if (error) {
    Log.Error ("leaderboard system error");
    return;
  }

  Log.Info("total player count: {0}", response.TotalPlayerCount);

  foreach (Leaderboard.Record record in response.Records)
  {
    Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
              record.Rank, record.Percentage,
              record.Score, record.PlayerAccount.Id);
  }
}

public static void example()
{
  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
    kPlayerGameScore,
    Leaderboard.Timespan.kWeekly,
    new Leaderboard.Range (Leaderboard.RangeType.kAll, 0, 0),  // 전체 랭킹 조회
    Leaderboard.RankingType.kOrdinal);  // rank 값으로 1, 2, 3, 4 사용

  Leaderboard.GetLeaderboard(request, OnResponse);
}

24.2.2. 주간 TOP 10 랭킹 조회하기

24.2.2.1. 동기 방식

 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
const char *kPlayerGameScore = "player_game";


void example() {
  LeaderboardQueryRequest request(
      kPlayerGameScore,
      kWeekly,
      LeaderboardRange(LeaderboardRange::kFromTop, 0, 9),  // Top 10 을 조회
      LeaderboardQueryRequest::kStdCompetition);  // 1, 2, 2, 4 식으로 동점자를 처리합니다.

  LeaderboardQueryResponse response;
  if (not GetLeaderboardSync(request, &response)) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  LOG(INFO) << "total player count: " << response.total_player_count;

  for (int i = 0; i < response.records.size(); ++i) {
    LOG(INFO) << "# " << response.records[i].rank << " - "
              << response.records[i].percentage << " - "
              << response.records[i].score << " - "
              << response.records[i].player_account.id();
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static string kPlayerGameScore = "player_game";

public static void example()
{
  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
    kPlayerGameScore,
    Leaderboard.Timespan.kWeekly,
    new Leaderboard.Range (Leaderboard.RangeType.kFromTop, 0, 9),  // Top 10 을 조회
    Leaderboard.RankingType.kStdCompetition);  // 1, 2, 2, 4 식으로 동점자를 처리

  Leaderboard.QueryResponse response;
  if (!Leaderboard.GetLeaderboardSync (request, out response))
  {
    Log.Error ("leaderboard system error");
    return;
  }

  foreach (Leaderboard.Record record in response.Records)
  {
    Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
              record.Rank, record.Percentage,
              record.Score, record.PlayerAccount.Id);
  }
}

24.2.2.2. 비동기 방식

 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
27
28
29
30
31
32
33
const char *kPlayerGameScore = "player_game";


void OnResponse(
    const LeaderboardQueryRequest &request,
    const LeaderboardQueryResponse &response,
    const bool &error) {
  if (error) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  LOG(INFO) << "total player count: " << response.total_player_count;

  for (int i = 0; i < response.records.size(); ++i) {
    LOG(INFO) << "# " << response.records[i].rank << " - "
              << response.records[i].percentage << " - "
              << response.records[i].score << " - "
              << response.records[i].player_account.id();
  }
}


void example() {
  LeaderboardQueryRequest request(
      kPlayerGameScore,
      kWeekly,
      LeaderboardRange(LeaderboardRange::kFromTop, 0, 9),  // Top 10 을 조회
      LeaderboardQueryRequest::kStdCompetition);  // 1, 2, 2, 4 식으로 동점자 처리

  LeaderboardQueryResponseHandler handler = OnResponse;
  GetLeaderboard(request, handler);
}
 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
27
28
29
30
31
32
static string kPlayerGameScore = "player_game";

public static void OnResponse(Leaderboard.QueryRequest request,
                              Leaderboard.QueryResponse response,
                              bool error)
{
  if (error) {
    Log.Error ("leaderboard system error");
    return;
  }

  Log.Info("total player count: {0}", response.TotalPlayerCount);

  foreach (Leaderboard.Record record in response.Records)
  {
    Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
              record.Rank, record.Percentage,
              record.Score, record.PlayerAccount.Id);
  }
}

public static void example()
{
  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
    kPlayerGameScore,
    Leaderboard.Timespan.kWeekly,
    new Leaderboard.Range (Leaderboard.RangeType.kFromTop, 0, 9),  // Top 10 조회
    Leaderboard.RankingType.kStdCompetition);  // 1, 2, 2, 4 식으로 동점자 처리

  Leaderboard.QueryResponse response;
  Leaderboard.GetLeaderboard(request, OnResponse);
}

24.2.3. 자기 순위 주변 랭킹 조회하기

아래는 사용자의 위아래 5순위에 해당하는 유저들을 조회하는 예제입니다. 그 중에서도 Facebook 사용자 그룹 안에서의 랭킹을 조회해보겠습니다.

24.2.3.1. 동기 방식

 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
27
28
29
30
const char *kPlayerGameScore = "player_game";


void example() {
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  LeaderboardQueryRequest request(
      kPlayerGameScore,
      service_provider,
      player_id,
      kWeekly,
      LeaderboardRange(LeaderboardRange::kNearBy, -5, 5),  // 위 아래 5순위.
      LeaderboardQueryRequest::kDense);  // 동점자가 있을 시 1, 2, 2, 3 형태로 처리

  LeaderboardQueryResponse response;
  if (not GetLeaderboardSync(request, &response)) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  LOG(INFO) << "total player count: " << response.total_player_count;

  for (int i = 0; i < response.records.size(); ++i) {
    LOG(INFO) << "# " << response.records[i].rank << " - "
              << response.records[i].percentage << " - "
              << response.records[i].score << " - "
              << response.records[i].player_account.id();
  }
}
 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
27
28
29
30
31
static string kPlayerGameScore = "player_game";

public static void example()
{
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
    kPlayerGameScore,
    service_provider,
    player_id,
    Leaderboard.Timespan.kWeekly,
    new Leaderboard.Range (Leaderboard.RangeType.kNearBy, -5, 5),  // 위 아래 5 순위.
    Leaderboard.RankingType.kDense);  // 동점자가 있을 시, 1, 2, 2, 3 형태로 처리

  Leaderboard.QueryResponse response;
  if (!Leaderboard.GetLeaderboardSync (request, out response))
  {
    Log.Error("leaderboard system error");
    return;
  }

  Log.Info("total player count: {0}", response.TotalPlayerCount);

  foreach (Leaderboard.Record record in response.Records)
  {
    Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
              record.Rank, record.Percentage,
              record.Score, record.PlayerAccount.Id);
  }
}

24.2.3.2. 비동기 방식

 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
27
28
29
30
31
32
33
34
35
36
37
38
const char *kPlayerGameScore = "player_game";


void OnResponse(
    const LeaderboardQueryRequest &request,
    const LeaderboardQueryResponse &response,
    const bool &error) {
  if (error) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  LOG(INFO) << "total player count: " << response.total_player_count;

  for (int i = 0; i < response.records.size(); ++i) {
    LOG(INFO) << "# " << response.records[i].rank << " - "
              << response.records[i].percentage << " - "
              << response.records[i].score << " - "
              << response.records[i].player_account.id();
  }
}


void example() {
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  LeaderboardQueryRequest request(
      kPlayerGameScore,
      service_provider,
      player_id,
      kWeekly,
      LeaderboardRange(LeaderboardRange::kNearBy, -5, 5),  // 위 아래 5순위.
      LeaderboardQueryRequest::kDense);  // 동점자를 1, 2, 2, 3 형태로 처리

  LeaderboardQueryResponseHandler handler = OnResponse;
  GetLeaderboard(request, handler);
}
 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
27
28
29
30
31
32
33
34
35
36
static string kPlayerGameScore = "player_game";

public static void OnResponse(Leaderboard.QueryRequest request,
                              Leaderboard.QueryResponse response,
                              bool error)
{
  if (error) {
    Log.Error ("leaderboard system error");
    return;
  }

  Log.Info("total player count: {0}", response.TotalPlayerCount);

  foreach (Leaderboard.Record record in response.Records)
  {
    Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
              record.Rank, record.Percentage,
              record.Score, record.PlayerAccount.Id);
  }
}

public static void example()
{
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
    kPlayerGameScore,
    service_provider,
    player_id,
    Leaderboard.Timespan.kWeekly,
    new Leaderboard.Range (Leaderboard.RangeType.kNearby, -5, 5),  // 위 아래 5순위.
    Leaderboard.RankingType.kDense);  // 동점자를 1, 2, 2, 3 형태로 처리

  Leaderboard.GetLeaderboard(request, OnResponse);
}

24.2.4. 자기 랭킹 조회하기

아래는 사용자의 랭킹을 조회하는 예제입니다. 그 중에서도 Facebook 사용자 그룹 안에서의 랭킹을 조회해보겠습니다.

24.2.4.1. 동기 방식

 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
27
28
29
30
31
const char *kPlayerGameScore = "player_game";


void example() {
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  // RankingType 은 입력하지 않았으므로 기본값인 kOrdinal 이 됩니다.
  LeaderboardQueryRequest request(
      kPlayerGameScore,
      service_provider,
      player_id,
      kWeekly,
      LeaderboardRange(LeaderboardRange::kNearBy, 0, 0));  // 자기 랭킹을 조회합니다.

  LeaderboardQueryResponse response;
  if (not GetLeaderboardSync(request, &response)) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  LOG(INFO) << "total player count: " << response.total_player_count;

  // 랭킹 타입을 묵시적 기본값인 kOrdinal 을 사용했으로 rank 값은 1, 2, 3, 4, ... 이 됩니다.
  for (int i = 0; i < response.records.size(); ++i) {
    LOG(INFO) << "# " << response.records[i].rank << " - "
              << response.records[i].percentage << " - "
              << response.records[i].score << " - "
              << response.records[i].player_account.id();
  }
}
 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
27
28
29
30
31
32
33
static string kPlayerGameScore = "player_game";

public static void example()
{
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  // RankingType 은 입력하지 않았으므로 기본값인 kOrdinal 이 됩니다.
  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
    kPlayerGameScore,
    service_provider,
    player_id,
    Leaderboard.Timespan.kWeekly,
    new Leaderboard.Range (Leaderboard.RangeType.kNearby, 0, 0)  // 자기 랭킹 조회
  );

  Leaderboard.QueryResponse response;
  if (!Leaderboard.GetLeaderboardSync (request, out response))
  {
    Log.Error("leaderboard system error");
    return;
  }

  Log.Info("total player count: {0}", response.TotalPlayerCount);

  // 랭킹 타입을 묵시적 기본값인 kOrdinal 을 사용했으므로 rank 값은 1, 2, 3, 4, ... 이 됩니다.
  foreach (Leaderboard.Record record in response.Records)
  {
    Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
              record.Rank, record.Percentage,
              record.Score, record.PlayerAccount.Id);
  }
}

24.2.4.2. 비동기 방식

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
const char *kPlayerGameScore = "player_game";


void OnResponse(
    const LeaderboardQueryRequest &request,
    const LeaderboardQueryResponse &response,
    const bool &error) {
  if (error) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  LOG(INFO) << "total player count: " << response.total_player_count;

  // 랭킹 타입을 묵시적 기본값인 kOrdinal 로 사용했으므로 rank 값은 1, 2, 3, 4, ... 이 됩니다.
  for (int i = 0; i < response.records.size(); ++i) {
    LOG(INFO) << "# " << response.records[i].rank << " - "
              << response.records[i].percentage << " - "
              << response.records[i].score << " - "
              << response.records[i].player_account.id();
  }
}


void example() {
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  // RankingType 은 입력하지 않았으므로 기본값인 kOrdinal 이 됩니다.
  LeaderboardQueryRequest request(
      kPlayerGameScore,
      service_provider,
      player_id,
      kWeekly,
      LeaderboardRange(LeaderboardRange::kNearBy, 0, 0));  // 자기 랭킹을 조회합니다.

  LeaderboardQueryResponseHandler handler = OnResponse;
  GetLeaderboard(request, handler);
}
 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using funapi;

static string kPlayerGameScore = "player_game";

public static void OnResponse(Leaderboard.QueryRequest request,
                              Leaderboard.QueryResponse response,
                              bool error)
{
  if (error) {
    Log.Error ("leaderboard system error");
    return;
  }

  Log.Info("total player count: {0}", response.TotalPlayerCount);

  // 랭킹 타입을 묵시적 기본값인 kOrdinal 로 사용했으므로 rank 값은 1, 2, 3, 4, ... 이 됩니다.
  foreach (Leaderboard.Record record in response.Records)
  {
    Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
              record.Rank, record.Percentage,
              record.Score, record.PlayerAccount.Id);
  }
}

public static void example()
{
  const string kPlayerGameScore = "player_game";

  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  // RankingType 은 입력하지 않았으므로 기본값인 kOrdinal 이 됩니다.
  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
    kPlayerGameScore,
    service_provider,
    player_id,
    Leaderboard.Timespan.kWeekly,
    new Leaderboard.Range (Leaderboard.RangeType.kNearby, 0, 0)  // 자기 랭킹 조회
  );

  Leaderboard.GetLeaderboard(request, OnResponse);
}

24.2.5. 친구간 랭킹 조회하기

다음은 Facebook 친구 사이에서 랭킹을 조회하는 방법입니다. Facebook 친구 리스트를 얻는 방법은 예제: Facebook 친구 추출 참고해주세요.

Tip

아래의 예에서 LeaderboardQueryRequest 를 만들 때 kWeekly 를 kLastWeek 로 변경하면 지난 주의 친구간 랭킹을 얻는 요청을 만들 수 있습니다. 유사하게 kYesterday 로 변경하면 전날 친구간 랭킹을 얻는 요청을 만들 수 있습니다.

LeaderboardQueryRequest request(
    kPlayerGameScore, service_provider, player_id, friend_list, kLastWeek);

24.2.5.1. 동기 방식

 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
27
28
29
const char *kPlayerGameScore = "player_game";


void example() {
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  PlayerAccountVector friend_list = ...;  // 친구 리스트를 추출했다고 가정하겠습니다.

  // RankingType 은 입력하지 않았으므로 기본값인 kOrdinal 이 됩니다.
  LeaderboardQueryRequest request(
      kPlayerGameScore, service_provider, player_id, friend_list, kWeekly);

  LeaderboardQueryResponse response;
  if (not GetLeaderboardSync(request, &response)) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  LOG(INFO) << "total player count: " << response.total_player_count;

  // 랭킹 타입을 묵시적 기본값인 kOrdinal 로 했으므로 rank 값은 1, 2, 3, 4, ... 이 됩니다.
  for (int i = 0; i < response.records.size(); ++i) {
    LOG(INFO) << "# " << response.records[i].rank << " - "
              << response.records[i].percentage << " - "
              << response.records[i].score << " - "
              << response.records[i].player_account.id();
  }
}
 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
27
28
29
30
31
32
33
34
static string kPlayerGameScore = "player_game";

public static void example()
{
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  List<PlayerAccount> friend_list = ...;  // 친구 목록을 추출했다고 가정하겠습니다.

  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
    kPlayerGameScore,
    service_provider,
    player_id,
    friend_list,
    Leaderboard.Timespan.kWeekly);

  Leaderboard.QueryResponse response;
  if (!Leaderboard.GetLeaderboardSync (request, out response))
  {
    Log.Error("leaderboard system error");
    return;

  }

  Log.Info("total player count: {0}", response.TotalPlayerCount);

  // 랭킹 타입을 묵시적 기본값인 kOrdinal 로 했으므로 rank 값은 1, 2, 3, 4, ... 이 됩니다.
  foreach (Leaderboard.Record record in response.Records)
  {
    Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
              record.Rank, record.Percentage,
              record.Score, record.PlayerAccount.Id);
  }
}

24.2.5.2. 비동기 버전

 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
27
28
29
30
31
32
33
34
35
36
const char *kPlayerGameScore = "player_game";

void OnResponse(
    const LeaderboardQueryRequest &request,
    const LeaderboardQueryResponse &response,
    const bool &error) {
  if (error) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  LOG(INFO) << "total player count: " << response.total_player_count;

  // 랭킹 타입을 묵시적 기본값인 kOrdinal 로 했으므로 rank 값은 1, 2, 3, 4, ... 이 됩니다.
  for (int i = 0; i < response.records.size(); ++i) {
    LOG(INFO) << "# " << response.records[i].rank << " - "
              << response.records[i].percentage << " - "
              << response.records[i].score << " - "
              << response.records[i].player_account.id();
  }
}


void example() {
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  PlayerAccountVector friend_list = ...;  // 친구 목록을 추출했다고 가정하겠습니다.

  // RankingType 은 입력하지 않았으므로 기본값인 kOrdinal 이 됩니다.
  LeaderboardQueryRequest request(
      kPlayerGameScore, service_provider, player_id, friend_list, kWeekly);

  LeaderboardQueryResponseHandler handler = OnResponse;
  GetLeaderboard(request, handler);
}
 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
27
28
29
30
31
32
33
34
35
36
37
38
static string kPlayerGameScore = "player_game";

public static void OnResponse(Leaderboard.QueryRequest request,
                              Leaderboard.QueryResponse response,
                              bool error)
{
  if (error) {
    Log.Error ("leaderboard system error");
    return;
  }

  Log.Info("total player count: {0}", response.TotalPlayerCount);

  // 랭킹 타입을 묵시적 기본값인 kOrdinal 로 했으므로 rank 값은 1, 2, 3, 4, ... 이 됩니다.
  foreach (Leaderboard.Record record in response.Records)
  {
    Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
              record.Rank, record.Percentage,
              record.Score, record.PlayerAccount.Id);
  }
}

public static void example()
{
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  List<PlayerAccount> friend_list = ...;  // 친구 목록을 추출했다고 가정하겠습니다.

  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
    kPlayerGameScore,
    service_provider,
    player_id,
    friend_list,
    Leaderboard.Timespan.kWeekly);

  Leaderboard.GetLeaderboard(request, OnResponse);
}

24.2.6. 한번에 여러 기준의 랭킹 조회하기

위에서 알아본 TOP 10 랭킹, 내 주변 랭킹, 내 랭킹, 친구간 랭킹을 조회하는 예제를 모두 합쳐서 한번의 요청으로 여러 기준의 랭킹을 조회하는 예제입니다.

24.2.6.1. 동기 방식

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
const char *kPlayerGameScore = "player_game";


void example() {
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  // 한번에 여러 랭킹을 조회할 것이므로 Vector 를 만듭니다.
  LeaderboardQueryRequestVector requests;

  // 우선 TOP 10 랭킹을 조회하기 위한 요청자를 만들고 vector 에 추가합니다.
  requests.push_back(
      LeaderboardQueryRequest(
          kPlayerGameScore,
          kWeekly,
          LeaderboardRange(LeaderboardRange::kFromTop, 0, 9),
          LeaderboardQueryRequest::kStdCompetition));

  // 내 주변 랭킹을 조회하기 위한 요청자를 만들고 vector 에 추가합니다.
  requests.push_back(
      LeaderboardQueryRequest(
          kPlayerGameScore,
          service_provider,
          player_id,
          kWeekly,
          LeaderboardRange(LeaderboardRange::kNearBy, -5, 5),
          LeaderboardQueryRequest::kDense));

  // 내 랭킹을 조회하기 위한 요청자를 만들고 vector 에 추가합니다.
  requests.push_back(
      LeaderboardQueryRequest(
          kPlayerGameScore,
          service_provider,
          player_id,
          kWeekly,
          LeaderboardRange(LeaderboardRange::kNearBy, 0, 0));

  // 친구 목록은 가져왔다고 가정하겠습니다.
  PlayerAccountVector friend_list = ...;

  requests.push_back(
      LeaderboardQueryRequest(
          kPlayerGameScore,
          service_provider,
          player_id,
          friend_list,
          kWeekly);

  // 랭킹을 조회합니다.
  LeaderboardQueryResponseVector responses;
  if (not GetLeaderboardSync(requests, &responses)) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  // responses 를 하나씩 루프돌면서 얻어온 랭킹 결과를 출력합니다.
  BOOST_FOREACH(const LeaderboardQueryResponse &response, responses) {
    LOG(INFO) << "total player count: " << response.total_player_count;

    for (int i = 0; i < response.records.size(); ++i) {
      LOG(INFO) << "# " << response.records[i].rank << " - "
                << response.records[i].percentage << " - "
                << response.records[i].score << " - "
                << response.records[i].player_account.id();
    }
  }
}
 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
  static string kPlayerGameScore = "player_game";

  public static void example()
  {
    string service_provider = "Facebook"; // service provider
    string player_id = "testuser";        // player id in service provider

    List<Leaderboard.QueryRequest> requests = new List<Leaderboard.QueryRequest> ();

    // 우선 TOP 10 랭킹을 조회하기 위한 요청자를 만들고 vector 에 추가합니다.
    requests.Add (new Leaderboard.QueryRequest(
        kPlayerGameScore,
        Leaderboard.Timespan.kWeekly,
        // 전체 랭킹을 조회합니다.
        new Leaderboard.Range (Leaderboard.RangeType.kFromTop, 0, 9),
        // 1224 랭킹 타입으로 동점자 순위를 처리합니다.
        Leaderboard.RankingType.kStdCompetition));

    // 내 주변 랭킹을 조회하기 위한 요청자를 만들고 vector 에 추가합니다.
    requests.Add (new Leaderboard.QueryRequest(
        kPlayerGameScore,
        service_provider,
        player_id,
        Leaderboard.Timespan.kWeekly,
        // 위 아래 5명의 랭킹을 조회합니다.
        new Leaderboard.Range (Leaderboard.RangeType.kNearby, -5, 5),
        // 1223 랭킹 타입으로 동점자 순위를 처리합니다.
        Leaderboard.RankingType.kDense));

    // 내 랭킹을 조회하기 위한 요청자를 만들고 list 에 추가합니다.
    requests.Add (new Leaderboard.QueryRequest(
        kPlayerGameScore,
        service_provider,
        player_id,
        Leaderboard.Timespan.kWeekly,
        // 나의 랭킹을 조회합니다.
        new Leaderboard.Range (Leaderboard.RangeType.kNearby, 0, 0)));

    // 친구 목록은 가져왔다고 가정하겠습니다.
    List<PlayerAccount> friend_list = ...;

    requests.Add (new Leaderboard.QueryRequest(
        kPlayerGameScore,
        service_provider,
        player_id,
        friend_list,
        Leaderboard.Timespan.kWeekly));

    List<Leaderboard.QueryResponse> responses;

    // 랭킹을 조회합니다.
    if (!Leaderboard.GetLeaderboard (requests, out responses))
    {
      return;
    }

    foreach (Leaderboard.QueryResponse response in responses)
    {
      foreach (Leaderboard.Record record in response.Records)
      {
        Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
                  record.Rank, record.Percentage,
                  record.Score, record.PlayerAccount.Id);
      }
    }
  }

24.2.6.2. 비동기 방식

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
const char *kPlayerGameScore = "player_game";


void OnResponse(
    const LeaderboardQueryRequestVector &requests,
    const LeaderboardQueryResponseVector &responses,
    const bool &error) {
  if (error) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  // responses 를 하나씩 루프돌면서 얻어온 랭킹 결과를 출력합니다.
  BOOST_FOREACH(const LeaderboardQueryResponse &response, responses) {
    LOG(INFO) << "total player count: " << response.total_player_count;

    for (int i = 0; i < response.records.size(); ++i) {
      LOG(INFO) << "# " << response.records[i].rank << " - "
                << response.records[i].percentage << " - "
                << response.records[i].score << " - "
                << response.records[i].player_account.id();
    }
  }
}


void example() {
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  LeaderboardQueryRequestVector requests;

  // 우선 TOP 10 랭킹을 조회하기 위한 요청자를 만들고 vector 에 추가합니다.
  requests.push_back(
      LeaderboardQueryRequest(
          kPlayerGameScore,
          kWeekly,
          LeaderboardRange(LeaderboardRange::kFromTop, 0, 9),
          LeaderboardQueryRequest::kStdCompetition));

  // 내 주변 랭킹을 조회하기 위한 요청자를 만들고 vector 에 추가합니다.
  requests.push_back(
      LeaderboardQueryRequest(
          kPlayerGameScore,
          service_provider,
          player_id,
          kWeekly,
          LeaderboardRange(LeaderboardRange::kNearBy, -5, 5),
          LeaderboardQueryRequest::kDense));

  // 내 랭킹을 조회하기 위한 요청자를 만들고 vector 에 추가합니다.
  requests.push_back(
      LeaderboardQueryRequest(
          kPlayerGameScore,
          service_provider,
          player_id,
          kWeekly,
          LeaderboardRange(LeaderboardRange::kNearBy, 0, 0));

  // 친구 목록은 가져왔다고 가정하겠습니다.
  PlayerAccountVector friend_list = ...;

  requests.push_back(
      LeaderboardQueryRequest(
          kPlayerGameScore,
          service_provider,
          player_id,
          friend_list,
          kWeekly);

  // 랭킹을 조회합니다.
  LeaderboardQueryResponseHandler2 handler = OnResponse;
  GetLeaderboard(requests, handler);
}
 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
static string kPlayerGameScore = "player_game";

public static void OnResponse(
    List<Leaderboard.QueryRequest> requests,
    List<Leaderboard.QueryResponse> responses,
    bool error)
{
  if (error) {
    return;
  }

  foreach (Leaderboard.QueryResponse response in responses)
  {
    foreach (Leaderboard.Record record in response.Records)
    {
      Log.Info ("rank={0}, percentage={1}, score={2}, id={3}",
                record.Rank, record.Percentage,
                record.Score, record.PlayerAccount.Id);
    }
  }
}

public static void example()
{
  string service_provider = "Facebook"; // service provider
  string player_id = "testuser";        // player id in service provider

  List<Leaderboard.QueryRequest> requests = new List<Leaderboard.QueryRequest> ();

  // 우선 TOP 10 랭킹을 조회하기 위한 요청자를 만들고 vector 에 추가합니다.
  requests.Add (new Leaderboard.QueryRequest(
      kPlayerGameScore,
      Leaderboard.Timespan.kWeekly,
      // 전체 랭킹을 조회합니다.
      new Leaderboard.Range (Leaderboard.RangeType.kFromTop, 0, 9),
      // 1224 랭킹 타입으로 동점자 순위를 처리합니다.
      Leaderboard.RankingType.kStdCompetition));

  // 내 주변 랭킹을 조회하기 위한 요청자를 만들고 vector 에 추가합니다.
  requests.Add (new Leaderboard.QueryRequest(
      kPlayerGameScore,
      service_provider,
      player_id,
      Leaderboard.Timespan.kWeekly,
      // 위 아래 5명의 랭킹을 조회합니다.
      new Leaderboard.Range (Leaderboard.RangeType.kNearby, -5, 5),
      // 1223 랭킹 타입으로 동점자 순위를 처리합니다.
      Leaderboard.RankingType.kDense));

  // 내 랭킹을 조회하기 위한 요청자를 만들고 list 에 추가합니다.
  requests.Add (new Leaderboard.QueryRequest(
      kPlayerGameScore,
      service_provider,
      player_id,
      Leaderboard.Timespan.kWeekly,
      // 나의 랭킹을 조회합니다.
      new Leaderboard.Range (Leaderboard.RangeType.kNearby, 0, 0)));

  // 친구 목록은 가져왔다고 가정하겠습니다.
  List<PlayerAccount> friend_list = ...;

  requests.Add (new Leaderboard.QueryRequest(
      kPlayerGameScore,
      service_provider,
      player_id,
      friend_list,
      Leaderboard.Timespan.kWeekly));

  // 랭킹을 조회합니다.
  Leaderboard.GetLeaderboard (requests, OnResponse);
}

24.3. 예제: 점수 갱신하기

점수를 갱신하는 방법에는 총 4가지가 있습니다.

타입 설명
kHighScore 주어진 점수가 최고 점수인 경우에만 갱신합니다.
kIncrement 주어진 점수만큼 현재 점수를 증가시킵니다.
kDecrement 주어진 점수만큼 현재 점수를 감소시킵니다.
kOverwriting 주어진 점수로 현재 점수를 덮어씁니다.

예제를 통해 점수를 갱신하는 방법을 살펴보겠습니다.

24.3.1. 최고 점수인 경우 갱신하기

24.3.1.1. 동기 방식

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const char *kPlayerGameScore = "player_game";

void example() {
  string service_provider = "Facebook";
  string player_id = "testuser";
  double score = 10000;

  // kHighScore 를 입력하여 최고 점수인 경우에만 갱신합니다.
  ScoreSubmissionRequest request(
      kPlayerGameScore,
      service_provider,
      player_id,
      score,
      ScoreSubmissionRequest::kHighScore);

  ScoreSubmissionResponse response;
  if (not SubmitScoreSync(request, &response)) {
    // system error
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  // kHighScore 인 경우 최고 점수 여부와 상관없이 입력한 score 와 동일합니다.
  // kIncrement, kDecrement 인 경우 증가, 감소된 점수입니다.
  // kOverwriting 인 경우 입력한 score 와 동일합니다.
  LOG(INFO) << "new score: " << response.new_score;

  // 결과값에 따라 대응합니다.
  switch (response.result) {
    case kNewRecord: {
      // 신기록이네요.
      break;
    }
    case kNewRecordWeekly: {
      // 주간 최고 기록입니다.
      break;
    }
    case kNewRecordDaily: {
      // 일간 최고 기록입니다.
      break;
    }
    case kNone: {
      // no update
      break;
    }
    default: {
      BOOST_ASSERT(false);
    }
  }
}
 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
static string kPlayerGameScore = "player_game";

public void exmaple()
{
  string service_provider = "Facebook";
  string player_id = "testuser";
  double score = 10000;

  // kHighScore 를 입력하여 최고 점수인 경우에만 갱신합니다.
  Leaderboard.ScoreSubmissionRequest request =
      new Leaderboard.ScoreSubmissionRequest(
          kPlayerGameScore,
          service_provider,
          player_id,
          score,
          Leaderboard.ScoreSubmissionType.kHighScore);

  Leaderboard.ScoreSubmissionResponse response;
  if (!Leaderboard.SubmitScoreSync (request, out response))
  {
    Log.Error ("leaderboard system error");
    return;
  }

  // kHighScore 인 경우 최고 점수 여부와 상관없이 입력한 score 와 동일합니다.
  // kIncrement, kDecrement 인 경우 증가, 감소된 점수입니다.
  // kOverwriting 인 경우 입력한 score 와 동일합니다.
  Log.Info ("new score: {0}", response.NewScore);

  // 결과값에 따라 대응합니다.
  switch (response.Result)
  {
    case Leaderboard.ScoreSubmissionResult.kNewRecord:
    {
      // 신기록이네요.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNewRecordWeekly:
    {
      // 주간 최고 기록입니다.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNewRecordDaily:
    {
      // 일간 최고 기록입니다.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNone:
    {
      // no update
      break;
    }
  }
}

24.3.1.2. 비동기 방식

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const char *kPlayerGameScore = "player_game";

void OnScoreSubmitted(
    const ScoreSubmissionRequest &request,
    const ScoreSubmissionResponse &response,
    const bool &error) {
  if (error) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  // kHighScore 인 경우 최고 점수 여부와 상관없이 입력한 score 와 동일합니다.
  // kIncrement, kDecrement 인 경우 증가, 감소된 점수입니다.
  // kOverwriting 인 경우 입력한 score 와 동일합니다.
  LOG(INFO) << "new score: " << response.new_score;

  // 결과값에 따라 대응합니다.
  switch (response.result) {
    case kNewRecord: {
      // 신기록이네요.
      break;
    }
    case kNewRecordWeekly: {
      // 주간 최고 기록입니다.
      break;
    }
    case kNewRecordDaily: {
      // 일간 최고 기록입니다.
      break;
    }
    case kNone: {
      // no update
      break;
    }
    default: {
      BOOST_ASSERT(false);
    }
  }
}


void example() {
  string service_provider = "Facebook";
  string player_id = "testuser";
  double score = 10000;

  // kHighScore 를 입력하여 최고 점수인 경우에만 갱신합니다.
  ScoreSubmissionRequest request(
      kPlayerGameScore,
      service_provider,
      player_id,
      score,
      ScoreSubmissionRequest::kHighScore);

  ScoreSubmissionResponseHandler handler = OnScoreSubmitted;
  SubmitScore(request, handler);
}
 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
string kPlayerGameScore = "player_game";

public static void OnScoreSubmitted(
    Leaderboard.ScoreSubmissionRequest request,
    Leaderboard.ScoreSubmissionResponse response,
    bool error)
{
  if (error)
  {
    Log.Error ("leaderboard system error");
    return;
  }

  // kHighScore 인 경우 최고 점수 여부와 상관없이 입력한 score 와 동일합니다.
  // kIncrement, kDecrement 인 경우 증가, 감소된 점수입니다.
  // kOverwriting 인 경우 입력한 score 와 동일합니다.
  Log.Info ("new score: {0}", response.NewScore);

  // 결과값에 따라 대응합니다.
  switch (response.Result)
  {
    case Leaderboard.ScoreSubmissionResult.kNewRecord:
    {
      // 신기록이네요.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNewRecordWeekly:
    {
      // 주간 최고 기록입니다.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNewRecordDaily:
    {
      // 일간 최고 기록입니다.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNone:
    {
      // no update
      break;
    }
  }
}

public static void example()
{
  string service_provider = "Facebook";
  string player_id = "testuser";
  double score = 10000;

  // kHighScore 를 입력하여 최고 점수인 경우에만 갱신합니다.
  Leaderboard.ScoreSubmissionRequest request =
      new Leaderboard.ScoreSubmissionRequest(
          kPlayerGameScore,
          service_provider,
          player_id,
          score,
          Leaderboard.ScoreSubmissionType.kHighScore);

  Leaderboard.SubmitScore (request, OnScoresubmitted);
}

24.3.2. 점수 갱신 후 내 랭킹 가져오기

이번에는 점수를 갱신하고 내 랭킹도 같이 가져오는 예제를 살펴보겠습니다.

24.3.2.1. 동기 방식

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const char *kPlayerGameScore = "player_game";

void example() {
  string service_provider = "Facebook";
  string player_id = "testuser";
  double score = 10000;

  // kHighScore 를 입력하여 최고 점수인 경우에만 갱신합니다.
  ScoreSubmissionRequest request(
      kPlayerGameScore,
      service_provider,
      player_id,
      score,
      LeaderboardQueryRequest::kOrdinal,
      kWeekly,
      ScoreSubmissionRequest::kHighScore);

  ScoreSubmissionResponse response;
  if (not SubmitScoreSync(request, &response)) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  // kHighScore 인 경우 최고 점수 여부와 상관없이 입력한 score 와 동일합니다.
  // kIncrement, kDecrement 인 경우 증가, 감소된 점수입니다.
  // kOverwriting 인 경우 입력한 score 와 동일합니다.
  // 내 랭킹 정보도 함께 출력합니다.
  LOG(INFO) << "new score: " << response.new_score
            << ", total player count: " << response.total_player_count
            << ", my rank: " << response.rank
            << ", percentage: " << response.percentage;

  // 결과값에 따라 대응합니다.
  switch (response.result) {
    case kNewRecord: {
      // 신기록이네요.
      break;
    }
    case kNewRecordWeekly: {
      // 주간 최고 기록입니다.
      break;
    }
    case kNewRecordDaily: {
      // 일간 최고 기록입니다.
      break;
    }
    case kNone: {
      // no update
      break;
    }
    default: {
      BOOST_ASSERT(false);
    }
  }
}
 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
static string kPlayerGameScore = "player_game";

public static void example()
{
  string service_provider = "Facebook";
  string player_id = "testuser";
  double score = 10000;

  // kHighScore 를 입력하여 최고 점수인 경우에만 갱신합니다.
  Leaderboard.ScoreSubmissionRequest request =
      new Leaderboard.ScoreSubmissionRequest(
          kPlayerGameScore,
          service_provider,
          player_id,
          score,
      Leaderboard.RankingType.kOrdinal,
      Leaderboard.Timespan.kWeekly,
      Leaderboard.ScoreSubmissionType.kHighScore);

  Leaderboard.ScoreSubmissionResponse response;
  if (!Leaderboard.SubmitScoreSync(request, out response))
  {
    Log.Error ("leaderboard system error");
    return;
  }

  // kHighScore 인 경우 최고 점수 여부와 상관없이 입력한 score 와 동일합니다.
  // kIncrement, kDecrement 인 경우 증가, 감소된 점수입니다.
  // kOverwriting 인 경우 입력한 score 와 동일합니다.
  // 내 랭킹 정보도 함께 출력합니다.
  Log.Info (
      "new score: {0}, total player count: {1}, my rank: {2}, percentage: {3}",
      response.NewScore, response.TotalPlayerCount, response.Rank, response.Percentage);

  // 결과값에 따라 대응합니다.
  switch (response.Result)
  {
    case Leaderboard.ScoreSubmissionResult.kNewRecord:
    {
        // 신기록이네요.
        break;
    }
    case Leaderboard.ScoreSubmissionResult.kNewRecordWeekly:
    {
      // 주간 최고 기록입니다.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNewRecordDaily:
    {
      // 일간 최고 기록입니다.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNone:
    {
      // no update
      break;
    }
  }
}

24.3.2.2. 비동기 방식

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
const char *kPlayerGameScore = "player_game";

void OnScoreSubmitted(
    const ScoreSubmissionRequest &request,
    const ScoreSubmissionResponse &response,
    const bool &error) {
  if (error) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  // kHighScore 인 경우 최고 점수 여부와 상관없이 입력한 score 와 동일합니다.
  // kIncrement, kDecrement 인 경우 증가, 감소된 점수입니다.
  // kOverwriting 인 경우 입력한 score 와 동일합니다.
  // 내 랭킹 정보도 함께 출력합니다.
  LOG(INFO) << "new score: " << response.new_score
            << ", total player count: " << response.total_player_count
            << ", my rank: " << response.rank
            << ", percentage: " << response.percentage;

  // 결과값에 따라 대응합니다.
  switch (response.result) {
    case kNewRecord: {
      // 신기록이네요.
      break;
    }
    case kNewRecordWeekly: {
      // 주간 최고 기록입니다.
      break;
    }
    case kNewRecordDaily: {
      // 일간 최고 기록입니다.
      break;
    }
    case kNone: {
      // no update
      break;
    }
    default: {
      BOOST_ASSERT(false);
    }
  }
}


void example() {
  string service_provider = "Facebook";
  string player_id = "testuser";
  double score = 10000;

  // kHighScore 를 입력하여 최고 점수인 경우에만 갱신합니다.
  ScoreSubmissionRequest request(
      kPlayerGameScore,
      service_provider,
      player_id,
      score,
      LeaderboardQueryRequest::kOrdinal,
      kWeekly,
      ScoreSubmissionRequest::kHighScore);

  ScoreSubmissionResponseHandler handler = OnScoreSubmitted;
  SubmitScore(request, handler);
}
 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
static string kPlayerGameScore = "player_game";

public static void OnScoreSubmitted(
    Leaderboard.ScoreSubmissionRequest request,
    Leaderboard.ScoreSubmissionResponse response,
    bool error)
{
  // kHighScore 인 경우 최고 점수 여부와 상관없이 입력한 score 와 동일합니다.
  // kIncrement, kDecrement 인 경우 증가, 감소된 점수입니다.
  // kOverwriting 인 경우 입력한 score 와 동일합니다.
  // 내 랭킹 정보도 함께 출력합니다.
  Log.Info (
      "new score: {0}, total player count: {1}, my rank: {2}, percentage: {3}",
      response.NewScore, response.TotalPlayerCount, response.Rank, response.Percentage);

  // 결과값에 따라 대응합니다.
  switch (response.Result)
  {
    case Leaderboard.ScoreSubmissionResult.kNewRecord:
    {
      // 신기록이네요.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNewRecordWeekly:
    {
      // 주간 최고 기록입니다.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNewRecordDaily:
    {
      // 일간 최고 기록입니다.
      break;
    }
    case Leaderboard.ScoreSubmissionResult.kNone:
    {
      // no update
      break;
    }
  }
}

public static void example()
{
  string service_provider = "Facebook";
  string player_id = "testuser";
  double score = 10000;

  // kHighScore 를 입력하여 최고 점수인 경우에만 갱신합니다.
  Leaderboard.ScoreSubmissionRequest request =
      new Leaderboard.ScoreSubmissionRequest(
          kPlayerGameScore,
          service_provider,
          player_id,
          score,
      Leaderboard.RankingType.kOrdinal,
      Leaderboard.Timespan.kWeekly,
      Leaderboard.ScoreSubmissionType.kHighScore);

  Leaderboard.SubmitScore (request, onScoreSubmitted);
}

24.4. 예제: 랭킹 삭제하기

플레이어 제재나 탈퇴로 인해 랭킹을 삭제하는 경우 DeleteScore() 또는 DeleteScoreSync() 함수를 호출하여 랭킹을 삭제합니다. 이 함수들을 호출하면 AllTime, Daily, Yesterday, Weekly, LastWeek 랭킹 데이터가 모두 삭제처리됩니다.

아래 예제는 두 개의 leaderboard ID 가 있을 때, 각 leaderboard ID 에서 사용자 랭킹 정보를 삭제하는 예제입니다.

Tip

아래 예제는 서버 코드 안에서 랭킹을 삭제하는 방식을 설명하며, 수작업으로 직접 Redis/MySQL 에서 랭킹을 삭제하는 것은 (고급) 랭킹 데이터 직접 삭제/복구 를 참고하세요.

24.4.1. 동기 방식

 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
27
28
29
30
31
const char *kPlayerGameScore = "player_game";
const char *kGuildGameScore = "guild_game";

void example() {
  string service_provider = "Facebook";
  string player_id = "testuser";

  ScoreDeletionRequestVector requests;

  requests.push_back(
      ScoreDeletionRequest(
          kPlayerGameScore,
          service_provider,
          player_id));

  requests.push_back(
      ScoreDeletionRequest(
          kGuildGameScore,
          service_provider,
          player_id));

  ScoreDeletionResponseVector responses;
  if (not DeleteScoreSync(requests, &responses)) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  BOOST_FOREACH(const ScoreDeletionResponse &response, responses) {
    LOG(INFO) << "result: " << response.result;
  }
}
 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
27
28
29
30
31
32
33
34
35
string kPlayerGameScore = "player_game";
string kGuildGameScore = "guild_game";

public static void example()
{
  string service_provider = "Facebook";
  string player_id = "testuser";

  List<Leaderboard.ScoreDeletionRequest> requests =
      new List<Leaderboard.ScoreDeletionRequest> ();

  requests.Add(
      new Leaderboard.ScoreDeletionRequest (
          kPlayerGameScore,
          service_provider,
          player_id));

  requests.Add(
      new Leaderboard.ScoreDeletionRequest (
          kGuildGameScore,
          service_provider,
          player_id));

  List<Leaderboard.ScoreDeletionResponse> responses;
  if (!Leaderboard.DeleteScoreSync (requests, out responses))
  {
    Log.Error ("leaderboard system error");
    return;
  }

  foreach (Leaderboard.ScoreDeletionResponse response in responses)
  {
    Log.Info ("result: {0}", response.Result);
  }
}

24.4.2. 비동기 방식

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const char *kPlayerGameScore = "player_game";
const char *kGuildGameScore = "guild_game";


void OnScoreDeleted(
    const ScoreDeletionRequestVector &requests,
    const ScoreDeletionResponseVector &responses,
    const bool &error) {
  if (error) {
    LOG(ERROR) << "leaderboard system error";
    return;
  }

  BOOST_FOREACH(const ScoreDeletionResponse &response, responses) {
    LOG(INFO) << "result: " << response.result;
  }
}


void example() {
  string service_provider = "Facebook";
  string player_id = "testuser";

  ScoreDeletionRequestVector requests;

  requests.push_back(
      ScoreDeletionRequest(
          kPlayerGameScore,
          service_provider,
          player_id));

  requests.push_back(
      ScoreDeletionRequest(
          kGuildGameScore,
          service_provider,
          player_id));

  ScoreDeletionResponseHandler handler = OnScoreDeleted;
  DeleteScore(requests, handler);
}
 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
string kPlayerGameScore = "player_game";
string kGuildGameScore = "guild_game";

public static void OnScoreDeleted(
    List<Leaderboard.ScoreDeletionRequest> requests,
    List<Leaderboard.ScoreDeletionResponse> responses,
    bool error)
{
  if (error)
  {
    Log.Error ("leaderboard system error");
    return;
  }

  foreach (Leaderboard.ScoreDeletionResponse response in responses)
  {
    Log.Info ("result: {0}", response.Result);
  }
}

public static void example()
{
  string service_provider = "Facebook";
  string player_id = "testuser";

  List<Leaderboard.ScoreDeletionRequest> requests =
      new List<Leaderboard.ScoreDeletionRequest> ();

  requests.Add (
      new Leaderboard.ScoreDeletionRequest (
          kPlayerGameScore,
          service_provider,
          player_id));

  requests.Add (
      new Leaderboard.ScoreDeletionRequest (
          kGuildGameScore,
          service_provider,
          player_id));

  Leaderboard.DeleteScore (requests, OnScoreDeleted);
}

24.5. 예제: 랭킹 리셋 스케쥴 알아내기

게임 서버는 iFun Leaderboard 와 연결되면 reset_schedules 정보를 자동으로 가져옵니다. 따라서 캐싱된 정보를 반환하는 동기 버전의 함수만 존재할 뿐 비동기 버전의 함수는 존재하지 않습니다.

GetLeaderboardResetSchedule() 함수를 통해 리셋 스케쥴 등록하기 에서 등록한 랭킹 리셋 스케쥴 정보를 얻어올 수 있습니다.

이 함수는 모든 리셋 스케쥴 정보를 얻어오는 버전과 특정 리셋 스케쥴 정보를 가져오는 버전이 있습니다. 어떠한 경우든 iFun Leaderboard 의 MANIFEST.json 상에 reset_schedules 가 없거나, 그 정보가 잘못되어 있다면 false 를 반환합니다.

전체 스케쥴 반환 버전:

bool GetLeaderboardResetSchedule(
    LeaderboardResetScheduleVector *reset_schedules,
    const string &tag = "");

특정 리셋 스케쥴 정보를 가져오는 버전:

// leaderboard_id 와 period 에 맞는 LeaderboardResetSchedule 을 얻어옵니다.
bool GetLeaderboardResetSchedule(const string &leaderboard_id,
                                 const LeaderboardResetSchedule::Period &period,
                                 LeaderboardResetSchedule *reset_schedule,
                                 const string &tag = "");

사용 예제:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
void Example() {
  LeaderboardResetScheduleVector reset_schedules;

  if (not GetLeaderboardResetSchedule(&reset_schedules)) {
    // Failed...
    return;
  }

  BOOST_FOREACH(const LeaderboardResetSchedule &reset_schedule, reset_schedules) {
    LOG(INFO) << "leaderboard id: " << reset_schedule.leaderboard_id
              << ", period: " << reset_schedule.period
              << ", interval: " << reset_schedule.interval
              << ", starts: " << reset_schedule.starts
              << ", end: " << reset_schedule.ends
              << ", latest reset date time: " << reset_schedule.latest_reset_date_time
              << ", upcoming date time" << reset_schedule.upcoming_date_time
              << ", upcoming time: " << reset_schedule.upcoming_time
              << ", next date time" << reset_schedule.next_date_time
              << ", next time: " << reset_schedule.next_time
              << ", expired: " << reset_schedule.expired;
  }
}

Leaderboard.GetResetSchedule() 함수를 통해 리셋 스케쥴 등록하기 에서 등록한 랭킹 리셋 스케쥴 정보를 얻어올 수 있습니다.

이 함수는 모든 리셋 스케쥴 정보를 얻어오는 버전과 특정 리셋 스케쥴 정보를 가져오는 버전이 있습니다. 어떠한 경우든 iFun Leaderboard 의 MANIFEST.json 상에 reset_schedules 가 없거나, 그 정보가 잘못되어 있다면 false 를 반환합니다.

전체 스케쥴 반환 버전:

bool GetResetSchedule(out List<Leaderboard.ResetSchedule> reset_schedules,
                      string tag = "");

특정 리셋 스케쥴 정보를 가져오는 버전:

// leaderboard_id 와 period 에 맞는 Leaderboard.ResetSchedule 을 얻어옵니다.
bool GetResetSchedule(string leaderboard_id,
                      Leaderboard.ResetSchedulePeriod period,
                      out Leaderboard.ResetSchedule reset_schedule,
                      string tag = "");

사용 예제:

 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
27
28
void Example()
{
  List<Leaderboard.ResetSchedule> reset_schedules;

  if (!Leaderboard.GetResetSchedule(out reset_schedules)) {
    // Failed...
    return;
  }

  foreach (Leaderboard.ResetSchedule reset_schedule in reset_schedules)
  {
    Log.Info("leaderboard_id: {0}, period: {1}, interval: {2}" +
             ", starts: {3}, end: {4}, latest reset date time: {5}" +
             ", upcoming date time: {6}, upcoming time: {7}" +
             ", next date time: {8}, next time: {9}, expired: {10}",
             reset_schedule.LeaderboardId,
             reset_schedule.ResetSchedulePeriod,
             reset_schedule.Interval,
             reset_schedule.Starts,
             reset_schedule.Ends,
             reset_schedule.LatestResetDateTime,
             reset_schedule.UpcomingDateTime,
             reset_schedule.UpcomingTime,
             reset_schedule.NextDateTime,
             reset_schedule.NextTime,
             reset_schedule.Expired);
  }
}

24.6. 예제: 랭킹 리셋 여부 확인하기

Leaderboard 가 리셋되었는지 확인하는 예제를 살펴보겠습니다.

24.6.1. 동기 방식

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void Example() {
    const string leaderboard_id = "player_score";
    LeaderboardResetSchedule reset_schedule;
    bool success = GetLeaderboardResetSchedule(leaderboard_id,
        LeaderboardResetSchedule::kWeek, &reset_schedule);
    if (not success) {
      return;
    }

    LeaderboardResetScheduleStatusQueryRequest request(
        leaderboard_id,
        LeaderboardResetSchedule::kWeek,
        reset_schedule.latest_reset_date_time);

    LeaderboardResetScheduleStatusQueryResponse response;
    success = GetLeaderboardResetScheduleStatusQuerySync(request, &response);
    if (not success) {
      return;
    }

    LOG(INFO) << "result: " << response.result
              << ", is_reset: " << response.is_reset
              << ", reset_date_time: " << response.reset_date_time;
  }
 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
27
public static void Example()
{
  string leaderboard_id = "player_score";
  Leaderboard.ResetSchedule reset_schedule;
  if (!Leaderboard.GetResetSchedule (leaderboard_id,
                                     Leaderboard.ResetSchedulePeriod.kWeek,
                                     out reset_schedule))
  {
    return;
  }

  Leaderboard.ResetScheduleStatusQueryRequest request =
      new Leaderboard.ResetScheduleStatusQueryRequest (
          leaderboard_id,
          Leaderboard.ResetSchedulePeriod.kWeek,
          reset_schedule.LatestResetDateTime);

  Leaderboard.ResetScheduleStatusQueryResponse response;
  if (!Leaderboard.GetResetScheduleStatusQuerySync (request, out response))
  {
    return;
  }

  Log.Info("is_reset: {0}, reset_date_time: {1}",
           response.IsReset,
           response.ResetDateTime);
}

24.6.2. 비동기 방식

 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
27
28
29
30
void OnResponse(const LeaderboardResetScheduleStatusQueryRequest &request,
                const LeaderboardResetScheduleStatusQueryResponse &response,
                const bool &error) {
  if (error) {
    return;
  }

  LOG(INFO) << "result: " << response.result
            << ", is_reset: " << response.is_reset
            << ", reset_date_time: " << response.reset_date_time;
}


void Example() {
    const string leaderboard_id = "player_score";
    LeaderboardResetSchedule reset_schedule;
    bool success = GetLeaderboardResetSchedule(leaderboard_id,
        LeaderboardResetSchedule::kWeek, &reset_schedule);
    if (not success) {
      return;
    }

    LeaderboardResetScheduleStatusQueryRequest request(
        leaderboard_id,
        LeaderboardResetSchedule::kWeek,
        reset_schedule.latest_reset_date_time);

    LeaderboardResetScheduleStatusQueryResponseHandler handler = OnResponse;
    GetLeaderboardResetScheduleStatusQuery(request, handler);
  }
 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
27
28
29
30
31
32
33
34
public static void OnResetScheduleStatusReceived(
    Leaderboard.ResetScheduleStatusQueryRequest request,
    Leaderboard.ResetScheduleStatusQueryResponse response,
    bool error)
{
  if (error) {
    return;
  }
  Log.Info("is_reset: {0}, reset_date_time: {1}",
           response.IsReset,
           response.ResetDateTime);
}


public static void Example()
{
  string leaderboard_id = "player_score";
  Leaderboard.ResetSchedule reset_schedule;
  if (!Leaderboard.GetResetSchedule (leaderboard_id,
                                     Leaderboard.ResetSchedulePeriod.kWeek,
                                     out reset_schedule))
  {
    return;
  }

  Leaderboard.ResetScheduleStatusQueryRequest request =
      new Leaderboard.ResetScheduleStatusQueryRequest (
          leaderboard_id,
          Leaderboard.ResetSchedulePeriod.kWeek,
          reset_schedule.LatestResetDateTime);

  Leaderboard.GetResetScheduleStatusQuery (
      request, OnResetScheduleStatusReceived);
}

24.7. 예제: 다수의 iFun Leaderboard 를 이용해 Leaderboard 요청 분산시키기

게임 서버 측 Leaderboard 설정 파라미터 를 통해서 입력한 여러 리더보드 서버를 tag 를 이용하면 특정 리더보드 에이전트를 지정하여 랭킹 처리를 요청할 수 있습니다.

아래 예제에서는 길드랭킹을 처리하는 리더보드 에이전트와 유저랭킹을 처리하는 리더보드 에이전트를 별도로 구성하여 총 2개의 독립된 리더보드 에이전트를 사용합니다.

MANIFEST.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  ...
  "dependency": {
    ...

    "LeaderboardClient": {
      "use_leaderboard" : true,
      "leaderboard_agents": {
        "guild" : {
          "address": "192.168.0.50:12820"
        },
        "user": {
          "address": "192.168.0.100:12820"
        }
      }
    }
  }
  ...
}

Note

만약 tag 를 지정할 필요가 없다면 “”(빈 문자열) 으로 입력해도 됩니다. 모든 함수의 tag parameter 의 기본값으로 “”(빈 문자열) 이 입력되어 있습니다. 따라서 함수에 tag 를 입력하지 않아도 리더보드 에이전트와 통신할 수 있습니다. 즉, “”(빈 문자열) 도 tag 가 되는 형태로 작동하게 됩니다.

길드 랭킹 요청:

이제 길드랭킹을 처리하는 리더보드 에이전트에게 Top 100 을 요청해보겠습니다. 간단한 예제 설명을 위해 동기 API 를 이용하겠습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const string kGuildTag("guild");

void GetGuildRanking() {
  LeaderboardQueryRequest request("guild_ranking", kWeekly,
      LeaderboardRange(LeaderboardRange::kFromTop, 0, 99));

  LeaderboardQueryResponse response;
  if (not GetLeaderboardSync(request, &response, kGuildTag)) {
    return;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
static string kGuildTag = "guild";

void GetGuildRanking()
{
  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
      "guild_ranking",
      Leaderboard.Timespan.kWeekly,
      new Leaderboard.Range(Leaderboard.RangeType.kFromTop, 0, 99));

  Leaderboard.QueryResponse response;
  if (!Leaderboard.GetLeaderboardSync(request, out response, kGuildTag))
  {
    return;
  }
}

유저 랭킹 요청:

이번에는 유저랭킹을 처리하는 리더보드 에이전트에게 Top 100 을 요청해보겠습니다. 간단한 예제 설명을 위해 동기 API 를 이용하겠습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const string kUserTag("user");

void GetUserRanking() {
  LeaderboardQueryRequest request("user_ranking", kWeekly,
      LeaderboardRange(LeaderboardRange::kFromTop, 0, 99));

  LeaderboardQueryResponse response;
  if (not GetLeaderboardSync(request, &response, kUserTag)) {
    return;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
static string kUserTag = "user";

void GetUserRanking()
{
  Leaderboard.QueryRequest request = new Leaderboard.QueryRequest(
      "user_ranking",
      Leaderboard.Timespan.kWeekly,
      new Leaderboard.Range(Leaderboard.RangeType.kFromTop, 0, 99));

  Leaderboard.QueryResponse response;
  if (!Leaderboard.GetLeaderboardSync(request, out response, kUserTag))
  {
    return;
  }
}

24.8. 게임 서버 측 Leaderboard 설정 파라미터

Note

이 설정 파라미터는 iFun Leaderboard 를 사용하는 아이펀 엔진 게임 서버의 값입니다. iFun Leaderboard 자체의 설정 파라미터는 iFun Leaderboard 설정 방법 (MANIFEST.json) 를 참고하세요.

MANIFEST.JSON File 과 아래 설명을 참고하여 리더보드 클라이언트의 Component 를 설정합니다.

  • use_leaderboard: iFun Leaderboard agent 와의 통신을 활성화시킬지 여부. false 로 지정할 경우 모든 랭킹 관련된 요청을 dummy 값으로 응답하는 테스트모드로 작동함. (type=bool, default=false)

  • leaderboard_agents: 접속하려는 리더보드 에이전트들의 연결 정보를 다음과 같은 format 으로 입력함.

    "leaderboard_agents": {
      // iFun Leaderboard agent를 tag 로 구분해서 각각의 접속 정보를 입력합니다.
      "<tag>": {
        // iFun Leaderboard 의 MANIFEST.json 에 기술된 server_tcp_port 를 입력합니다.
        "address": "127.0.0.1:12820"
      },
      ...
    }
    

    Note

    tag 를 이용하면 특정 iFun Leaderboard agent 에게 랭킹 처리를 요청할 수 있습니다. 자세한 내용은 예제: 다수의 iFun Leaderboard 를 이용해 Leaderboard 요청 분산시키기 를 참고하세요.

    특별히 tag 를 입력하지 않을 경우 “” (빈 문자열) 을 입력할 수 있습니다. tag 는 “” 을 포함하여 중복될 수 없습니다.