GS2-Enhance

강화 기능

이 기능은 개발자에 따라서는 전혀 이해가 안 될 수도 있는 기능일 것입니다.

하지만 일본을 비롯한 모바일 게임의 사실상 표준 게임 시스템에는 반드시 존재하는 기능입니다. 이 게임 메커니즘 자체는 Game as a Service를 실현하는 데 유용한 지식이 될 것이므로, 이해가 어려운 개발자를 위해 게임 메커니즘에 대한 해설을 추가합니다.

강화 기능의 게임 메커니즘에 대해 충분한 지식이 있는 경우에는 이어지는 해설 섹션은 건너뛰어도 무방합니다.

강화 기능의 게임 메커니즘

강화 기능 자체는 매우 단순한 게임 시스템으로, 소재 아이템을 소비함으로써 대상의 경험치를 가산할 수 있는 구조입니다. 배틀에 참가해 경험치를 얻는 방법뿐만 아니라, 다양한 방법으로 캐릭터나 장비를 육성할 수 있게 되어 있습니다. (장비에도 레벨이 있습니다)

그럼 이 게임 시스템이 왜 Game as a Service를 실현하는 데 한몫하고 있는지 설명해 보겠습니다.

한마디로 말하면, 플레이 시간을 늘리는 데 활약하고 있습니다.

개발 속도보다 게임 플레이 속도가 압도적으로 빠르다는 것은 여러분도 충분히 이해하고 계실 것입니다. 우리 게임 개발자가 3년에 걸쳐 개발한 게임을 플레이어는 10시간 만에 끝내버리는 것입니다. 하지만 이 격차를 메우지 않으면 Game as a Service는 성립하지 않습니다. 즉, 플레이어의 콘텐츠 소비 속도에 디버프를 걸 필요가 있습니다. 그 마법이 바로 강화 게임 시스템입니다.

일반적인 Game as a Service 게임은 매달 한 번은 이벤트를 실시합니다. 그 이벤트에는 어떤 형태로든 보스가 존재하며, 플레이어는 그 보스를 이벤트 기간 동안 반복해서 토벌하게 됩니다. 보스는 플레이어의 성장 단계에 맞춰 여러 난이도를 준비해 두고, 토벌함으로써 캐릭터나 장비의 강화 소재를 얻을 수 있습니다.

모은 강화 소재를 사용해 캐릭터나 장비의 경험치로 전환해 성장시킵니다. 캐릭터나 장비를 성장시키면 더 높은 난이도의 보스에 도전할 수 있게 됩니다.

이렇게 함으로써 모든 플레이어가 이벤트에 참여하면서, 자신의 캐릭터 성장 단계에 맞는 강화 소재를 입수하고, 캐릭터나 장비를 성장시키는 재미를 즐길 수 있습니다.

실제로는 강화 소재를 보스가 직접 드롭하는 경우는 적을 수도 있습니다. 대신 이벤트 기간에만 도전할 수 있는 가챠가 있어, 그 가챠를 뽑기 위한 포인트를 모아 가챠를 통해 소재를 얻거나 이벤트 기간에만 개점하는 상점에서 보스를 토벌해 얻을 수 있는 게임 내 통화를 모아 상점에서 강화 소재를 구매하는 등 플레이어의 판단으로 어디를 우선해서 강화할지 결정할 수 있도록 하는 장치는 있지만, 최종적으로는 플레이어가 캐릭터 육성에 필요한 시간을 늘려 플레이 시간을 부풀리게 됩니다.

이벤트를 통해 일관되게 변하지 않는 것은 플레이어의 캐릭터가 강해진다는 점입니다. 이벤트가 끝나면 강해진 캐릭터를 사용해 더 높은 난이도의 상시 콘텐츠를 즐기는 등, 다음 재미로 이어집니다.

graph TD
  BossBattle["Boss Battle"] -- Acquire Event Point --> Shop
  Shop -- Buy Enhance Materials --> Enhance["Enhance Character"]
  Enhance -- More Formidable --> BossBattle

