Documentation index for AI agents

GS2-Inventory

소지품 관리 기능

플레이어가 소지한 아이템 정보를 관리합니다.

아이템 관리에는 3가지 방식이 있습니다. 첫 번째는 스탠다드 인벤토리, 두 번째는 심플 인벤토리, 세 번째는 거대 인벤토리입니다.

스탠다드 인벤토리는 다음과 같은 기능을 제공합니다

  • 인벤토리 용량 제한
  • 동일한 아이템을 일정 수량마다 여러 스택으로 분할
  • 아이템에 유효기간 설정

심플 인벤토리는 스탠다드 인벤토리가 가진 기능을 갖고 있지 않습니다. 그 대신 여러 아이템의 증감 처리를 한 번에 수행할 수 있습니다. 심플 인벤토리를 잘 활용하면 API 호출 횟수를 줄일 수 있습니다.

거대 인벤토리는 심플 인벤토리와 마찬가지로 스탠다드 인벤토리가 가진 기능을 갖고 있지 않습니다. 심플 인벤토리처럼 여러 아이템의 증감 처리를 한 번에 수행할 수도 없습니다. 그 대신 아이템 소지 수량을 64bit 정수 범위를 초과하여 보유할 수 있습니다.

스탠다드 인벤토리

인벤토리

플레이어가 소지하는 가방에 해당하는 엔티티입니다. 가방에는 용량을 설정할 수 있으며, 그 용량을 초과하는 아이템은 저장할 수 없습니다.

아이템

아이템은 소지품의 종류를 정의합니다. 아이템에는 스택 가능한 최대 수량을 지정할 수 있습니다.

예를 들어 포션이라는 아이템 종류가 존재하고 최대 99개까지 스택 가능하도록 설정한 경우, 인벤토리 용량 소비량 1로 최대 99개까지 포션을 소지할 수 있습니다.

또한 아이템에는 여러 스택을 소지할 수 있는지 여부를 지정할 수 있습니다. 여러 스택 소지를 가능하도록 설정하면 100개 이상의 포션을 소지할 수 있게 되며, 예를 들어 150개의 포션을 소지하고 있는 경우 99개 스택된 포션과 51개 스택된 포션이라는 2개의 엔트리가 생성되어 인벤토리 용량이 2 소비됩니다.

여러 스택 소지를 불가능하도록 설정한 경우, 99개를 초과하는 포션은 소지할 수 없게 되며 그 이상 포션을 입수하더라도 폐기됩니다.

아이템의 유효기간

아이템에는 유효기간을 설정할 수 있습니다. 유효기간을 설정한 아이템은 설정 시각이 지나면 자동으로 인벤토리에서 삭제됩니다.

유효기간이 설정된 아이템은 유효기간마다 서로 다른 스택이 생성되며, 각각 인벤토리 용량을 소비합니다. 따라서 여러 스택을 소지할 수 없는 아이템에 유효기간을 붙여 배포하면, 가장 먼저 입수한 유효기간의 아이템(또는 가장 먼저 입수한 유효기간이 없는 아이템)을 제외하고는 폐기됩니다.

아이템을 사용할 때 유효기간(아이템 세트 이름)을 명시적으로 지정하지 않으면 유효기간이 가까운 아이템부터 우선적으로 소비됩니다.

아이템 세트

한 종류의 아이템을 여러 스택으로 관리할 수 있도록, 아이템 종류를 묶은 아이템 세트라는 엔티티가 존재합니다. 이 엔티티는 스택마다 아이템 종류를 나타내는 ID와는 별도로 스택 고유의 ID를 갖습니다. 이 스택 고유 ID를 사용하면 “포션 x 99"와 “포션 x 51"이라는 각각의 스택을 명확히 구분할 수 있습니다.

참조원 (Reference Of)

아이템 세트에는 “참조원” 정보를 부여할 수 있습니다. 이는 특정 아이템이 다른 엔티티(예: 캐릭터 장비, 파티 편성, 마켓 출품 등)에서 사용되고 있음을 나타내기 위해 사용됩니다. 참조원이 하나 이상 설정된 아이템 세트는 소지 수량이 충분하더라도 소비하거나 삭제할 수 없게 됩니다. 이를 통해 장비 중인 아이템을 실수로 판매하거나 강화 소재로 사용해 버리는 실수를 시스템 수준에서 방지할 수 있습니다.

