> For the complete documentation index, see [llms.txt](/llms.txt)

# GS2-SeasonRating SDK for Game Engine API 레퍼런스

게임 엔진용 GS2-SeasonRating SDK의 모델 사양과 API 레퍼런스



## 모델

### EzBallot

투표용지<br>

특정 매치 세션에 대해 각 플레이어에게 발급되는 투표권입니다.<br>
대전 컨텍스트(시즌, 세션, 참가 인원 수)와 플레이어의 사용자 ID를 포함합니다.<br>
투표용지는 서버에 의해 서명되어 플레이어에게 반환되며, 플레이어는 게임 결과를 기입하여 WrittenBallot으로 다시 제출합니다.<br>
투표 집계 시, 모든 참가자의 투표용지를 다수결로 비교하여 결과 조작을 방지합니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| userId | string |  | ✓ |  |  ~ 128자 | 사용자ID |
| seasonName | string |  | ✓ |  |  ~ 128자 | 시즌 이름<br>이 대전의 레이팅 계산에 사용되는 시즌 모델의 이름.<br>이 대전에 적용되는 티어 구조와 포인트 변동 규칙을 정의하는 SeasonModel을 참조합니다. |
| sessionName | string |  | ✓ |  |  ~ 128자 | 세션 이름<br>이 투표용지가 속한 매치 세션의 이름.<br>동일한 세션 내의 모든 투표용지는 투표 프로세스에서 함께 집계됩니다. |
| numberOfPlayer | int |  | ✓ |  | 2 ~ 10 | 참가 인원 수<br>이 대전의 총 참가자 수.<br>투표 완료 여부 판정에 사용됩니다. 결과 확정에는 전체 투표용지의 수집(또는 과반수 성립)이 필요합니다.<br>유효 범위: 2~10. |

**관련 메서드:**
createVote - 대전 결과 보고용 서명된 투표 용지를 작성한다
vote - 자신의 투표용지를 제출하여 대전 결과에 투표합니다 (개별 투표)
voteMultiple - 모든 투표용지를 모아 대전 결과를 한 번에 제출합니다 (즉시 결과 반영)


---

### EzSeasonModel

시즌 모델<br>

