Documentation index for AI agents

GS2-Idle

방치 보상 기능

게임을 플레이하지 않은 기간에 따라 보상을 지급하는 구조를 구현합니다. 스마트폰 대상 게임에서는 “오랜만에 앱을 실행했더니 대량의 보상을 획득할 수 있었다"는 디자인이 플레이어의 재방문 촉진에 크게 기여합니다. GS2-Idle은 이러한 방치 보상을 간단하게 도입하기 위한 마이크로서비스입니다.

graph LR
  Start["대기 시작"] --> Idle["방치 시간 경과"]
  Idle --> Prediction["획득 예정 보상 조회"]
  Prediction --> Receive["보상 수령"]
  Receive --> Start

카테고리

방치 보상은 여러 개를 준비할 수 있습니다. 플레이어는 카테고리별로 1개의 대기 시간을 가질 수 있습니다.

카테고리는 “캐릭터의 스태미나 자연 회복”, “채굴장의 자동 채굴”, “소재의 자동 생성” 등, 각각 독립된 방치 보상의 단위로 활용할 수 있습니다.

대기 시간

카테고리에는 대기 시간(몇 분마다 보상을 얻을 수 있는지)과 대기 시간 최댓값의 초깃값을 설정할 수 있습니다. 대기 시간의 최댓값은 플레이어별로 늘릴 수 있습니다. 보상 수령 후에 미소진된 대기 시간을 초기화할지 다음으로 이월할지는 rewardResetMode로 선택할 수 있습니다.

rewardResetMode동작
Reset보상 수령 시 경과 시간이 0으로 초기화됩니다. 획득 타이밍의 나머지 시간은 폐기됩니다.
CarryOver보상 수령 시 획득한 인터벌만큼 경과 시간에서 차감됩니다. 나머지 시간은 다음 대기에도 이어집니다.

방치 보상

방치 보상에는 대기 시간이 일정 시간 경과하면 얻을 수 있는 아이템 목록을 정의합니다. 지급하는 보상은 “경험치+아이템"처럼 여러 개(최대 10개)를 설정할 수 있습니다.

또한, 대기 시간 내에서 보상 내용에 변화를 줄 수 있도록 여러 개의 보상 목록을 설정할 수 있습니다.

예를 들어, 10분 대기할 때마다 “경험치+10"과 “강화 소재 Lv.1 x 1"을 얻을 수 있습니다. 단, 60분마다인 타이밍에는 위 보상 대신 “경험치+20"과 “강화 소재 Lv.2 x 1"을 얻을 수 있습니다. 위와 같은 예시를 생각해 봅시다.

이 경우, 방치 보상에는 다음과 같은 테이블을 설정합니다.

-보상1보상2
1경험치+10강화 소재 Lv.1 x 1
2경험치+10강화 소재 Lv.1 x 1
3경험치+10강화 소재 Lv.1 x 1
4경험치+10강화 소재 Lv.1 x 1
5경험치+10강화 소재 Lv.1 x 1
6경험치+20강화 소재 Lv.2 x 1

이렇게 하면 경과 시간에 따라 보상 아이템 내용을 변경할 수 있습니다. 보상 내용은 순환하므로, 2시간 경과 후에는 “1,2,3,4,5,6,1,2,3,4,5,6” 순서의 아이템을 얻게 됩니다.

방치 보상의 랜덤 추첨

보상 내용에 좀 더 무작위성을 부여하고 싶은 경우가 있습니다. 그러한 경우에는 보상에 GS2-Lottery의 추첨 처리를 설정해 주세요.

기존의 GS2-Lottery에서는 추첨을 수행할 때까지 결과가 정해지지 않지만, GS2-Idle을 사용하는 경우에는 대기 시작 시점에 난수 시드를 생성하고 보상 계산 시 그 난수 시드를 이용해 추첨을 수행함으로써, 대기 도중에도 무작위로 추첨된 아이템 내용을 플레이어에게 제시할 수 있으며, 내용은 불변이 됩니다. (GS2-Lottery의 경품 테이블을 변경하면 이 전제는 무너집니다)

대기 시간의 스케줄 관리

이벤트와 연동한 방치 보상을 구현할 수 있도록, 카테고리별로 GS2-Schedule의 이벤트와 연결할 수 있습니다.

이벤트 개최 기간을 2023-01-01 00:00 ~ 2023-02-01 00:00으로 하고, 2023-01-31 23:00부터 대기를 시작했다고 가정합시다. 이 경우, 2023-02-01 00:00이 되는 시점에 방치 시간 카운트가 정지합니다.

따라서 2023-02-01 01:00에 보상을 수령하는 경우도, 2023-02-01 09:00에 보상을 수령하는 경우도 내용은 동일합니다.

이벤트에 반복 설정이 있는 경우, 반복 횟수가 바뀌는 시점에 방치 시간이 초기화됩니다. 예를 들어 매주 월요일 00:00 ~ 화요일 00:00에 반복되는 이벤트의 경우, 다음 주 월요일 00:00이 되는 순간 전주의 보상을 수령하지 않았더라도 대기 시간은 초기화됩니다.

대기 시간의 스케줄과는 별도로, 보상 수령 가능 기간을 설정할 수 있습니다. 수령 가능 기간 외에도 Prediction으로 예정 보상을 가져올 수는 있지만, 수령 API를 호출하면 오류가 발생합니다.