아키텍처

강화는 소재가 되는 아이템을 관리하는 GS2-Inventory와, 강화 대상인 캐릭터나 장비를 관리하는 GS2-Inventory 또는 GS2-Dictionary. 그리고 그 캐릭터나 장비의 경험치·레벨을 관리하는 GS2-Experience를 GS2-Enhance를 통해 조작함으로써 실현하고 있습니다.

현재 GS2에서는 1회의 API 요청으로 소비와 경험치 가산을 완결하는 DirectEnhance를 이용한 강화를 권장하고 있습니다. 이전에는 트랜잭션 자동 실행 기능이 없었기 때문에, 대성공이 나올 때까지 통신을 차단하고 다시 시도하는 행위를 방지할 목적으로, 준비·실행·완료 보고 단계로 나누어진 강화 프로세스(Progress)를 권장했지만, 지금은 트랜잭션 자동 실행이나 원자적 커밋(atomic commit) 구조가 갖추어져 그 우려는 해소되었습니다.

actor Player
participant "GS2-Enhance#Namespace"
participant "GS2-Inventory#ItemSet(Material)"
participant "GS2-Experience#Status"
Player -> "GS2-Enhance#Namespace" : Direct Enhance(Materials/Target Character)
"GS2-Enhance#Namespace" -> "GS2-Inventory#ItemSet(Material)" : Get experience value from metadata
"GS2-Enhance#Namespace" -> "GS2-Inventory#ItemSet(Material)" : Consume
"GS2-Enhance#Namespace" -> "GS2-Experience#Status": Add experience(Key: Target Character/Equipment Id)
"GS2-Enhance#Namespace" -> Player : Enhance result

GS2-Enhance에 “강화 대상”, “강화에 사용할 소재"를 파라미터로 하여 강화 실행 API(DirectEnhance)를 호출합니다.

그러면 GS2-Enhance는 소재가 되는 아이템의 마스터 데이터를 GS2-Inventory에서 가져와, 메타데이터 내에 기록되어 있는 소재로 사용했을 때의 경험치량을 취득합니다. 경험치량이 확정되면 아이템을 소비하고 경험치 가산 처리를 GS2-Experience에서 실행합니다.

강화 대상을 지정하고는 있지만, GS2-Experience의 경험치를 관리하는 키로 사용될 뿐이며, 강화 대상의 정보를 직접 사용하는 일은 없습니다.

DirectEnhance와 Progress의 차이

GS2-Enhance에서는 두 가지 강화 흐름을 제공하고 있습니다.

항목 DirectEnhance Progress (Start / End)
API 호출 횟수 1회 2회(시작·종료)
권장 용도 모든 강화 처리 대성공 등 추첨 결과를 연출에 반영하기 위해, 추첨 결과를 먼저 취득해 두고 싶은 경우
원자성 요청 내에서 소비·경험치 가산이 완결 서버에서 추첨한 결과를 유지하고, 이후 End에서 확정
통신 차단에 의한 부정 이용 발생하지 않음 진행 중인 Progress는 동시에 1건만 유지할 수 있어, DeleteProgress를 통해서만 재시도 가능

특별한 요건이 없는 한 DirectEnhance 이용을 권장합니다.

sequenceDiagram
  participant Player
  participant Enhance as GS2-Enhance
  participant Inventory as GS2-Inventory
  participant Experience as GS2-Experience
  Player ->> Enhance: DirectEnhance(rateName, targetItemSetId, materials)
  Enhance ->> Inventory: 메타데이터에서 경험치량 취득
  Enhance ->> Inventory: 소재 아이템 소비
  Enhance ->> Experience: 경험치 가산
  Enhance -->> Player: 가산 경험치·대성공 배율

강화 레이트

강화에 사용할 수 있는 소재나 강화 대상을 한정하기 위해 강화 레이트를 마스터 데이터로 설정해야 합니다.