포션을 소비할 때 포션을 나타내는 아이템 ID에 더해 스택 고유 ID를 지정함으로써 어느 스택에서 소비할지 명시할 수 있습니다. 스택 고유 ID는 생략 가능하며, 생략한 경우에는 수량이 가장 적은 스택부터 우선적으로 사용됩니다.

심플 인벤토리

심플 인벤토리

플레이어가 소지하는 가방에 해당하는 엔티티입니다. 여러 심플 아이템을 묶는 존재로, 심플 인벤토리는 특별한 프로퍼티를 갖지 않습니다.

심플 아이템

아이템은 소지품의 종류를 정의합니다.

거대 인벤토리

거대 인벤토리

플레이어가 소지하는 가방에 해당하는 엔티티입니다. 여러 거대 아이템을 묶는 존재로, 거대 인벤토리는 특별한 프로퍼티를 갖지 않습니다.

거대 아이템

아이템은 소지품의 종류를 정의합니다.

스크립트 트리거

네임스페이스에 acquireScript, overflowScript, consumeScript, simpleItemAcquireScript, simpleItemConsumeScript, bigItemAcquireScript, bigItemConsumeScript를 설정하면 아이템 입수·소비 처리 전후에 커스텀 스크립트를 호출할 수 있습니다. 스크립트는 동기·비동기 실행 방식을 선택할 수 있으며, 비동기의 경우 GS2-Script나 Amazon EventBridge를 통한 외부 처리에도 대응합니다.

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

  • acquireScript(완료 통지: acquireDone): 스탠다드 아이템 입수 전후
  • overflowScript(완료 통지: overflowDone): 용량 초과 시 전후
  • consumeScript(완료 통지: consumeDone): 스탠다드 아이템 소비 전후
  • simpleItemAcquireScript(완료 통지: simpleItemAcquireDone): 심플 아이템 입수 전후
  • simpleItemConsumeScript(완료 통지: simpleItemConsumeDone): 심플 아이템 소비 전후
  • bigItemAcquireScript(완료 통지: bigItemAcquireDone): 거대 아이템 입수 전후
  • bigItemConsumeScript(완료 통지: bigItemConsumeDone): 거대 아이템 소비 전후

마스터 데이터 운용

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

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

  • InventoryModel: 인벤토리 용량이나 종류를 정의
  • ItemModel: 스택 상한이나 유효기간을 정의

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

버프에 의한 보정

GS2-Buff와 연동하면 인벤토리의 currentInventoryMaxCapacityAcquireItemSetByUserId, ConsumeItemSetByUserId, AcquireSimpleItemsByUserId, ConsumeSimpleItemsByUserId, AcquireBigItemByUserId, ConsumeBigItemByUserId의 입수량·소비량을 버프로 보정할 수 있습니다. 이벤트나 캠페인에 맞춰 용량이나 입수·소비량을 유연하게 조정할 수 있습니다.

트랜잭션 액션

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

스탠다드 인벤토리

  • 검증 액션: 인벤토리 최대 용량 검증, 아이템 소지 수량 검증, 참조원 검증
  • 소비 액션: 아이템 세트 소비
  • 입수 액션: 용량 가산·설정, 아이템 세트 입수(등급 지정 포함), 참조원 추가·삭제

“참조원 추가·삭제"를 입수 액션으로 활용하면 장비 변경이나 편성 갱신을 트랜잭션 내에서 안전하게 수행함과 동시에 아이템의 보호 상태를 전환할 수 있습니다.

심플 인벤토리

  • 검증 액션: 심플 아이템 소지 수량 검증
  • 소비 액션: 심플 아이템 소비
  • 입수 액션: 심플 아이템 입수·설정

거대 인벤토리

  • 검증 액션: 거대 아이템 소지 수량 검증
  • 소비 액션: 거대 아이템 소비
  • 입수 액션: 거대 아이템 입수·설정

구현 예제

아이템 입수

아이템 입수는 게임 엔진용 SDK에서는 처리할 수 없습니다.

아이템 소비

이 API로 아이템 소비 처리를 수행하는 것은 권장하지 않습니다.