시즌 기간 동안 적용되는 티어 구조와 포인트 변동 규칙을 정의하는 마스터 데이터입니다.<br>
각 티어별 포인트 변동 범위·참가료·랭크업 보너스를 설정하고, 포인트 관리에 사용할 Experience 모델을 지정합니다.<br>
실제 포인트 및 티어의 사용자 데이터는 GS2-Experience에 의해 관리됩니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| seasonModelId | string |  | ※ |  |  ~ 1024자 | 시즌 모델GRN<br>※ 서버가 자동으로 설정 |
| name | string |  | ✓ |  |  ~ 128자 | 시즌 모델 이름<br>시즌 모델 고유의 이름. 영숫자와 -(하이픈) _(밑줄) .(마침표)로 지정합니다. |
| metadata | string |  |  |  |  ~ 128자 | 메타데이터<br>메타데이터에는 임의의 값을 설정할 수 있습니다.<br>이 값들은 GS2의 동작에는 영향을 주지 않으므로, 게임 내에서 사용하는 정보를 저장하는 용도로 사용할 수 있습니다. |
| tiers | [List&lt;EzTierModel&gt;](#eztiermodel) |  | ✓ |  | 1 ~ 100 items | 티어 모델 목록<br>시즌의 랭킹 사다리를 구성하는 티어 정의의 순서가 있는 목록.<br>각 티어는 고유한 포인트 변동 규칙(참가료, 최소/최대 변동량, 승급 보너스)을 정의합니다.<br>플레이어는 GS2-Experience에서 관리되는 누적 포인트에 따라 티어를 진행합니다.<br>최소 1개 티어, 최대 100개 티어. |
| experienceModelId | string |  | ✓ |  |  ~ 1024자 | 경험치 모델 ID<br>시즌 포인트와 티어 진행 관리에 사용되는 GS2-Experience 경험치 모델의 GRN.<br>경험치 모델의 랭크 임계값이 티어 경계를 결정하며, 경험치 값은 플레이어의 현재 시즌 포인트를 나타냅니다.<br>대전 결과에 따른 포인트 변동은 이 경험치 모델에 적용됩니다. |

**관련 메서드:**
getSeasonModel - 특정 시즌 레이팅 정의의 상세 정보를 가져오기
listSeasonModels - 시즌 레이팅 정의의 목록을 가져오기


---

### EzTierModel

티어 모델<br>

티어 모델은 시즌 내 각 티어의 포인트 변동 규칙을 정의하는 모델입니다.<br>
순위에 따른 포인트 변동 범위, 참가료, 승급 시 보너스를 설정합니다.<br>
포인트의 실제 데이터는 GS2-Experience에 의해 관리되지만, 그 증감 로직은 TierModel의 설정에 따라 결정됩니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| metadata | string |  |  |  |  ~ 128자 | 메타데이터<br>메타데이터에는 임의의 값을 설정할 수 있습니다.<br>이 값들은 GS2의 동작에는 영향을 주지 않으므로, 게임 내에서 사용하는 정보를 저장하는 용도로 사용할 수 있습니다. |
| raiseRankBonus | int |  | ✓ |  | 0 ~ 10000 | 랭크 승급 보너스<br>플레이어가 이 티어로 승급했을 때 가산되는 보너스 포인트로, 즉각적인 강등을 방지하는 완충 역할을 합니다.<br>예를 들어 100으로 설정하면, 승급 기준점보다 100포인트 많은 상태로 새 티어를 시작합니다.<br>유효 범위: 0~10000. |
| minimumChangePoint | int |  | ✓ |  | -99999999 ~ -1 | 최소 변동 포인트<br>한 번의 대전 결과로 발생할 수 있는 최소(가장 음수인) 포인트 변동량으로, 일반적으로 최악의 패배 시 값을 나타냅니다.<br>음수 값이어야 합니다. 패배한 플레이어의 실제 포인트 변동은 이 값과 0 사이가 됩니다.<br>유효 범위: -99999999~-1. |
| maximumChangePoint | int |  | ✓ |  | 1 ~ 99999999 | 최대 변동 포인트<br>한 번의 대전 결과로 발생할 수 있는 최대(가장 양수인) 포인트 변동량으로, 일반적으로 최고의 승리 시 값을 나타냅니다.<br>양수 값이어야 합니다. 승리한 플레이어의 실제 포인트 변동은 0과 이 값 사이가 됩니다.<br>유효 범위: 1~99999999. |


**관련 모델:**
EzSeasonModel - 시즌 모델



---

### EzGameResult

대전 결과<br>

대전에서 한 플레이어의 결과를 나타내는 모델입니다.<br>
플레이어의 사용자 ID와 순위를 보유합니다. WrittenBallot 내에서 모든 참가자의 결과를 보고하는 데 사용됩니다.<br>
투표 집계 시, 제출된 모든 투표용지의 대전 결과를 다수결로 비교하여 공식 결과를 결정합니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| rank | int |  | ✓ |  | 0 ~ 2147483646 | 순위<br>대전에서 이 플레이어의 최종 순위.<br>1은 1위(승자)를 나타냅니다. 순위 값은 TierModel의 설정에 따라 포인트 변동량을 결정하는 데 사용됩니다. |
| userId | string |  | ✓ |  |  ~ 128자 | 사용자ID |

**관련 메서드:**
vote - 자신의 투표용지를 제출하여 대전 결과에 투표합니다 (개별 투표)
voteMultiple - 모든 투표용지를 모아 대전 결과를 한 번에 제출합니다 (즉시 결과 반영)


---

### EzSignedBallot

서명된 투표용지<br>

GS2-Key를 사용하여 서버가 암호 서명한 투표용지입니다.<br>
서명을 통해 투표용지의 내용(시즌, 세션, 플레이어, 참가 인원 수)이 변조되지 않았음을 보장합니다.<br>
투표 제출 시, 서버 측에서 서명된 투표용지를 검증한 후 게임 결과가 수락됩니다.

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| body | string |  | ✓ |  |  ~ 1024자 | 서명 대상 데이터<br>서명 대상이 되는 투표용지 데이터의 직렬화된 JSON 표현.<br>투표용지의 내용(사용자 ID, 시즌 이름, 세션 이름, 참가 인원 수)을 서명과 대조할 수 있는 형식으로 포함합니다.<br>최대 1024자. |
| signature | string |  | ✓ |  |  ~ 256자 | 서명<br>투표용지 본문에 대해 GS2-Key가 생성한 암호 서명.<br>투표용지가 서버에 의해 발급되었으며 클라이언트에 의해 변경되지 않았음을 검증하는 데 사용됩니다.<br>Base64 인코딩, 최대 256자. |

**관련 메서드:**
voteMultiple - 모든 투표용지를 모아 대전 결과를 한 번에 제출합니다 (즉시 결과 반영)


---

### EzVerifyActionResult

검증 액션 실행 결과

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| action | 문자열 열거형<br>enum {<br>}<br> |  | ✓ |  |  | 검증 액션에서 실행할 액션의 종류 |
| verifyRequest | string |  | ✓ |  |  ~ 524288자 | 액션 실행 시 사용되는 요청의 JSON 문자열 |
| statusCode | int |  |  |  | 0 ~ 999 | 상태 코드 |
| verifyResult | string |  |  |  |  ~ 1048576자 | 결과 내용 |


**관련 모델:**
EzTransactionResult - 트랜잭션 실행 결과



---

### EzConsumeActionResult

소비 액션 실행 결과

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| action | 문자열 열거형<br>enum {<br>}<br> |  | ✓ |  |  | 소비 액션에서 실행할 액션의 종류 |
| consumeRequest | string |  | ✓ |  |  ~ 524288자 | 액션 실행 시 사용되는 요청의 JSON 문자열 |
| statusCode | int |  |  |  | 0 ~ 999 | 상태 코드 |
| consumeResult | string |  |  |  |  ~ 1048576자 | 결과 내용 |


**관련 모델:**
EzTransactionResult - 트랜잭션 실행 결과



---

### EzAcquireActionResult

획득 액션 실행 결과

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| action | 문자열 열거형<br>enum {<br>}<br> |  | ✓ |  |  | 입수 액션에서 실행할 액션의 종류 |
| acquireRequest | string |  | ✓ |  |  ~ 524288자 | 액션 실행 시 사용되는 요청의 JSON 문자열 |
| statusCode | int |  |  |  | 0 ~ 999 | 상태 코드 |
| acquireResult | string |  |  |  |  ~ 1048576자 | 결과 내용 |


**관련 모델:**
EzTransactionResult - 트랜잭션 실행 결과



---

### EzTransactionResult

트랜잭션 실행 결과<br>

서버 사이드에서 트랜잭션 자동 실행 기능을 이용하여 실행된 트랜잭션의 실행 결과

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| transactionId | string |  | ✓ |  | 36 ~ 36자 | 트랜잭션 ID |
| verifyResults | [List&lt;EzVerifyActionResult&gt;](#ezverifyactionresult) |  |  |  | 0 ~ 10 items | 검증 액션의 실행 결과 목록 |
| consumeResults | [List&lt;EzConsumeActionResult&gt;](#ezconsumeactionresult) |  |  | [] | 0 ~ 10 items | 소비 액션의 실행 결과 목록 |
| acquireResults | [List&lt;EzAcquireActionResult&gt;](#ezacquireactionresult) |  |  | [] | 0 ~ 100 items | 획득 액션 실행 결과 리스트 |


---

## 메서드

### getSeasonModel

특정 시즌 레이팅 정의의 상세 정보를 가져오기<br>

시즌 이름을 지정하여, 티어 설정 및 규칙을 포함한 상세 정보를 가져옵니다.<br>

응답에는 다음이 포함됩니다:<br>
- 티어 정의: 레이팅 티어 목록(예: 브론즈 0포인트, 실버 1000포인트, 골드 2500포인트)과 랭크업 시 부여되는 보너스 포인트<br>
- 경험치 모델 참조: 레이팅 값의 저장 및 관리에 사용되는 GS2-Experience 모델<br>
- 도전 기간 이벤트: GS2-Schedule 이벤트와 연계되어 있는 경우, 경쟁 시즌이 유효하여 레이팅 대전에 참가할 수 있는 기간<br>

랭크전 화면에서 시즌의 상세 정보를 표시할 때 사용합니다. 예를 들어 플레이어의 현재 티어, 다음 티어까지 필요한 포인트, 시즌이 현재 활성 상태인지 여부를 표시할 수 있습니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| seasonName | string |  | ✓|  |  ~ 128자 | 시즌 모델 이름<br>시즌 모델 고유의 이름. 영숫자와 -(하이픈) _(밑줄) .(마침표)로 지정합니다. |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzSeasonModel](#ezseasonmodel) | 시즌 모델|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).SeasonModel(
        seasonName: "mode1"
    );
    var item = await domain.ModelAsync();

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).SeasonModel(
        seasonName: "mode1"
    );
    var future = domain.ModelFuture();
    yield return future;
    var item = future.Result;

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    )->SeasonModel(
        "mode1" // seasonName
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

```

**Godot**
```gdscript

var domain = ez.season_rating.namespace_(
        "namespace-0001"
    ).season_model(
        "mode1"
    )

var async_result = await domain.model()
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

```


##### 값 변경 이벤트 핸들링




**Unity (UniTask)**
```csharp
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).SeasonModel(
        seasonName: "mode1"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    domain.Unsubscribe(callbackId);

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).SeasonModel(
        seasonName: "mode1"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    domain.Unsubscribe(callbackId);

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    )->SeasonModel(
        "mode1" // seasonName
    );
    
    // 이벤트 핸들링 시작
    const auto CallbackId = Domain->Subscribe(
        [](TSharedPtr<Gs2::SeasonRating::Model::FSeasonModel> value) {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    Domain->Unsubscribe(CallbackId);

```

**Godot**
```gdscript

var domain = ez.season_rating.namespace_(
        "namespace-0001"
    ).season_model(
        "mode1"
    )

# 이벤트 핸들링 시작
var callback_id = domain.subscribe_model(func(value):
    # 값이 변화했을 때 호출됨
    # value에는 변경 후의 값이 전달됩니다
    pass
)

# 이벤트 핸들링 정지
domain.unsubscribe_model(callback_id)

```


**⚠️ Warning**

이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.

로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.

따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.

---

### listSeasonModels

시즌 레이팅 정의의 목록을 가져오기<br>

네임스페이스에 정의된 모든 시즌 모델을 가져옵니다.<br>
각 시즌 모델은 독립된 경쟁 레이팅 시스템을 나타냅니다. 예를 들어 "랭크 배틀 시즌 1", "아레나 레이팅", "토너먼트 레이팅" 등입니다.<br>

시즌 레이팅은 플레이어가 대전을 진행하고, 승패에 따라 레이팅이 오르내리는 시스템입니다.<br>
레이팅은 GS2-Experience 를 사용해 저장되므로, 레이팅 값은 경험치처럼 취급됩니다. 증감하며, 랭크업(예: 브론즈 → 실버 → 골드)을 트리거할 수 있습니다.<br>

각 시즌 모델에는 다음이 정의되어 있습니다:<br>
- 레이팅 티어(예: 브론즈, 실버, 골드, 플래티넘)와 승급 임계값<br>
- 레이팅 값을 저장하는 GS2-Experience 모델<br>
- 선택적으로, 경쟁 시즌이 유효한 기간을 제어하는 GS2-Schedule 이벤트(도전 기간)<br>

게임의 랭크전 화면에서 이용 가능한 경쟁 모드 목록을 표시할 때 사용합니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| items | [List&lt;EzSeasonModel&gt;](#ezseasonmodel) | 시즌 모델 목록|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    );
    var items = await domain.SeasonModelsAsync(
    ).ToListAsync();

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    );
    var it = domain.SeasonModels(
    );
    List<EzSeasonModel> items = new List<EzSeasonModel>();
    while (it.HasNext())
    {
        yield return it.Next();
        if (it.Error != null)
        {
            onError.Invoke(it.Error, null);
            break;
        }
        if (it.Current != null)
        {
            items.Add(it.Current);
        }
        else
        {
            break;
        }
    }

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    );
    const auto It = Domain->SeasonModels(
    );
    TArray<Gs2::UE5::SeasonRating::Model::FEzSeasonModelPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

```


##### 값 변경 이벤트 핸들링




**Unity (UniTask)**
```csharp
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.SubscribeSeasonModels(
        () => {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    domain.UnsubscribeSeasonModels(callbackId);

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.SubscribeSeasonModels(
        () => {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    domain.UnsubscribeSeasonModels(callbackId);

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    );
    
    // 이벤트 핸들링 시작
    const auto CallbackId = Domain->SubscribeSeasonModels(
        []() {
            // 리스트의 요소가 변화했을 때 호출됨
        }
    );

    // 이벤트 핸들링 정지
    Domain->UnsubscribeSeasonModels(CallbackId);

```


**⚠️ Warning**

이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.

로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.

따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.

---

### createVote

대전 결과 보고용 서명된 투표 용지를 작성한다<br>

플레이어가 대전 결과를 보고하는 데 사용할 서명된 투표 용지를 생성합니다.<br>
이는 대전 결과 보고 플로우의 첫 번째 단계입니다. 대전에 참가한 각 플레이어는 자신의 투표 용지를 작성해야 합니다.<br>

대전 결과를 보고하는 전체 흐름:<br>
1. 대전이 종료되고 각 플레이어의 게임 클라이언트가 CreateVote 를 호출하여 서명된 투표 용지를 가져옵니다<br>
2. 각 플레이어가 투표 용지와 대전 결과를 Vote 로 전송합니다(개별 투표)<br>
— 또는 승리한 쪽이 모든 투표 용지를 모아 VoteMultiple 로 한 번에 전송합니다(즉시 결과 반영)<br>
3. 시스템이 투표 용지를 검증하고, 다수결로 결과를 결정하며, 각 플레이어의 레이팅을 갱신합니다<br>

파라미터:<br>
- seasonName: 이 대전이 어느 시즌 레이팅 시스템에 속하는지(예: "ranked-battle-season-1")<br>
- sessionName: 이 특정 대전을 식별하는 고유 ID(같은 대전의 모든 플레이어가 공유)<br>
- numberOfPlayer: 대전에 참가한 플레이어 수(2~10명)<br>
- keyId: 투표 용지 서명에 사용하는 암호화 키(변조 방지)<br>

반환되는 투표 용지에는 바디(데이터)와 시그니처(변조되지 않았다는 증명)가 포함됩니다. 투표 시에는 둘 다 필요합니다.

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| seasonName | string |  | ✓|  |  ~ 128자 | 시즌 모델 이름<br>시즌 모델 고유의 이름. 영숫자와 -(하이픈) _(밑줄) .(마침표)로 지정합니다. |
| sessionName | string |  | ✓| UUID |  ~ 128자 | 세션 이름<br>세션 고유의 이름. 영숫자와 -(하이픈) _(밑줄) .(마침표)로 지정합니다. |
| gameSession | GameSession | | ✓|  |  | GameSession |
| numberOfPlayer | int |  | ✓|  | 2 ~ 10 | 참가 인원 |
| keyId | string |  | | "grn:gs2:{region}:{ownerId}:key:default:key:default" |  ~ 1024자 | 암호화 키 GRN |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzBallot](#ezballot) | 투표용지|
| body | string | 서명 대상 데이터|
| signature | string | 서명 데이터|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Ballot(
        seasonName: "season-0001",
        sessionName: "gathering-0001",
        numberOfPlayer: 4,
        keyId: "key-0001"
    );
    var item = await domain.ModelAsync();

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Ballot(
        seasonName: "season-0001",
        sessionName: "gathering-0001",
        numberOfPlayer: 4,
        keyId: "key-0001"
    );
    var future = domain.ModelFuture();
    yield return future;
    var item = future.Result;

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->Ballot(
        "season-0001", // seasonName
        "gathering-0001", // sessionName
        4, // numberOfPlayer
        "key-0001" // keyId
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

```

**Godot**
```gdscript

var domain = ez.season_rating.namespace_(
        "namespace-0001"
    ).me(game_session).ballot(
        "season-0001",
        "gathering-0001",
        4,
        "key-0001"
    )

var async_result = await domain.model()
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

```


##### 값 변경 이벤트 핸들링




**Unity (UniTask)**
```csharp
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Ballot(
        seasonName: "season-0001",
        sessionName: "gathering-0001",
        numberOfPlayer: 4,
        keyId: "key-0001"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    domain.Unsubscribe(callbackId);

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Ballot(
        seasonName: "season-0001",
        sessionName: "gathering-0001",
        numberOfPlayer: 4,
        keyId: "key-0001"
    );
    
    // 이벤트 핸들링 시작
    var callbackId = domain.Subscribe(
        value => {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    domain.Unsubscribe(callbackId);

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->Ballot(
        "season-0001", // seasonName
        "gathering-0001", // sessionName
        4, // numberOfPlayer
        "key-0001" // keyId
    );
    
    // 이벤트 핸들링 시작
    const auto CallbackId = Domain->Subscribe(
        [](TSharedPtr<Gs2::SeasonRating::Model::FBallot> value) {
            // 값이 변화했을 때 호출됨
            // value에는 변경 후의 값이 전달됨
        }
    );

    // 이벤트 핸들링 정지
    Domain->Unsubscribe(CallbackId);

```

**Godot**
```gdscript

var domain = ez.season_rating.namespace_(
        "namespace-0001"
    ).me(game_session).ballot(
        "season-0001",
        "gathering-0001",
        4,
        "key-0001"
    )

# 이벤트 핸들링 시작
var callback_id = domain.subscribe_model(func(value):
    # 값이 변화했을 때 호출됨
    # value에는 변경 후의 값이 전달됩니다
    pass
)

# 이벤트 핸들링 정지
domain.unsubscribe_model(callback_id)

```


**⚠️ Warning**

이 이벤트는 SDK가 가진 로컬 캐시의 값이 변경되었을 때 호출됩니다.

로컬 캐시는 SDK가 가진 API의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-Distributor를 통한 스탬프 시트의 실행, 또는 GS2-Gateway의 알림을 활성화한 GS2-JobQueue의 실행에 의해 변화한 것만이 대상이 됩니다.

따라서 이 방법 이외로 값이 변경된 경우 콜백은 호출되지 않습니다.

---

### vote

자신의 투표용지를 제출하여 대전 결과에 투표합니다 (개별 투표)<br>

각 플레이어가 자신의 서명된 투표용지와 대전 결과(누가 이기고 누가 졌는지)를 함께 제출합니다.<br>
대전 결과를 보고하는 두 가지 방법 중 하나로, 각 플레이어가 독립적으로 투표하는 단순한 방식입니다.<br>

개별 투표의 작동 방식:<br>
- 대전이 종료된 후, 각 플레이어가 CreateVote를 호출하여 서명된 투표용지를 받은 다음 Vote를 호출하여 제출합니다<br>
- 시스템은 첫 투표로부터 최대 5분간 모든 플레이어의 투표를 기다립니다<br>
- 모든 플레이어가 투표를 완료하거나 5분이 경과하면 시스템은 다수결로 결과를 결정합니다<br>
- 투표가 동수인 경우(예: 2명이 "플레이어 A 승리", 2명이 "플레이어 B 승리"라고 응답), 결과는 기본적으로 폐기됩니다(스크립트로 변경 가능)<br>
- 결정된 결과에 따라 각 플레이어의 레이팅 포인트가 조정됩니다<br>

이 방식은 구현이 간단하지만 지연이 발생합니다 — 모든 투표가 완료되거나 5분의 대기 시간이 지날 때까지 결과가 반영되지 않습니다.<br>

즉시 결과를 반영하고 싶다면, 승리한 측이 모든 투표용지를 모아 한 번에 제출하는 VoteMultiple을 대신 사용하세요.<br>

파라미터:<br>
- ballotBody / ballotSignature: CreateVote로 획득한 서명된 투표용지<br>
- gameResults: 대전 결과 — 참가 플레이어와 순위(1위 = 승자, 2위 = 패자 등) 목록<br>
- keyId: 투표용지 서명을 검증하는 데 사용되는 암호화 키

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| ballotBody | string |  | ✓|  |  ~ 1024자 | 투표용지의 서명 대상 데이터 |
| ballotSignature | string |  | ✓|  |  ~ 256자 | 투표용지의 서명 |
| gameResults | [List&lt;EzGameResult&gt;](#ezgameresult) |  | |  | 0 ~ 10 items | 대전 결과<br>대전을 진행한 플레이어 그룹에 속한 사용자 ID 목록 |
| keyId | string |  | | "grn:gs2:{region}:{ownerId}:key:default:key:default" |  ~ 1024자 | 암호화 키 GRN |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzBallot](#ezballot) | 투표용지|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    );
    var result = await domain.VoteAsync(
        ballotBody: "ballotBody...",
        ballotSignature: "ballotSignature...",
        gameResults: new List<Gs2.Unity.Gs2SeasonRating.Model.EzGameResult> {
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 1,
                UserId = "user-0001",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0002",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0003",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 3,
                UserId = "user-0004",
            },
        },
        keyId: "key-0001"
    );
    var item = await result.ModelAsync();

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    );
    var future = domain.VoteFuture(
        ballotBody: "ballotBody...",
        ballotSignature: "ballotSignature...",
        gameResults: new List<Gs2.Unity.Gs2SeasonRating.Model.EzGameResult> {
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 1,
                UserId = "user-0001",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0002",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0003",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 3,
                UserId = "user-0004",
            },
        },
        keyId: "key-0001"
    );
    yield return future;
    if (future.Error != null)
    {
        onError.Invoke(future.Error, null);
        yield break;
    }
    var future2 = future.Result.ModelFuture();
    yield return future2;
    if (future2.Error != null)
    {
        onError.Invoke(future2.Error, null);
        yield break;
    }
    var result = future2.Result;

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    );
    const auto Future = Domain->Vote(
        "ballotBody...", // ballotBody
        "ballotSignature...", // ballotSignature
        []
        {
            auto v = MakeShared<TArray<TSharedPtr<Gs2::UE5::SeasonRating::Model::FEzGameResult>>>();
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(1))
                ->WithUserId(TOptional<FString>("user-0001"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(2))
                ->WithUserId(TOptional<FString>("user-0002"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(2))
                ->WithUserId(TOptional<FString>("user-0003"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(3))
                ->WithUserId(TOptional<FString>("user-0004"))
            );
            return v;
        }(), // gameResults
        "key-0001" // keyId
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // 변경된 값 / 결과 값을 취득
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError())
    {
        return Future2->GetTask().Error();
    }
    const auto Result = Future2->GetTask().Result();

```

**Godot**
```gdscript

var domain = ez.season_rating.namespace_(
        "namespace-0001"
    )

var async_result = await domain.vote(
    "ballotBody...", # ballot_body
    "ballotSignature...", # ballot_signature
    [
        Gs2SeasonRatingEzGameResult.new()
            .with_rank(1)
            .with_user_id("user-0001"),
        Gs2SeasonRatingEzGameResult.new()
            .with_rank(2)
            .with_user_id("user-0002"),
        Gs2SeasonRatingEzGameResult.new()
            .with_rank(2)
            .with_user_id("user-0003"),
        Gs2SeasonRatingEzGameResult.new()
            .with_rank(3)
            .with_user_id("user-0004"),
    ], # game_results
    "key-0001" # key_id
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

```


---

### voteMultiple

모든 투표용지를 모아 대전 결과를 한 번에 제출합니다 (즉시 결과 반영)<br>

승리한 측의 대표 플레이어가 모든 참가자의 서명된 투표용지를 모아 대전 결과와 함께 한 번에 제출합니다.<br>
이 방식은 결과가 즉시 반영됩니다. 5분간의 투표 대기 시간이 없습니다.<br>

승리한 측이 투표용지를 모아야 하는 이유:<br>
- 패배한 측은 "우리가 이겼다"고 거짓말할 유인이 있지만, 승리한 측은 승리에 대해 거짓말할 이유가 없습니다<br>
- 일부 패배 플레이어가 투표용지를 제출하지 않더라도, 과반수의 투표용지만 모이면 결과를 확정할 수 있습니다<br>
- 예를 들어 2대2 대전에서 4명 중 3명이 "팀 A 승리"라는 투표용지를 제출하면 과반수로 충분합니다<br>

일반적인 구현 방법:<br>
1. 대전이 종료되면 각 플레이어가 CreateVote를 호출하여 서명된 투표용지를 받습니다<br>
2. 각 플레이어는 게임의 통신 경로(예: 실시간 서버, P2P)를 통해 승리한 측의 대표 플레이어에게 투표용지(본문 + 서명)를 전송합니다<br>
3. 대표 플레이어가 수집한 모든 투표용지와 대전 결과를 VoteMultiple로 제출합니다<br>
4. 시스템이 즉시 투표용지를 검증하고 결과를 결정하여 모든 플레이어의 레이팅을 업데이트합니다<br>

파라미터:<br>
- signedBallots: 수집한 모든 서명된 투표용지 목록(각 플레이어의 본문 + 서명 쌍)<br>
- gameResults: 대전 결과 — 참가 플레이어와 순위 목록<br>
- keyId: 투표용지 서명을 검증하는 데 사용되는 암호화 키

#### Request

|  | 타입 | 활성화 조건 | 필수 | 기본값 | 값 제한 | 설명 |
| --- | --- | --- | --- | --- | --- | --- |
| namespaceName | string |  | ✓|  |  ~ 128자 | 네임스페이스 이름<br>네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다. |
| signedBallots | [List&lt;EzSignedBallot&gt;](#ezsignedballot) |  | |  | 0 ~ 10 items | 서명이 첨부된 투표용지 리스트 |
| gameResults | [List&lt;EzGameResult&gt;](#ezgameresult) |  | |  | 0 ~ 10 items | 투표 내용. 대전을 진행한 플레이어 그룹 1에 속한 사용자 ID 리스트 |
| keyId | string |  | | "grn:gs2:{region}:{ownerId}:key:default:key:default" |  ~ 1024자 | 암호화 키 GRN |

#### Result

|  | 타입 | 설명 |
| --- | --- | --- |
| item | [EzBallot](#ezballot) | 투표용지|

#### 구현 예제




**Unity (UniTask)**
```csharp
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    );
    var result = await domain.VoteMultipleAsync(
        signedBallots: new List<Gs2.Unity.Gs2SeasonRating.Model.EzSignedBallot> {
            new Gs2.Unity.Gs2SeasonRating.Model.EzSignedBallot() {
                Body = "aaa",
                Signature = "bbb",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzSignedBallot() {
                Body = "aaa",
                Signature = "bbb",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzSignedBallot() {
                Body = "aaa",
                Signature = "bbb",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzSignedBallot() {
                Body = "aaa",
                Signature = "bbb",
            },
        },
        gameResults: new List<Gs2.Unity.Gs2SeasonRating.Model.EzGameResult> {
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 1,
                UserId = "user-0001",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0002",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0003",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 3,
                UserId = "user-0004",
            },
        },
        keyId: "key-0001"
    );
    var item = await result.ModelAsync();

```

**Unity (Vanilla)**
```cs
    var domain = gs2.SeasonRating.Namespace(
        namespaceName: "namespace-0001"
    );
    var future = domain.VoteMultipleFuture(
        signedBallots: new List<Gs2.Unity.Gs2SeasonRating.Model.EzSignedBallot> {
            new Gs2.Unity.Gs2SeasonRating.Model.EzSignedBallot() {
                Body = "aaa",
                Signature = "bbb",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzSignedBallot() {
                Body = "aaa",
                Signature = "bbb",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzSignedBallot() {
                Body = "aaa",
                Signature = "bbb",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzSignedBallot() {
                Body = "aaa",
                Signature = "bbb",
            },
        },
        gameResults: new List<Gs2.Unity.Gs2SeasonRating.Model.EzGameResult> {
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 1,
                UserId = "user-0001",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0002",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 2,
                UserId = "user-0003",
            },
            new Gs2.Unity.Gs2SeasonRating.Model.EzGameResult() {
                Rank = 3,
                UserId = "user-0004",
            },
        },
        keyId: "key-0001"
    );
    yield return future;
    if (future.Error != null)
    {
        onError.Invoke(future.Error, null);
        yield break;
    }
    var future2 = future.Result.ModelFuture();
    yield return future2;
    if (future2.Error != null)
    {
        onError.Invoke(future2.Error, null);
        yield break;
    }
    var result = future2.Result;

```

**Unreal Engine 5**
```cpp
    const auto Domain = Gs2->SeasonRating->Namespace(
        "namespace-0001" // namespaceName
    );
    const auto Future = Domain->VoteMultiple(
        []
        {
            auto v = MakeShared<TArray<TSharedPtr<Gs2::UE5::SeasonRating::Model::FEzSignedBallot>>>();
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzSignedBallot>()
                ->WithBody(TOptional<FString>("aaa"))
                ->WithSignature(TOptional<FString>("bbb"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzSignedBallot>()
                ->WithBody(TOptional<FString>("aaa"))
                ->WithSignature(TOptional<FString>("bbb"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzSignedBallot>()
                ->WithBody(TOptional<FString>("aaa"))
                ->WithSignature(TOptional<FString>("bbb"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzSignedBallot>()
                ->WithBody(TOptional<FString>("aaa"))
                ->WithSignature(TOptional<FString>("bbb"))
            );
            return v;
        }(), // signedBallots
        []
        {
            auto v = MakeShared<TArray<TSharedPtr<Gs2::UE5::SeasonRating::Model::FEzGameResult>>>();
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(1))
                ->WithUserId(TOptional<FString>("user-0001"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(2))
                ->WithUserId(TOptional<FString>("user-0002"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(2))
                ->WithUserId(TOptional<FString>("user-0003"))
            );
            v->Add(
                MakeShared<Gs2::UE5::SeasonRating::Model::FEzGameResult>()
                ->WithRank(TOptional<int32>(3))
                ->WithUserId(TOptional<FString>("user-0004"))
            );
            return v;
        }(), // gameResults
        "key-0001" // keyId
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // 변경된 값 / 결과 값을 취득
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError())
    {
        return Future2->GetTask().Error();
    }
    const auto Result = Future2->GetTask().Result();

```

**Godot**
```gdscript

var domain = ez.season_rating.namespace_(
        "namespace-0001"
    )

var async_result = await domain.vote_multiple(
    [
        Gs2SeasonRatingEzSignedBallot.new()
            .with_body("aaa")
            .with_signature("bbb"),
        Gs2SeasonRatingEzSignedBallot.new()
            .with_body("aaa")
            .with_signature("bbb"),
        Gs2SeasonRatingEzSignedBallot.new()
            .with_body("aaa")
            .with_signature("bbb"),
        Gs2SeasonRatingEzSignedBallot.new()
            .with_body("aaa")
            .with_signature("bbb"),
    ], # signed_ballots
    [
        Gs2SeasonRatingEzGameResult.new()
            .with_rank(1)
            .with_user_id("user-0001"),
        Gs2SeasonRatingEzGameResult.new()
            .with_rank(2)
            .with_user_id("user-0002"),
        Gs2SeasonRatingEzGameResult.new()
            .with_rank(2)
            .with_user_id("user-0003"),
        Gs2SeasonRatingEzGameResult.new()
            .with_rank(3)
            .with_user_id("user-0004"),
    ], # game_results
    "key-0001" # key_id
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

```


---