마스터 데이터에는 소재로 사용할 수 있는 아이템의 GS2-Inventory 네임스페이스 이름이나 인벤토리 이름, 강화 대상으로 사용할 수 있는 아이템의 GS2-Inventory 네임스페이스 이름이나 인벤토리 이름과 같은 정보를 기록합니다.

마스터 데이터는 JSON 형식으로 관리합니다.

여기서는 ItemModel의 메타데이터에 { “experience”: 50 } 와 같은 JSON 형식으로 설정하는 예를 보여드립니다.

RateModel의 acquireExperienceHierarchy에서 JSON의 키(위의 예에서는 experience)를 정의합니다. 또한 acquireExperienceHierarchy에는 계층 구조를 정의할 수 있습니다. 예를 들어 { “aaa”: { “bbb”: { “experienceValue”: 100 } } } 와 같은 구조의 데이터를 메타데이터에 설정하고 싶은 경우에는, acquireExperienceHierarchy에 [ “aaa”, “bbb”, “experienceValue” ] 와 같이 지정합니다.

RateModel의 주요 설정 항목

항목 설명
name 강화 레이트 이름
targetInventoryModelId 강화 대상 아이템이 저장되어 있는 GS2-Inventory의 인벤토리 모델 ID
materialInventoryModelId 강화 소재 아이템이 저장되어 있는 GS2-Inventory의 인벤토리 모델 ID
acquireExperienceHierarchy 소재 아이템의 메타데이터에서 경험치량을 꺼내기 위한 JSON 경로
acquireExperienceSuffix 경험치 모델 이름 키에 부여하는 접미사(예: :level)
experienceModelId 경험치를 가산할 GS2-Experience의 경험치 모델 ID
bonusRates 대성공 시의 배율과 추첨 가중치

GS2-Enhance RateModel 마스터 데이터 메타데이터에 경험치를 설정하는 예:


{
  "version": "2020-08-22",
  "rateModels": [
    {
      "name": "enhanceRate",
      "description": "",
      "metadata": "",
      "targetInventoryModelId": "grn:gs2:ap-northeast-1:YourOwnerId:inventory:enhance-inventory:model:character",
      "acquireExperienceSuffix": ":level",
      "materialInventoryModelId": "grn:gs2:ap-northeast-1:YourOwnerId:inventory:enhance-inventory:model:material",
      "acquireExperienceHierarchy": [
        "experience"
      ],
      "experienceModelId": "grn:gs2:ap-northeast-1:YourOwnerId:experience:enhance-experience:model:character",
      "bonusRates": [
        {
          "rate": 2,
          "weight": 1
        },
        {
          "rate": 1,
          "weight": 1
        }
      ]
    }
  ]
}

bonusRates에는 여러 엔트리를 설정할 수 있으며, 각 엔트리는 추첨 가중치(weight)와 배율(rate)을 가집니다. 위의 예에서는 “2배 경험치"와 “등배 경험치"가 동일한 가중치로 추첨되어, 대략 50%의 확률로 대성공(2배)이 발생합니다.

GS2-Experience ExperienceModel 마스터 데이터 예:

{
  "version": "2019-01-11",
  "experienceModels": [
    {
      "name": "character",
      "metadata": "CHARACTER",
      "defaultExperience": 0,
      "defaultRankCap": 50,
      "maxRankCap": 80,
      "rankThreshold": {
        "metadata": "RANK_THRESHOLD",
        "values": [
          100,
          300,
          500,
          1000
        ]
      }
    }
  ]
}

GS2-Inventory ItemModel 마스터 데이터 메타데이터에 경험치를 설정하는 예:

{
  "version": "2019-02-05",
  "inventoryModels": [
    {
      "name": "character",
      "initialCapacity": 1,
      "maxCapacity": 1,
      "protectReferencedItem": false,
      "itemModels": [
        {
          "name": "character-0001",
          "stackingLimit": 1,
          "allowMultipleStacks": false,
          "sortValue": 0
        }
      ]
    },
    {
      "name": "material",
      "metadata": "",
      "initialCapacity": 10,
      "maxCapacity": 10,
      "protectReferencedItem": false,
      "itemModels": [
        {
          "name": "material-0001",
          "metadata": "{\"experience\":50}",
          "stackingLimit": 99,
          "allowMultipleStacks": false,
          "sortValue": 0
        }
      ]
    }
  ]
}

