Documentation index for AI agents

GS2-Lottery

추첨 처리 기능

가챠를 구현하기 위한 시스템입니다. GS2-Lottery는 일반 가챠와 박스 가챠를 지원합니다.

graph TD
  Trigger["추첨 트리거<br/>(GS2-Showcase의 구매 보상 / 퀘스트 클리어 보상 등)"] --> LotteryModel["LotteryModel<br/>(추첨 모델)"]
  LotteryModel -- "mode = normal" --> Normal["일반 모드 추첨"]
  LotteryModel -- "mode = box" --> Box["박스 모드 추첨"]
  Normal --> PrizeTable["경품 테이블<br/>(PrizeTable)"]
  Box --> BoxData["플레이어 전용 박스"]
  PrizeTable --> Prize["Prize<br/>(acquireActions)"]
  BoxData --> Prize
  Prize --> Acquire["GS2-Distributor<br/>보상 배포"]

일반 가챠

일반 가챠는 지정된 확률로 순수한 추첨을 진행합니다.

경품 테이블

경품 테이블이란 가챠를 실행했을 때 나오는 경품의 배출 확률을 정의한 것입니다. 마스터 데이터로 정의하게 됩니다.

가중치

경품 테이블에서 확률 설정은 경품별 가중치를 설정합니다. 구체적으로는 다음과 같은 테이블을 작성하게 됩니다.

가중치
경품A1
경품B2
경품C4

이를 백분율 확률로 해석하면 다음과 같이 해석할 수 있습니다.

가중치확률
경품A114.285%
경품B228.571%
경품C457.143%

백분율 기준으로 확률을 설정해야 할 경우 모든 값을 더해서 100%가 되도록 고려해야 하지만 가중치 기준으로 확률을 설정할 경우에는 그런 부담을 질 필요가 없습니다.

경품 테이블의 중첩

경품 테이블은 최대 5단계까지 중첩할 수 있습니다.

구체적인 사용 방법으로는

  • SSR 캐릭터의 배출 확률을 3%
  • SR 캐릭터의 배출 확률을 7%
  • R 캐릭터의 배출 확률을 90%

라는 기본 방침에 따라 배출 캐릭터를 설정하고 싶을 때 유용합니다. 이 경우 2단계로, 4개의 경품 테이블을 정의하게 됩니다.

레어도 추첨용 경품 테이블

가중치경품 종류추첨 테이블 이름
SSR3경품 테이블의 중첩SSR 캐릭터 추첨용 경품 테이블
SR7경품 테이블의 중첩SR 캐릭터 추첨용 경품 테이블
R90경품 테이블의 중첩R 캐릭터 추첨용 경품 테이블

SSR 캐릭터 추첨용 경품 테이블

가중치경품 종류추첨 테이블 이름
SSR-00011입수 처리SSR-0001을 GS2-Dictionary에 기록
SSR-00021입수 처리SSR-0002를 GS2-Dictionary에 기록
SSR-00031입수 처리SSR-0003을 GS2-Dictionary에 기록

SR 캐릭터 추첨용 경품 테이블

가중치경품 종류추첨 테이블 이름
SR-00011입수 처리SR-0001을 GS2-Dictionary에 기록
SR-00021입수 처리SR-0002를 GS2-Dictionary에 기록
SR-00031입수 처리SR-0003을 GS2-Dictionary에 기록

R 캐릭터 추첨용 경품 테이블

가중치경품 종류추첨 테이블 이름
R-00011입수 처리R-0001을 GS2-Dictionary에 기록
R-00021입수 처리R-0002를 GS2-Dictionary에 기록
R-00031입수 처리R-0003을 GS2-Dictionary에 기록

박스 가챠

박스 가챠는 마스터 데이터로 정의된 경품을 지정된 수량만큼 박스에 투입하고 추첨 처리를 진행할 때는 박스에서 경품을 꺼내는 방식으로 경품의 내용을 결정합니다.

즉, 100개의 경품 중 1개의 “당첨"을 넣은 박스를 준비한 경우, 100회 이내에 반드시 “당첨” 경품이 추첨됩니다.

박스에 경품 투입

박스 내부의 경품 설정에도 경품 테이블을 사용합니다. 일반 추첨 모드에서는 배출 가중치를 설정했지만, 박스 모드에서는 가중치 파라미터에 박스에 경품을 투입하는 수량을 설정합니다.

가중치
경품A1
경품B2
경품C4

이 경우, 박스에는 처음에 7개의 경품이 들어 있으며 “경품A 1개”, “경품B 2개”, “경품C 4개"가 들어 있는 상태가 됩니다.