GS2-Exchange / GS2-Showcase / GS2-Quest와 같은 서비스를 통해 아이템 소비를 수행하는 대신, 어떠한 처리를 실행하는 것을 권장합니다.

스탠다드

    var result = await gs2.Inventory.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Inventory(
        inventoryName: "inventory-0001"
    ).ItemSet(
        itemName: "item-0001",
        itemSetName: null
    ).ConsumeAsync(
        consumeCount: 1L
    );
    const auto Future = Gs2->Inventory->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Inventory(
        "inventory-0001" // inventoryName
    )->ItemSet(
        "item-0001", // itemName
        nullptr // itemSetName
    )->Consume(
        1L
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.inventory.namespace_(
        "namespace-0001"
    ).me(game_session).inventory(
        "inventory-0001"
    ).item_set(
        "item-0001",
        null
    )

var async_result = await domain.consume(
    1 # consume_count
)
if async_result.error != null:
    if async_result.error.type == "ConflictException":
        # 아이템 조작 처리가 충돌했습니다. 재시도가 필요합니다
        pass
    if async_result.error.type == "InsufficientException":
        # 아이템 소지 수량이 부족합니다
        pass
    push_error(str(async_result.error))
    return

var result = async_result.result

심플

    var result = await gs2.Inventory.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SimpleInventory(
        inventoryName: "inventory-0001"
    ).SimpleItem(
        itemName: "item-0001"
    ).ConsumeAsync(
        consumeCount: 1L
    );
    const auto Future = Gs2->Inventory->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->SimpleInventory(
        "inventory-0001" // inventoryName
    )->SimpleItem(
        "item-0001" // itemName
    )->Consume(
        1L
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.inventory.namespace_(
        "namespace-0001"
    ).me(game_session).simple_inventory(
        "inventory-0001"
    )

var async_result = await domain.consume_simple_items(
    [
        Gs2InventoryEzConsumeCount.new()
            .with_item_name("item-0001")
            .with_count(5),
        Gs2InventoryEzConsumeCount.new()
            .with_item_name("item-0002")
            .with_count(3),
    ] # consume_counts
)
if async_result.error != null:
    if async_result.error.type == "ConflictException":
        # 아이템 조작 처리가 충돌했습니다. 재시도가 필요합니다
        pass
    if async_result.error.type == "InsufficientException":
        # 아이템 소지 수량이 부족합니다
        pass
    push_error(str(async_result.error))
    return

var result = async_result.result

거대

    var result = await gs2.Inventory.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).BigInventory(
        inventoryName: "inventory-0001"
    ).BigItem(
        itemName: "item-0001"
    ).ConsumeAsync(
        consumeCount: "1"
    );
    const auto Future = Gs2->Inventory->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->BigInventory(
        "inventory-0001" // inventoryName
    )->BigItem(
        "item-0001" // itemName
    )->Consume(
        "1"
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
var domain = ez.inventory.namespace_(
        "namespace-0001"
    ).me(game_session).big_inventory(
        "inventory-0001"
    ).big_item(
        "item-0001"
    )

var async_result = await domain.consume_big_item(
    "1234567890123456789012345678901234567890" # consume_count
)
if async_result.error != null:
    if async_result.error.type == "ConflictException":
        # 아이템 조작 처리가 충돌했습니다. 재시도가 필요합니다
        pass
    if async_result.error.type == "InsufficientException":
        # 아이템 소지 수량이 부족합니다
        pass
    push_error(str(async_result.error))
    return

var result = async_result.result

인벤토리 정보 취득

스탠다드

    var item = await gs2.Inventory.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Inventory(
        inventoryName: "inventory-0001"
    ).ModelAsync();
    const auto item = Gs2->Inventory->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Inventory(
        "inventory-0001" // inventoryName
    )->Model();
var domain = ez.inventory.namespace_(
        "namespace-0001"
    ).me(game_session).inventory(
        "inventory-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.Inventory.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Inventory(
        inventoryName: "item"
    ).ItemSetsAsync(
    ).ToListAsync();
    const auto It = Gs2->Inventory->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Inventory(
        "item" // inventoryName
    )->ItemSets(
    );
    TArray<Gs2::UE5::Inventory::Model::FEzItemSetPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.inventory.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).inventory(
        "item"
    ).item_sets(
    )

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

var items = async_result.result

심플

    var items = await gs2.Inventory.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SimpleInventory(
        inventoryName: "item"
    ).SimpleItemsAsync(
    ).ToListAsync();
    const auto It = Gs2->Inventory->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->SimpleInventory(
        "item" // inventoryName
    )->SimpleItems(
    );
    TArray<Gs2::UE5::Inventory::Model::FEzSimpleItemPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.inventory.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).simple_inventory(
        "item"
    ).simple_items(
    )

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