한계 돌파 (Unleash)

같은 종류의 아이템 등을 소재로 소비해, 대상의 레벨 상한을 끌어올리는 “한계 돌파” 기능도 제공하고 있습니다. GS2-Grade가 관리하는 그레이드를 1 올리고, 이에 연동하여 GS2-Experience가 관리하는 rankCap(랭크 상한)을 끌어올리는 구조로, UnleashRateModel의 마스터 데이터로 다음을 설정합니다.

  • 한계 돌파 대상 인벤토리 모델(targetInventoryModelId)
  • 연결된 그레이드 모델(GS2-Grade의 gradeModelId)
  • 각 그레이드에 도달하기 위해 필요한 소재(gradeEntries)

이를 통해 “같은 캐릭터를 4명 모으면 한계 돌파를 할 수 있다”, “같은 부위의 장비나 전용 소재 중 하나를 소비하여 장비를 합성한다” 같은 게임 메커니즘을 구현할 수 있습니다.

gradeEntries 각 항목의 gradeValue는 한계 돌파로 도달하는 그레이드입니다. 대상의 현재 그레이드가 2라면 gradeValue가 3인 항목의 조건이 사용됩니다. GS2-Grade의 그레이드는 1부터 시작합니다(어떤 defaultGrades에도 해당하지 않는 대상은 그레이드 1부터 시작합니다).

소재 조건의 종류

항목마다 type으로 소재 조건의 작성 방식을 선택합니다. 그레이드마다 섞어서 사용할 수 있습니다.

type 설명
simple(기본값) 대상과 같은 종류의 아이템을 needCount개 소비합니다. 소재는 실행 시 materials로 아이템 세트를 지정합니다. 같은 종류인지는 GS2-Grade의 gradeUpPropertyIdRegex로 판정합니다.
recipe recipes에 정의한 여러 레시피 중에서 플레이어가 하나를 선택하여 실행합니다. 선택한 레시피의 소재는 모두 소비됩니다.

레시피

레시피의 소재는 소비 방식을 materialType으로 선택하고, 종류별 설정을 가집니다.

materialType 소비 방식 설정
individual 플레이어가 선택한 아이템 세트(장비의 복제 등)를 1개씩 소비합니다. 소재는 대상과 같은 인벤토리 모델에 속해 있어야 합니다. individualSetting
quantity 설정으로 정해지는 아이템(범용 강화 소재 등)을 수량으로 소비합니다. 플레이어는 아이템을 선택하지 않습니다. 일반 인벤토리와 심플 인벤토리 모두 사용할 수 있습니다. quantitySetting

individualSetting의 항목은 다음과 같습니다.

항목 설명
matchType sameItem: 대상과 같은 아이템 모델만. sameGroup: 대상과 같은 그룹 키를 가진 아이템 모델.
gradeCondition any: 소재의 그레이드를 묻지 않음. sameAsTarget: 소재의 그레이드가 대상의 현재 그레이드와 같음. equal: 소재의 그레이드가 gradeValue와 같음. 소재의 그레이드는 트랜잭션 안에서 GS2-Grade가 검증합니다.
count 소비할 아이템 세트의 수

quantitySetting의 항목은 다음과 같습니다.

항목 설명
matchType specified: itemModelId로 지정한 아이템을 소비. sameGroup: materialInventoryModelId의 인벤토리 안에서 대상과 같은 그룹 키를 가진 아이템을 소비(해당하는 아이템 모델은 정확히 1개여야 합니다).
itemModelId / materialInventoryModelId 소비할 아이템 모델 / 소재를 찾을 인벤토리 모델. 심플 인벤토리의 GRN도 지정할 수 있습니다.
count 소비할 수량

