Documentation index for AI agents

GS2-Stamina

스태미나 기능

스태미나는 게임 내의 횟수 제한 기능을 실현하는 것입니다. GS2-Limit 는 “하루에 3회” 와 같이 횟수 제한을 표현하지만, GS2-Stamina 는 “8시간에 1포인트 회복하는 스태미나를 사용하여 하루 3회로 횟수를 제한한다” 와 같이, 시간 경과와 소비 액션의 조합으로 제한을 표현합니다.

스태미나의 경우, 소비하는 포인트에 차등을 둠으로써 A라는 행동이라면 하루 3회, B라는 행동이라면 하루 5회와 같은 사양을 구현할 수 있습니다. 이는 스태미나 1당 얻을 수 있는 경험치나 게임 내 통화의 기준을 마련함으로써, 게임 내에서 어떤 행동을 취하더라도 특별히 효율이 좋은 행동이라는 것을 배제할 수 있어, 게임 밸런스를 조정하기 쉬워집니다.

개발자는 매일 로그인하도록 유도하기 위해 노력해야 하므로 이런 사양은 그다지 채택하지 않지만, 플레이어 입장에서는 8시간에 1포인트 회복하고 최대 15포인트까지 축적할 수 있는 사양이라면, 매일 로그인하지 않고 5일에 1회 로그인하더라도 포인트를 낭비하지 않고 다 사용할 수 있습니다.

스태미나

스태미나에는 “회복 간격”, “회복량”, “최대값” 을 설정합니다.

graph LR
  Time["시간 경과"] -- "recoverIntervalMinutes 마다" --> Recover["회복<br/>(+recoverValue)"]
  Recover --> Value["현재값"]
  Consume["소비 액션"] -- "-consumeValue" --> Value
  Value -- "maxValue 초과" --> Overflow["오버플로우 상태"]
파라미터설명
value현재 보유하고 있는 스태미나 값
maxValue자연 회복으로 도달할 수 있는 상한값
recoverIntervalMinutes1포인트 회복하는 데 필요한 분 수
recoverValue1회 회복으로 증가하는 값
overflowValue최대값을 초과한 만큼의 값
nextRecoverAt다음 회복 예정 일시

오버플로우

스태미나는 최대값을 초과하여 회복시킬 수 있습니다. 최대값을 초과한 상태에서는 시간 경과에 따른 회복이 발생하지 않습니다. 또한 최대값을 초과한 상태에서도 UI 상의 편의 등을 위해, 그 이상은 가산되지 않도록 하는 “진짜 최대값” 을 설정할 수 있습니다.

왜 오버플로우시키는 건가요?

이 사양은 스태미나를 사양에 포함한 게임을 플레이해 본 적이 별로 없는 개발자에게는 이상하게 느껴질 수 있습니다.

스태미나는 일반적으로 회복시키기 위한 아이템이나, 게임 내 과금을 통해 회복시키는 수단이 존재합니다. 그 아이템을 사용하거나 스태미나를 구매할 때 플레이어의 스트레스를 최소화하고자 이러한 사양이 만들어졌습니다.

구체적인 예를 들어 보겠습니다.

이 게임은 5분에 1포인트씩 스태미나가 회복되며, 당신의 스태미나 최대값은 50포인트입니다. 당신이 다음에 플레이하고 싶은 퀘스트를 플레이하려면 스태미나가 10포인트 필요합니다. 하지만 현재 당신의 스태미나 값은 9포인트밖에 없어, 5분을 기다리지 않으면 다음 퀘스트를 플레이할 수 없습니다. 그래서 당신은 스태미나를 구매하려고 합니다. 스태미나를 구매하면 스태미나가 50포인트 회복됩니다. 하지만 지금 당장 스태미나를 구매하면, 50포인트가 회복되더라도 9포인트는 낭비되고 맙니다. 그래서 당신은 5분을 기다려 1포인트가 회복된 후 퀘스트를 플레이하여 0포인트로 만든 다음, 그다음 퀘스트를 플레이하기 위해 스태미나를 구매했습니다.

이래서는 플레이어가 게임을 쾌적하게 플레이할 수 없습니다. 그래서 최대값을 초과하여 스태미나를 회복할 수 있는 사양을 도입하는 게임이 등장했습니다.