var items = async_result.result

거대

    var items = await gs2.Inventory.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).BigInventory(
        inventoryName: "item"
    ).BigItemsAsync(
    ).ToListAsync();
    const auto It = Gs2->Inventory->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->BigInventory(
        "item" // inventoryName
    )->BigItems(
    );
    TArray<Gs2::UE5::Inventory::Model::FEzBigItemPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
var iterator = ez.inventory.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).big_inventory(
        "item"
    ).big_items(
    )

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

var items = async_result.result

인벤토리 용량 확대

인벤토리 용량 확대는 게임 엔진용 SDK에서는 처리할 수 없습니다.

소지 증명 서명 취득

GS2 내 다른 마이크로서비스와 연동할 때, 실제로 GS2-Inventory에서 아이템을 소유하고 있음을 보증하는 데이터가 요구되는 경우가 있습니다.

예를 들어 GS2-Inventory에서 캐릭터의 소지 상태를 관리하고, GS2-Formation에서 파티 편성 상태를 관리한다고 가정합니다. GS2-Formation에 파티 멤버를 설정할 때 “character-0001"이라는 캐릭터를 설정하도록 API 요청을 보내게 되는데, GS2-Formation은 소지 증명 서명과 함께 “character-0001"을 지정하도록 요구합니다.

이를 통해 GS2-Formation은 뒤에서 GS2-Inventory와 통신하여 실제로 소지하고 있는 캐릭터인지 판단할 필요가 없어집니다.

스탠다드

    var result = await gs2.Inventory.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Inventory(
        inventoryName: "inventory-0001"
    ).ItemSet(
        itemName: "item-0001",
        itemSetName: "item-set-0001"
    ).GetItemWithSignatureAsync(
        keyId: "grn:gs2:{region}:{yourOwnerId}:key:namespace-0001:key:key-0001"
    );
    var item = await result.ModelAsync();
    var body = result.Body;
    var signature = result.Signature;
    const auto Future = Gs2->Inventory->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Inventory(
        "inventory-0001" // inventoryName
    )->ItemSet(
        "item-0001", // itemName
        "item-set-0001" // itemSetName
    )->GetItemWithSignature(
        "key-0001"
    );
    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();
    const auto Body = Result->Body;
    const auto Signature = Result->Signature;
var domain = ez.inventory.namespace_(
        "namespace-0001"
    ).me(game_session).inventory(
        "inventory-0001"
    ).item_set(
        "item-0001",
        null
    )

var async_result = await domain.get_item_with_signature(
    "key-0001" # key_id
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

심플

    var result = await gs2.Inventory.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SimpleInventory(
        inventoryName: "inventory-0001"
    ).SimpleItem(
        itemName: "item-0001"
    ).GetSimpleItemWithSignatureAsync(
        keyId: "grn:gs2:{region}:{yourOwnerId}:key:namespace-0001:key:key-0001"
    );
    var item = await result.ModelAsync();
    var body = result.Body;
    var signature = result.Signature;
    const auto Future = Gs2->Inventory->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->SimpleInventory(
        "inventory-0001" // inventoryName
    )->SimpleItem(
        "item-0001" // itemName
    )->GetSimpleItemWithSignature(
        "key-0001"
    );
    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();
    const auto Body = Result->Body;
    const auto Signature = Result->Signature;
var domain = ez.inventory.namespace_(
        "namespace-0001"
    ).me(game_session).simple_inventory(
        "inventory-0001"
    ).simple_item(
        "item-0001"
    )

var async_result = await domain.get_simple_item_with_signature(
    "key-0001" # key_id
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

거대

거대 인벤토리에는 이 기능이 없습니다.

상세 레퍼런스