레시피의 targetGroupKeys를 지정하면, 대상의 그룹 키가 그 안에 포함될 때만 해당 레시피를 사용할 수 있습니다. 비어 있으면 어떤 대상에도 사용할 수 있습니다.

그룹 키

“같은 부위” 같은 분류는 아이템 모델의 메타데이터(JSON)에 작성한 그룹 키로 표현합니다. UnleashRateModel의 groupKeyHierarchy로 메타데이터의 어느 계층에 그룹 키가 있는지를 지정합니다. 값은 문자열이어야 합니다.

예를 들어 아이템 모델의 메타데이터가 다음과 같다면 groupKeyHierarchy에 ["unleash", "group"]을 지정합니다.

{ "unleash": { "group": "weapon" } }

부위별 범용 소재의 아이템 모델에도 같은 형태로 그룹 키를 작성해 두면, quantitySetting의 sameGroup으로 “대상과 같은 부위의 범용 소재"를 하나의 레시피로 표현할 수 있습니다.

마스터 데이터 예

그레이드 2에 도달하려면 같은 장비를 1개, 그레이드 3에 도달하려면 같은 부위의 장비(그레이드 1) 또는 부위별 범용 소재 2개 중 하나를 소비하는 예입니다.

{
  "name": "equipment",
  "targetInventoryModelId": "grn:gs2:{region}:{ownerId}:inventory:namespace-0001:model:equipment",
  "gradeModelId": "grn:gs2:{region}:{ownerId}:grade:namespace-0001:model:equipment",
  "groupKeyHierarchy": ["unleash", "group"],
  "gradeEntries": [
    {
      "gradeValue": 2,
      "type": "recipe",
      "recipes": [
        {
          "name": "merge",
          "materials": [
            {
              "name": "same",
              "materialType": "individual",
              "individualSetting": { "matchType": "sameItem", "gradeCondition": "sameAsTarget", "count": 1 }
            }
          ]
        }
      ]
    },
    {
      "gradeValue": 3,
      "type": "recipe",
      "recipes": [
        {
          "name": "fodder",
          "materials": [
            {
              "name": "fodder",
              "materialType": "individual",
              "individualSetting": { "matchType": "sameGroup", "gradeCondition": "equal", "gradeValue": 1, "count": 1 }
            }
          ]
        },
        {
          "name": "material",
          "targetGroupKeys": ["weapon", "armor"],
          "materials": [
            {
              "name": "material",
              "materialType": "quantity",
              "quantitySetting": {
                "matchType": "sameGroup",
                "materialInventoryModelId": "grn:gs2:{region}:{ownerId}:inventory:namespace-0001:simple:model:unleash-material",
                "count": 2
              }
            }
          ]
        }
      ]
    }
  ]
}

실행 시 지정

type이 recipe인 항목에서는 한계 돌파 실행 시 recipeName으로 레시피를 지정하고, recipeMaterials로 individual 소재마다 소비할 아이템 세트를 지정합니다. quantity 소재는 지정할 필요가 없습니다.

{
  "targetItemSetId": "grn:...:item:sword-0001:itemSet:target",
  "recipeName": "merge",
  "recipeMaterials": [
    { "name": "same", "itemSetIds": ["grn:...:item:sword-0001:itemSet:material"] }
  ]
}

다음의 경우에는 트랜잭션을 발행하기 전에 오류가 됩니다.

  • 소재에 대상 자신이 포함되어 있거나, 같은 아이템 세트가 중복되어 있다
  • 소재의 수가 레시피의 count와 일치하지 않거나, 레시피에 없는 소재 이름을 지정했다
  • 소재가 대상과 다른 인벤토리 모델에 속해 있거나, matchType의 조건에 일치하지 않는다
  • targetGroupKeys에 대상의 그룹 키가 포함되지 않거나, 대상의 그룹 키를 메타데이터에서 가져올 수 없다
  • quantitySetting의 sameGroup에서 해당하는 아이템 모델이 0개이거나 2개 이상이다