박스 리셋

박스는 “박스 리셋” 액션을 통해 내용물을 초기 상태로 되돌릴 수 있습니다. 플레이어마다 독립된 박스를 가지므로, 컴플리트 보상 지급 후 박스를 다시 뽑고 싶거나, 매달 박스를 갱신하고 싶은 용도로 활용할 수 있습니다.

추첨 모델

추첨 모델에서는 경품 테이블 중 추첨 API에서 사용할 수 있는 경품 테이블을 지정합니다. 마스터 데이터로 정의하게 됩니다.

위 예시로 말하면, 추첨 API를 호출했을 때 갑자기 SSR 캐릭터 추첨용 경품 테이블을 사용해 추첨 처리가 이루어지면 불안합니다.

추첨 모델에 다음과 같은 마스터 데이터를 정의해 두고

추첨 모델 이름경품 테이블 이름
가챠레어도 추첨용 경품 테이블

추첨 처리를 호출할 때는 추첨 모델 이름에 “가챠"를 지정해 추첨하게 됩니다.

추첨 모드(mode)와 추첨 방법(method)

추첨 모델에서는 mode로 추첨 방식을, method로 경품 테이블 참조 방법을 지정합니다.

mode설명
normal일반 모드(지정된 확률로 매번 독립적으로 추첨을 진행)
box박스 모드(플레이어 고유의 박스에서 경품을 꺼냄)
method설명
prize_table정적으로 지정된 경품 테이블을 사용
scriptGS2-Script를 사용해 동적으로 경품 테이블을 선택

스크립트를 통한 경품 테이블 선택

methodscript로 설정하고 choicePrizeTableScriptId를 지정하면, 추첨 실행 시 GS2-Script를 호출해 동적으로 사용할 경품 테이블을 선택할 수 있습니다. 플레이어별 확률 변경(천장 시스템)이나, 진행 중인 이벤트에 따른 경품 테이블 전환을 구현할 수 있습니다.

스크립트 트리거

네임스페이스에 lotteryTriggerScriptId를 설정하면, 추첨 처리 전에 GS2-Script를 동기 실행할 수 있습니다. 스크립트에서는 추첨 허용/거부를 판정하거나 추첨 결과를 덮어쓸 수 있습니다.

설정할 수 있는 주요 이벤트 트리거와 스크립트 설정 이름은 다음과 같습니다.

  • lotteryTriggerScriptId: 추첨 처리 전에 동기 실행

추첨 결과 검증이나, 특정 조건에서의 결과 교체 같은 커스텀 처리를 구현할 수 있습니다.

트랜잭션 액션

GS2-Lottery에서는 다음과 같은 트랜잭션 액션을 제공합니다.

  • 입수 액션: 추첨 실행, 박스 리셋

“추첨 실행"을 입수 액션으로 활용하면, 상점에서 상품을 구매하거나 퀘스트를 클리어했을 때의 보상으로 가챠(추첨)를 직접 실행하는 처리가 가능해집니다. 또한 “박스 리셋"을 입수 액션에 포함하면, 특정 아이템을 입수한 시점이나 이벤트의 전환점에서 박스 가챠의 내용물을 자동으로 리셋해 처음부터 다시 뽑을 수 있는 상태로 만드는 제어를 트랜잭션 내에서 안전하게 수행할 수 있습니다.

마스터 데이터 운용

마스터 데이터를 등록함으로써 마이크로서비스에서 사용 가능한 데이터와 동작을 설정할 수 있습니다.

마스터 데이터의 종류에는 다음이 있습니다.

  • LotteryModel: 추첨 모델. 추첨 방식과 사용할 경품 테이블을 정의합니다.
  • PrizeTable: 경품 테이블. 경품의 가중치와 입수 액션, 또는 중첩된 경품 테이블을 정의합니다.
  • Prize: 경품의 단위. acquireActions로 얻을 수 있는 보상을 지정하며, drawnLimitlimitFailOverPrizeId로 배출 제한과 실패 시 대체 대상을 설정할 수 있습니다.

마스터 데이터의 등록은 관리 콘솔에서 등록하는 방법 외에도, GitHub에서 데이터를 반영하거나 GS2-Deploy를 사용해 CI에서 등록하는 워크플로우를 구성할 수 있습니다.

이하는 마스터 데이터의 JSON 예시입니다.