상태

플레이어는 카테고리별로 1개의 Status를 보유합니다. Status에는 다음과 같은 정보가 포함됩니다.

필드설명
idleStartedAt대기 시작 시각
idleMinutes누적 방치 시간(분)
nextRewardsAt다음 보상 획득 타이밍
maximumIdleMinutes이 플레이어에게 적용되는 방치 시간의 상한
randomSeedGS2-Lottery와 결합한 추첨용 고정 시드

구현 예제

대기 시작

처음으로 대기 시간 정보를 가져올 때 대기가 시작됩니다.

    var item = await gs2.Idle.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        category: "category-0001"
    ).ModelAsync();
    const auto Future = Gs2->Idle->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
        "category-0001" // categoryName
    )->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.idle.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "category-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 items = await gs2.Idle.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).StatusesAsync(
    ).ToListAsync();
    const auto It = Gs2->Idle->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Statuses(
    );
    TArray<Gs2::UE5::Idle::Model::FEzStatusPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.idle.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).statuses(
    )

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

var items = async_result.result

보상 내용 확인

획득 예정 보상을 수령 전에 확인하기 위해 Prediction API를 사용합니다. 플레이어에게 “앞으로 N분 기다리면 이만큼의 아이템을 얻을 수 있다"와 같은 사전 안내를 하고 싶은 경우에 활용할 수 있습니다.

    var items = await gs2.Idle.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        category: "category-0001"
    ).PredictionAsync();
    const auto Domain = Gs2->Idle->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
        "category-0001" // categoryName
    );
    const auto Future = Domain->Prediction(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result = Future2->GetTask().Result();
var domain = ez.idle.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "category-0001"
    )

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

var result = async_result.result

보상 수령

보상을 수령하면 대기 시간이 초기화됩니다. 또한, 대기 시간에 보상 획득 타이밍까지의 나머지가 존재하는 경우도 0으로 초기화됩니다(rewardResetModeCarryOver를 지정한 경우에는 나머지 시간이 다음으로 이월됩니다).

    await gs2.Idle.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        category: "category-0001"
    ).ReceiveAsync();
    const auto Future = Gs2->Idle->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
        "category-0001" // categoryName
    )->Receive();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.idle.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "category-0001"
    )

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

var result = async_result.result

스크립트 트리거

네임스페이스에 overrideAcquireActionsScriptIdreceiveScript를 설정하면, 보상 산출이나 수령 처리 전후에 커스텀 스크립트를 실행할 수 있습니다. 트리거는 동기·비동기 실행 방식을 선택할 수 있으며, 비동기 처리에서는 GS2-Script나 Amazon EventBridge를 이용한 외부 연계도 가능합니다.

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

  • overrideAcquireActionsScriptId: 보상 산출 시 실행되는 동기 스크립트. 동적으로 보상 목록을 덮어쓸 수 있습니다. 플레이어 상태에 따른 보상 교체 등에 활용할 수 있습니다.
  • receiveScript(완료 통지: receiveDone): 보상 수령 전후. 동기 실행으로 수령 허가·거부나 배율 보정, 비동기 실행으로 Amazon EventBridge를 통한 외부 연계가 가능합니다.

트랜잭션 액션

GS2-Idle에서는 다음과 같은 트랜잭션 액션을 제공하고 있습니다.

유형액션설명
소비Gs2Idle:DecreaseMaximumIdleMinutesByUserId최대 방치 시간 감소
입수Gs2Idle:IncreaseMaximumIdleMinutesByUserId최대 방치 시간 증가
입수Gs2Idle:SetMaximumIdleMinutesByUserId최대 방치 시간 설정
입수Gs2Idle:ReceiveByUserId보상 수령

“최대 방치 시간 증가"를 입수 액션으로 이용하면, 특정 아이템을 입수했을 때나 플레이어 랭크가 상승했을 때 등, 자동으로 방치 보상을 쌓을 수 있는 상한 시간을 확장하는 처리가 가능해집니다. 이를 통해 플레이어의 성장에 맞춰 더 많은 방치 보상을 축적할 수 있게 되어, 플레이 경험 향상으로 이어질 수 있습니다.

버프에 의한 보정

GS2-Buff를 이용하면, 카테고리 모델의 acquireActions나 플레이어별 maximumIdleMinutes를 버프로 보정하여, 이벤트나 캠페인에 따라 대기 중 보상 내용이나 상한 시간을 동적으로 조정할 수 있습니다.

예를 들어 “주말에는 방치 보상 1.5배”, “특정 칭호를 가진 플레이어는 최대 방치 시간 +60분"과 같은 운영이 가능합니다.

마스터 데이터 운용

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

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

  • CategoryModel: 대기 시간이나 보상 테이블 설정

다음은 마스터 데이터의 JSON 예시입니다.

{
  "version": "2024-04-25",
  "categoryModels": [
    {
      "name": "category-0001",
      "metadata": "Stamina",
      "rewardIntervalMinutes": 10,
      "defaultMaximumIdleMinutes": 360,
      "rewardResetMode": "CarryOver",
      "acquireActions": [
        {
          "acquireActions": [
            {
              "action": "Gs2Experience:AddExperienceByUserId",
              "request": "{\"experienceValue\": 10}"
            }
          ]
        }
      ]
    }
  ]
}

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

상세 레퍼런스