소재로 소비한 아이템 세트가 가지고 있던 경험치나 그레이드는 대상에게 인계되지 않습니다.

스크립트 트리거

네임스페이스에 enhanceScript를 설정하면 강화 실행 전후로 커스텀 스크립트를 호출할 수 있습니다. 처리의 허가·거부나 입수 경험치의 오버라이드가 가능하며, 실행 방식은 동기·비동기를 선택할 수 있습니다. 비동기에서는 GS2-Script나 Amazon EventBridge를 이용한 외부 연동에도 대응합니다.

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

  • enhanceScript(완료 통지: enhanceDone): 강화 실행 전후

스크립트 내에서는 강화 대상·소재 아이템·추첨되는 배율을 참조할 수 있어, 게임 밸런스 조정이나 이벤트 기간 중 부스트 배율 변경과 같은 용도로 활용할 수 있습니다.

마스터 데이터 운용

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

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

  • RateModel: 강화 소재나 대상의 정의
  • UnleashRateModel: 상한 해제용 레이트 정의

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

트랜잭션 액션

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

  • 소비 액션: 실행 중인 강화 진행 정보(Progress)의 삭제
  • 입수 액션: 즉시 강화(DirectEnhance)의 실행, 한계 돌파(Unleash)의 실행, 강화(Progress)의 시작

“즉시 강화(DirectEnhance)의 실행"을 입수 액션으로 이용함으로써, 특정 퀘스트 클리어 보상이나 상점에서의 아이템 구매 시 보상으로, 직접 캐릭터나 장비에 경험치를 부여하는 처리가 가능해집니다. 또한 “한계 돌파(Unleash)“를 보상으로 설정함으로써, 특정 미션 달성 시 자동으로 레벨 상한을 끌어올리는 운용도 가능합니다.

구현 예제

강화 레이트 목록 취득

    var items = await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).RateModelsAsync(
    ).ToListAsync();
    const auto It = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->RateModels();
    TArray<Gs2::UE5::Enhance::Model::FEzRateModelPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.enhance.namespace_(
        "namespace-0001"
    ).rate_models(
    )

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

var items = async_result.result

강화 실행(DirectEnhance)

materials에는 소비할 소재 아이템의 ItemSetId와 수량을 지정합니다. 강화 대상으로 targetItemSetId에 강화하고 싶은 캐릭터·장비의 ItemSetId를 전달합니다.

    var result = await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Enhance(
    ).EnhanceAsync(
        rateName: "rate-0001",
        targetItemSetId: "item-set-0001",
        materials: new [] {
            new Gs2.Unity.Gs2Enhance.Model.EzMaterial
            {
                MaterialItemSetId = "material-0001",
                Count = 1,
            },
        }
    );

    var transaction = result;
    await transaction.WaitAsync();
    const auto Domain = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Enhance(
    );
    const auto Future = Domain->Enhance(
        "rate-0001",
        "item-set-0001",
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::Enhance::Model::FMaterial>>>();
            v->Add(MakeShared<Gs2::Enhance::Model::FMaterial>()
                ->WithMaterialItemSetId(TOptional<FString>("material-0001"))
                ->WithCount(TOptional<int32>(1)));
            return v;
        }()
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    const auto Transaction = Future->GetTask().Result();
    const auto Future2 = Transaction->Wait();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
var domain = ez.enhance.namespace_(
        "namespace-0001"
    ).me(game_session).enhance(
    )