스태미나에 최대값을 초과하는 사양을 추가하면, 사용자 경험이 어떻게 달라지는지 살펴보겠습니다.

현재 당신의 스태미나 값은 9포인트밖에 없어, 5분을 기다리지 않으면 다음 퀘스트를 플레이할 수 없습니다. 그래서 당신은 스태미나를 구매하여 스태미나를 50포인트 회복시켜 59포인트로 만들었습니다. 이 상태에서는 시간 경과에 따른 스태미나 회복이 이루어지지 않지만, 당신은 곧바로 다음 퀘스트를 시작하여 스태미나를 49포인트로 만들었습니다.

GS2-Experience 와의 연동

MaxStaminaTable / RecoverIntervalTable / RecoverValueTable 를 마스터 데이터에 등록하여, GS2-Experience 의 랭크(플레이어 레벨 등)에 따라 자동으로 최대값·회복 간격·회복량을 변화시킬 수 있습니다. 플레이어의 레벨업에 맞춰 자동으로 스태미나 상한이 증가하는 등, 흔히 볼 수 있는 성장 요소를 간단하게 구현할 수 있습니다.

자연 회복의 동기화

Apply API 를 호출하면, 마지막으로 접근한 후 경과한 시간만큼의 스태미나 자연 회복을 즉시 반영할 수 있습니다. 일반적으로는 스태미나 취득 API 내부에서 자동으로 반영되므로 명시적으로 호출할 필요가 거의 없지만, 서버 측에서 스태미나 값을 엄밀하게 동기화하고 싶은 경우에 사용합니다.

스크립트 트리거

네임스페이스에 overflowTriggerScript 를 설정하면, 자연 회복에 의해 스태미나가 최대값을 초과했을 때 커스텀 스크립트를 실행할 수 있습니다. 이를 통해 오버플로우 시 넘친 스태미나를 다른 리소스로 변환하는 등의 응용이 가능합니다.

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

  • overflowTriggerScript: 스태미나 오버플로우 시

버프에 의한 보정

GS2-Buff 와 연동하면, maxValue·recoverIntervalMinutes·recoverValue 외에도 consumeValuerecoverValue 같은 액션 파라미터를 버프로 보정할 수 있어, 이벤트 등에서 스태미나 상한이나 회복 속도, 소비량을 유연하게 조정할 수 있습니다.

“기간 한정으로 스태미나 최대값을 1.5배로 늘린다”, “로그인 직후 1시간만 회복 속도를 2배로 한다” 와 같은 한시적인 게임 체험 조정에 활용할 수 있습니다.

트랜잭션 액션

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

  • 검증 액션: 현재값 검증, 최대값 검증, 회복 속도 검증, 회복량 검증, 오버플로우량 검증
  • 소비 액션: 스태미나 소비, 최대값 감산
  • 입수 액션: 스태미나 회복(가산), 최대값 가산·설정, 회복 속도 설정, 회복량 설정

“최대값 가산” 을 입수 액션으로 이용함으로써, 상점에서 아이템 구매 시나 플레이어 랭크가 상승했을 때 등에, 자동으로 스태미나의 최대 용량을 확장하는 처리를 트랜잭션 내에서 안전하게 실행할 수 있습니다. 이를 통해 플레이어의 성장을 체감하게 하는 보상 설계가 쉬워집니다. 또한 “스태미나 회복” 을 보상으로 설정함으로써, 특정 미션 달성 시 스태미나를 전부 회복시켜 지속적인 플레이를 유도하는 운용도 가능합니다.

마스터 데이터 운용

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

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

  • StaminaModel: 회복량·회복 간격·최대값·오버플로우 허용 여부의 정의
  • MaxStaminaTable: GS2-Experience 의 랭크로부터 최대값을 산출하는 테이블
  • RecoverIntervalTable: GS2-Experience 의 랭크로부터 회복 간격을 산출하는 테이블
  • RecoverValueTable: GS2-Experience 의 랭크로부터 회복량을 산출하는 테이블

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

{
  "version": "2019-04-23",
  "staminaModels": [
    {
      "name": "stamina-0001",
      "metadata": "퀘스트용 스태미나",
      "recoverIntervalMinutes": 5,
      "recoverValue": 1,
      "initialCapacity": 50,
      "isOverflow": true,
      "maxCapacity": 999
    }
  ]
}