{
  "version": "2019-02-21",
  "lotteryModels": [
    {
      "name": "lottery-0001",
      "metadata": "일반 가챠",
      "mode": "normal",
      "method": "prize_table",
      "prizeTableName": "rarity"
    }
  ],
  "prizeTables": [
    {
      "name": "rarity",
      "metadata": "레어도 추첨",
      "prizes": [
        {
          "prizeId": "ssr",
          "type": "prize_table",
          "prizeTableName": "ssr-prizes",
          "weight": 3
        },
        {
          "prizeId": "sr",
          "type": "prize_table",
          "prizeTableName": "sr-prizes",
          "weight": 7
        },
        {
          "prizeId": "r",
          "type": "prize_table",
          "prizeTableName": "r-prizes",
          "weight": 90
        }
      ]
    }
  ]
}

구현 예제

추첨 실행

추첨 실행은 게임 엔진용 SDK에서는 처리할 수 없습니다.

GS2-Showcase의 상품 구매 보상으로 추첨을 실행하는 방식으로 구현해 주세요. GS2-Showcase의 acquireActions에 GS2-Lottery의 DrawByUserId를 포함시키면, 구매 처리의 트랜잭션으로 추첨을 실행할 수 있습니다.

배출 확률 조회

경품 표시용으로, 현재 플레이어에 대한 배출 확률을 가져올 수 있습니다. 배출 수량 제한(후술)이 걸려 있는 경우, 그 영향이 반영된 확률이 반환됩니다.

    var items = await gs2.Lottery.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).ProbabilitiesAsync(
        lotteryName: "lottery-0001"
    ).ToListAsync();
    const auto It = Gs2->Lottery->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Probabilities(
        "lottery-0001" // lotteryName
    );
    TArray<Gs2::UE5::Lottery::Model::FEzProbabilityPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.lottery.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).probabilities(
        "lottery-0001"
    )

var async_result = await iterator.load()
if async_result.error != null:
    # 에러를 처리
    push_error(str(async_result.error))
    return

var items = async_result.result

박스의 내용물 조회

    var item = await gs2.Lottery.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).BoxItems(
        prizeTableName: "prizeTable-0001"
    ).ModelAsync();
    const auto Domain = Gs2->Lottery->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->BoxItems(
        "prizeTable-0001" // prizeTableName
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Result = Future->GetTask().Result();
var domain = ez.lottery.namespace_(
        "namespace-0001"
    ).me(game_session).box_items(
        "prizeTable-0001"
    )

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

var result = async_result.result

박스 리셋

    var result = await gs2.Lottery.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).BoxItems(
        prizeTableName: "prizeTable-0001"
    ).ResetBoxAsync(
    );
    const auto Future = Gs2->Lottery->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->BoxItems(
        "prizeTable-0001" // prizeTableName
    )->ResetBox(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.lottery.namespace_(
        "namespace-0001"
    ).me(game_session).box_items(
        "prizeTable-0001"
    )

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

var result = async_result.result

추첨 모델 목록 조회

UI에서 가챠 목록을 표시하는 용도로, 네임스페이스 내에 정의된 추첨 모델을 조회할 수 있습니다.

    var items = await gs2.Lottery.Namespace(
        namespaceName: "namespace-0001"
    ).LotteryModelsAsync(
    ).ToListAsync();
    const auto It = Gs2->Lottery->Namespace(
        "namespace-0001" // namespaceName
    )->LotteryModels();
    TArray<Gs2::UE5::Lottery::Model::FEzLotteryModelPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.lottery.namespace_(
        "namespace-0001"
    ).lottery_models(
    )

var async_result = await iterator.load()
if async_result.error != null:
    # 에러를 처리
    push_error(str(async_result.error))
    return

var items = async_result.result

기타 기능

경품 배출 수량 제한

일반 모드로 동작시킬 때, 경품별로 배출 수량의 상한을 설정할 수 있습니다. 이 기능을 사용하면 Probabilities 함수가 응답하는 확률과 실제 확률 사이에 차이가 발생합니다. Probabilities를 사용해 배출 확률을 표시할 때는 이 기능을 사용해서는 안 됩니다.

용도로 상정하고 있는 것은 실물 경품을 배포하기 위한 추첨 처리입니다. 확률 1%로 배출되는 상품권이 있다고 할 때, 상품권이 100장밖에 준비되어 있지 않은 경우 이 기능이 도움이 됩니다. 상품권의 배출 수량 상한을 100으로 설정하고, 실패 시 대체 경품에는 꽝이나 다른 당첨 경품을 설정합니다.

이렇게 설정하면, 이미 상품권을 100장 배출한 상태에서 상품권이 당첨된 경우 상품권 대신 실패 시 대체 경품으로 설정한 경품이 배출됩니다. 실패 시 대체 경품에 수량 제한이 설정되어 있는 경우는 동일한 제한이 적용됩니다.

상세 레퍼런스