var async_result = await domain.enhance(
    "rate-0001", # rate_name
    "item-set-0001", # target_item_set_id
    [
        Gs2EnhanceEzMaterial.new()
            .with_material_item_set_id("material-0001")
            .with_count(1),
    ], # materials
    null # config
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

강화(Progress)의 시작

추첨 결과를 먼저 취득해 두고 싶은 경우의 흐름입니다. 진행 중인 Progress는 1명의 사용자당 1건만 유지할 수 있습니다.

    var result = await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Progress(
    ).StartAsync(
        rateName: "rate-0001",
        targetItemSetId: "item-set-0001",
        materials: new [] {
            new Gs2.Unity.Gs2Enhance.Model.EzMaterial
            {
                MaterialItemSetId = "material-0001",
                Count = 1,
            },
        }
    );
    const auto Future = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Progress(
    )->Start(
        "rate-0001", // rateName
        "item-set-0001", // targetItemSetId
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::Enhance::Model::FMaterial>>>();
            v->Add(MakeShared<Gs2::Enhance::Model::FMaterial>()
                ->WithMaterialItemSetId(TOptional<FString>("material-0001"))
                ->WithCount(TOptional<int32>(1)));
            return v;
        }()
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.enhance.namespace_(
        "namespace-0001"
    ).me(game_session).progress(
    )

var async_result = await domain.start(
    "character-level", # rate_name
    "item-set-0001", # target_item_set_id
    [
        Gs2EnhanceEzMaterial.new()
            .with_material_item_set_id("material-0001")
            .with_count(1),
    ], # materials
    null, # force
    null # config
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

진행 중인 강화 정보 취득

추첨이 완료된 배율(대성공 플래그)이나 입수 경험치량을 취득해, 강화 연출에 활용할 수 있습니다.

    var item = await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Progress(
    ).ModelAsync();

    var experienceValue = item.ExperienceValue;
    var rate = item.Rate;
    const auto Domain = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Progress(
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    const auto Result = Future->GetTask().Result();
    const auto ExperienceValue = Result->GetExperienceValue();
    const auto Rate = Result->GetRate();
var domain = ez.enhance.namespace_(
        "namespace-0001"
    ).me(game_session).progress(
    )

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

var result = async_result.result

강화(Progress)의 완료

Start로 시작한 강화를 End로 확정하여, 소비·경험치 가산을 실행합니다.

    var result = await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Progress(
    ).EndAsync();

    await result.WaitAsync();
    const auto Future = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Progress(
    )->End();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    const auto Transaction = Future->GetTask().Result();
    const auto Future2 = Transaction->Wait();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
var domain = ez.enhance.namespace_(
        "namespace-0001"
    ).me(game_session).progress(
    )

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

var result = async_result.result

진행 중인 강화 정보 파기

통신이 끊겨 완료 보고를 할 수 없게 된 경우 등의 복구에 사용합니다.

    await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Progress(
    ).DeleteProgressAsync();
    const auto Future = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Progress(
    )->DeleteProgress();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.enhance.namespace_(
        "namespace-0001"
    ).me(game_session).progress(
    )

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

var result = async_result.result

기타 기능

강화의 대성공

강화 사양으로서, 일정 확률로 “대성공"이 발생해 입수할 수 있는 경험치량이 1.5배나 2배가 되는 사양이 있는 경우가 있습니다. GS2-Enhance에서는 강화 레이트로서 대성공이 발생할 확률과 그 경우의 경험치 획득량 배율을 설정할 수 있습니다.

bonusRates의 weight 합계값에 대한 각 엔트리의 weight 비율이 그 배율의 발생 확률이 됩니다.

Config를 사용한 스크립트로의 파라미터 전달

EnhanceAsync / StartAsync에는 config 파라미터를 지정할 수 있어, 스크립트 트리거 실행 시 임의의 키와 값의 쌍을 전달할 수 있습니다. 게임 내 이벤트 부스트 상태나 플레이어가 선택한 강화 모드와 같은 정보를 스크립트에 전달할 수 있습니다.

투기적 실행(Speculative Execute)

speculativeExecute 인수(기본값 true)를 활성화하면, API 요청의 응답을 기다리기 전에 클라이언트 측 캐시를 갱신하여, UI 상에서는 즉시 경험치 가산 후의 상태를 표시할 수 있습니다. 통신 지연의 영향을 덜 받는 쾌적한 조작감을 실현할 수 있습니다.

상세 레퍼런스