마스터 데이터의 등록은 매니지먼트 콘솔에서 등록하는 것 외에도, GitHub 에서 데이터를 반영하거나, GS2-Deploy 를 사용해 CI 에서 등록하는 등의 워크플로우를 구성하는 것이 가능합니다.

구현 예제

현재 스태미나 값 취득

취득 시각에서의 자연 회복이 반영된 상태의 스태미나 값이 반환됩니다.

    var item = await gs2.Stamina.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Stamina(
        staminaName: "stamina-0001"
    ).ModelAsync();
    const auto Domain = Gs2->Stamina->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Stamina(
        "stamina-0001" // staminaName
    );
    const auto Item = Domain->Model();
var domain = ez.stamina.namespace_(
        "namespace-0001"
    ).me(game_session).stamina(
        "stamina-0001"
    )

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

var result = async_result.result

스태미나 목록 취득

네임스페이스에 등록된 여러 StaminaModel 중, 플레이어가 이용하고 있는 모든 스태미나의 현재값을 일괄로 취득할 수 있습니다.

    var items = await gs2.Stamina.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).StaminasAsync(
    ).ToListAsync();
    const auto Domain = Gs2->Stamina->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    );
    const auto It = Domain->Staminas(
    );
    TArray<Gs2::UE5::Stamina::Model::FEzStaminaPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.stamina.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).staminas(
    )

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

var items = async_result.result

스태미나 소비

이 API로 직접 스태미나를 소비하는 처리를 수행하는 것은 권장하지 않습니다.

GS2-Quest 와 같은 서비스를 통해 스태미나 소비를 수행함으로써, 소비와 맞바꾸어 퀘스트 시작이나 아이템 입수 등의 처리를 하나의 트랜잭션으로 안전하게 다룰 수 있습니다.

    var result = await gs2.Stamina.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Stamina(
        staminaName: "stamina-0001"
    ).ConsumeAsync(
        consumeValue: 50
    );
    var item = await result.ModelAsync();
    const auto Domain = Gs2->Stamina->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Stamina(
        "stamina-0001" // staminaName
    );
    const auto Future = Domain->Consume(
        50
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.stamina.namespace_(
        "namespace-0001"
    ).me(game_session).stamina(
        "stamina-0001"
    )

var async_result = await domain.consume(
    50 # consume_value
)
if async_result.error != null:
    if async_result.error.type == "InsufficientException":
        # 스태미나 잔량이 부족합니다
        pass
    push_error(str(async_result.error))
    return

var result = async_result.result

자연 회복 반영

마지막 접근 이후 경과한 시간만큼의 자연 회복을 즉시 반영합니다. 일반적으로는 API 호출 시 내부에서 자동으로 반영되므로 명시적으로 호출할 필요가 거의 없습니다.

    var result = await gs2.Stamina.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Stamina(
        staminaName: "stamina-0001"
    ).ApplyAsync(
    );
    var item = await result.ModelAsync();
    const auto Domain = Gs2->Stamina->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Stamina(
        "stamina-0001" // staminaName
    );
    const auto Future = Domain->Apply(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.stamina.namespace_(
        "namespace-0001"
    ).me(game_session).stamina(
        "stamina-0001"
    )

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

var result = async_result.result

응용 예: 마을 만들기 게임의 채집 기능

스태미나의 구조를 응용하면, 마을 만들기 게임의 채집 기능도 구현할 수 있습니다. 스태미나 모델에 건축물을 설정하고, 스태미나 값을 건축물의 생산량으로 표현함으로써, 시간 경과에 따라 자원이 축적되는 채집 시스템을 구현할 수 있습니다.

예를 들어, “철광석을 생산하는 광산” 을 하나의 스태미나 모델로 다루어, 회복 간격을 채굴 간격, 회복량을 1회당 채굴량, 최대값을 창고의 상한으로 표현할 수 있습니다. 플레이어가 회수 액션을 수행했을 때 Consume 으로 전량을 소비하고, 얻은 포인트 수를 GS2-Inventory 에 아이템으로 저장하는 구성이 가능합니다.

상세 레퍼런스