> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 플레이어 인벤토리 사용

> 서버 권한 기반 클라이언트 및 서버 API를 사용하여 PlayFab 플레이어 인벤토리로 작업하고, 아이템을 구매하고, 통화를 부여하고, 카탈로그 기반 콘텐츠를 관리합니다.

# 플레이어 인벤토리

## 요구 사항

플레이어 인벤토리를 사용하려면 타이틀에 대해 카탈로그가 정의되어 있어야 합니다. 자세한 내용은 [Catalogs](/services/playfab/economy-monetization/economy/items/catalogs) 자습서를 참조하세요.

<Note>
  선택적으로 카탈로그에 대해 스토어를 정의할 수도 있습니다.
</Note>

카탈로그가 게임에서 사용 가능한 모든 아이템의 목록인 반면, 스토어는 고유한 가격 책정 옵션이 있는 카탈로그의 아이템 하위 집합입니다.

카탈로그당 여러 스토어를 정의할 수 있으므로, 사용자 세그멘테이션 또는 기타 요인에 따라 플레이어에게 제시할 별개의 아이템 세트를 가질 수 있습니다.

[**Game Manager**](https://developer.playfab.com/)를 통해, 또는 관리자 **[SetCatalogItems](xref:titleid.playfabapi.com.admin.title-widedatamanagement.setcatalogitems)** 또는 **[UpdateCatalogItems](xref:titleid.playfabapi.com.admin.title-widedatamanagement.updatecatalogitems)** API 호출을 통해 카탈로그를 정의하면 클라이언트와 서버에서 다양한 인벤토리 API 호출을 사용할 수 있습니다.

## API 개요

모든 인벤토리 API 호출은 *서버 권한 기반*으로 안전하게 설계되었습니다. 올바르게 사용하면 고객이 부정 행위를 하거나 획득하지 않은 아이템을 얻을 수 없습니다.

**클라이언트**:

* 가상 통화로 아이템 구매: **[PurchaseItem](xref:titleid.playfabapi.com.client.playeritemmanagement.purchaseitem)**
* 실제 화폐 구매: **[StartPurchase](xref:titleid.playfabapi.com.client.playeritemmanagement.startpurchase), [PayForPurchase](xref:titleid.playfabapi.com.client.playeritemmanagement.payforpurchase), [ConfirmPurchase](xref:titleid.playfabapi.com.client.playeritemmanagement.confirmpurchase)**
* 플레이어가 소유한 아이템 보기: **[GetUserInventory](xref:titleid.playfabapi.com.client.playeritemmanagement.getuserinventory)**
* 아이템 제거: **[ConsumeItem](xref:titleid.playfabapi.com.client.playeritemmanagement.consumeitem), [UnlockContainerInstance](xref:titleid.playfabapi.com.client.playeritemmanagement.unlockcontainerinstance)**
* 아이템 거래: **[OpenTrade](xref:titleid.playfabapi.com.client.trading.opentrade), [GetPlayerTrades](xref:titleid.playfabapi.com.client.trading.getplayertrades), [AcceptTrade](xref:titleid.playfabapi.com.client.trading.accepttrade), [CancelTrade](xref:titleid.playfabapi.com.client.trading.canceltrade)**

**서버**:

* 아이템 선물/부여: **[GrantItemsToUser](xref:titleid.playfabapi.com.server.playeritemmanagement.grantitemstouser)**
* 아이템 보기: **[GetUserInventory](xref:titleid.playfabapi.com.server.playeritemmanagement.getuserinventory)**
* 아이템 수정: **[ModifyItemUses](xref:titleid.playfabapi.com.server.playeritemmanagement.modifyitemuses), [UpdateUserInventoryItemCustomData](xref:titleid.playfabapi.com.server.playeritemmanagement.updateuserinventoryitemcustomdata)**
* 아이템 제거: **[RevokeInventoryItem](xref:titleid.playfabapi.com.server.playeritemmanagement.revokeinventoryitem), [ConsumeItem](xref:titleid.playfabapi.com.server.playeritemmanagement.consumeitem), [UnlockContainerInstance](xref:titleid.playfabapi.com.server.playeritemmanagement.unlockcontainerinstance)**

아래 표시된 예제는 이러한 API 메서드를 호출하는 코드 블록을 보여주고 플레이어 인벤토리에 대한 기본 사용 사례를 설정합니다.

<Note>
  참고로 이 예제들은 PlayFab 기능을 시연하기 위해 만든 예제 게임인 **Unicorn Battle**에서 가져온 것입니다.
</Note>

아래에서 사용된 **AU** 가상 통화는 **Gold**로, 몬스터와 싸워 얻는 무료 통화입니다([Currencies](/services/playfab/economy-monetization/economy-v2/tutorials/currencies) 자습서 참조).

시작하기 전에, 이 가이드의 대부분의 예제에서 사용되고 재사용될 몇 가지 유틸리티 함수를 정의합니다.

```csharp theme={null}
// **** Shared example utility functions ****

// This is typically NOT how you handle success
// You will want to receive a specific result-type for your API, and utilize the result parameters
void LogSuccess(PlayFabResultCommon result) {
    var requestName = result.Request.GetType().Name;
    Debug.Log(requestName + " successful");
}

// Error handling can be very advanced, such as retry mechanisms, logging, or other options
// The simplest possible choice is just to log it
void LogFailure(PlayFabError error) {
    Debug.LogError(error.GenerateErrorReport());
}
```

## 클라이언트 전용 예제: 체력 물약 구매 및 소비

클라이언트 API 호출 순서: [PurchaseItem](xref:titleid.playfabapi.com.client.playeritemmanagement.purchaseitem), [GetUserInventory](xref:titleid.playfabapi.com.client.playeritemmanagement.getuserinventory), [ConsumeItem](xref:titleid.playfabapi.com.server.playeritemmanagement.consumeitem)

먼저 카탈로그에서 아이템을 정의해야 합니다.

<img src="https://mintcdn.com/microsoft-4404708b/N3T1ucKV7zIMBudj/images/playfab/player-progression/player-data/tutorials/playfab-edit-catalog-item.png?fit=max&auto=format&n=N3T1ucKV7zIMBudj&q=85&s=773db3352052dc2e8653fa2bca38fb0b" alt="PlayFab - Economy - Edit Catalog Item" width="1280" height="1400" data-path="images/playfab/player-progression/player-data/tutorials/playfab-edit-catalog-item.png" />

**Health Potion**에 대한 `CatalogItem` 요구 사항은 다음과 같습니다.

* `PurchaseItem`은 양수의 아이템 가격이 필요합니다(`5 AU`).
* `ConsumeItem`은 아이템이 `Consumable`이어야 하며, 양수 아이템 카운트(`3`)가 있어야 합니다.
* 구매하는 플레이어는 가상 통화 잔액에 5 AU 이상이 있어야 합니다.

각 호출의 코드는 아래에 제공됩니다.

```csharp theme={null}
void MakePurchase() {
    PlayFabClientAPI.PurchaseItem(new PurchaseItemRequest {
        // In your game, this should just be a constant matching your primary catalog
        CatalogVersion = "CharacterClasses",
        ItemId = "MediumHealthPotion",
        Price = 5,
        VirtualCurrency = "AU"
    }, LogSuccess, LogFailure);
}

void GetInventory() {
    PlayFabClientAPI.GetUserInventory(new GetUserInventoryRequest(), LogSuccess, LogFailure);
}

void ConsumePotion() {
    PlayFabClientAPI.ConsumeItem(new ConsumeItemRequest {
        ConsumeCount = 1,
        // This is a hex-string value from the GetUserInventory result
        ItemInstanceId = "potionInstanceId"
    }, LogSuccess, LogFailure);
}
```

## 예제: 플레이어에게 컨테이너가 부여되고 열림

API 호출 순서:

* PlayFab 서버 API [GrantItemsToUser](xref:titleid.playfabapi.com.server.playeritemmanagement.grantitemstouser)
* PlayFab 클라이언트 API [UnlockContainerInstance](xref:titleid.playfabapi.com.client.playeritemmanagement.unlockcontainerinstance)

먼저 카탈로그에 컨테이너가 정의되어 있어야 합니다. 이 예제의 컨테이너에는 **CrystalContainer**를 선택했습니다.

이 예제는 또한 키로 컨테이너를 여는 것을 시연합니다. 이는 `UnlockContainerInstance` 호출이 성공하려면 플레이어 인벤토리에도 있어야 하는 *선택적* 아이템입니다.

<img src="https://mintcdn.com/microsoft-4404708b/N3T1ucKV7zIMBudj/images/playfab/player-progression/player-data/tutorials/playfab-edit-catalog-container.png?fit=max&auto=format&n=N3T1ucKV7zIMBudj&q=85&s=9de90fda3309e3046ca2835412f738cd" alt="PlayFab - Economy - Edit Catalog Container" width="1280" height="1400" data-path="images/playfab/player-progression/player-data/tutorials/playfab-edit-catalog-container.png" />

이 예제에서 **CrystalContainer**에 대한 `CatalogItem` 요구 사항은 다음과 같습니다:

* **CrystalContainer**를 **Container**로 정의해야 합니다.

* **Containers**는 선택적으로 **Key Item**을 정의할 수 있으며, 이는 **Container**를 잠금 해제하는 데 필요합니다. 이 경우에는 **CrystalKey**입니다.

* **Container**와 모든 **Key**를 *모두* **Consumable**로 정의하는 것을 강력히 권장합니다. 이렇게 하면 사용 후 플레이어 인벤토리에서 제거되도록 양수 사용 횟수와 함께 정의할 수 있습니다.

### 서버 코드

```csharp theme={null}
void GrantItem() {
    PlayFabServerAPI.GrantItemsToUser(new GrantItemsToUserRequest {
        // In your game, this should just be a constant
        CatalogVersion = "CharacterClasses",
        // Servers must define which character they're modifying in every API call
        PlayFabId = "playFabId",
        ItemIds = new List<string> { "CrystalContainer" }
    }, LogSuccess, LogFailure);
}
```

### 클라이언트 코드

```csharp theme={null}
void OpenContainer() {
    PlayFabClientAPI.UnlockContainerInstance(new UnlockContainerInstanceRequest {
        // In your game, this should just be a constant matching your primary catalog
        CatalogVersion = "CharacterClasses",
        ContainerItemInstanceId = "containerInstanceId",
        KeyItemInstanceId = "keyInstanceId"
    }, LogSuccess, LogFailure);
}
```

### 키와 컨테이너 소비

이전 예제에서는 키 및/또는 컨테이너를 *소비 가능*으로 설정하도록 제안했지만, 이는 권장사항일 뿐입니다.

그러나 컨테이너와 그 키(있는 경우)가 *소비 불가능*하다면, 컨테이너를 *무한히* 다시 열 수 있어 매번 그 내용물을 플레이어에게 부여할 수 있습니다.

플레이어 인벤토리 용량은 *무한하지 않기* 때문에, 이 패턴은 크게 권장되지 않습니다. 소비 가능한 컨테이너가 잠금 해제되면 컨테이너와 사용된 소비 가능한 키 모두 자동으로 사용 횟수가 *감소*하여, 사용 횟수가 0에 도달하면 플레이어 인벤토리에서 제거됩니다.

### 실행 가능한 옵션

**소비 가능 컨테이너**, **키 없음**: 가장 기본적인 패턴으로, 컨테이너가 열릴 때 소비되며 키가 없습니다.

**소비 가능 컨테이너, 소비 가능 키**: 간단한 잠긴 컨테이너 사례로, 플레이어가 키로 컨테이너를 열 수 있습니다. *둘 다* 소비되며, 플레이어는 사용 횟수가 남은 키로만 사용 횟수가 남은 컨테이너를 열 수 있습니다.

**내구성 컨테이너, 소비 가능 키**: 플레이어가 키를 찾을 때마다 컨테이너를 열 수 있습니다. 키는 소비되고, 컨테이너는 키에 사용 횟수가 남아 있는 동안에만 열립니다.

**소비 가능 컨테이너, 내구성 키**: 플레이어가 키 아이템이 되는 *모든* 컨테이너를 열 수 있는 키를 유지할 수 있게 해줍니다. 컨테이너는 소비되지만, 플레이어는 나중에 키로 컨테이너를 여는 능력을 유지합니다.

## 예제: 플레이어로부터 인벤토리 아이템 구매

프로세스가 게임별로 다르기 때문에 플레이어로부터 인벤토리 아이템을 다시 사는 내장 API가 없습니다. 그러나 *기존* API 메서드를 사용하여 자신만의 **SellItem** 경험을 만들 수 있습니다:

* PlayFab 서버 API [RevokeInventoryItem](xref:titleid.playfabapi.com.server.playeritemmanagement.revokeinventoryitem)\*\* 은 인벤토리 아이템을 제거할 수 있게 해줍니다.

* PlayFab 서버 API [AddUserVirtualCurrency](xref:titleid.playfabapi.com.server.playeritemmanagement.adduservirtualcurrency)\*\* 는 적절한 양의 가상 통화를 반환할 수 있습니다. 현재 PlayFab API 메서드를 통해 실제 화폐를 반환하는 것은 불가능합니다.

<Note>
  아이템과 가상 통화는 밀접한 관계가 있습니다. 자세한 내용은 [Currencies](/services/playfab/economy-monetization/economy-v2/tutorials/currencies) 자습서를 참조하세요.
</Note>

다음 CloudScript 함수는 앞에서 설명한 두 서버 호출을 단일 클라이언트 액세스 호출로 결합합니다.

```javascript theme={null}
var SELL_PRICE_RATIO = 0.75;
function SellItem_internal(soldItemInstanceId, requestedVcType) {
    var inventory = server.GetUserInventory({ PlayFabId: currentPlayerId });
    var itemInstance = null;
    for (var i = 0; i < inventory.Inventory.length; i++) {
        if (inventory.Inventory[i].ItemInstanceId === soldItemInstanceId)
            itemInstance = inventory.Inventory[i];
    }
    if (!itemInstance)
        throw "Item instance not found"; // Protection against client providing incorrect data
    var catalog = server.GetCatalogItems({ CatalogVersion: itemInstance.CatalogVersion });
    var catalogItem = null;
    for (var c = 0; c < catalog.Catalog.length; c++) {
        if (itemInstance.ItemId === catalog.Catalog[c].ItemId)
            catalogItem = catalog.Catalog[c];
    }
    if (!catalogItem)
        throw "Catalog Item not found"; // Title catalog consistency check (You should never remove a catalog/catalogItem if any player owns that item
    var buyPrice = 0;
    if (catalogItem.VirtualCurrencyPrices.hasOwnProperty(requestedVcType))
        buyPrice = catalogItem.VirtualCurrencyPrices[requestedVcType];
    if (buyPrice <= 0)
        throw "Cannot redeem this item for: " + requestedVcType; // The client requested a virtual currency which doesn't apply to this item
    // Once we get here all safety checks are passed - Perform the sell
    var sellPrice = Math.floor(buyPrice * SELL_PRICE_RATIO);
    server.AddUserVirtualCurrency({ PlayFabId: currentPlayerId, Amount: sellPrice, VirtualCurrency: requestedVcType });
    server.RevokeInventoryItem({ PlayFabId: currentPlayerId, ItemInstanceId: soldItemInstanceId });
}

handlers.SellItem = function (args) {
    if (!args || !args.soldItemInstanceId || !args.requestedVcType)
        throw "Invalid input parameters, expected soldItemInstanceId and requestedVcType";
    SellItem_internal(args.soldItemInstanceId, args.requestedVcType);
};
```

### 모범 사례

* 변경하기 전에 모든 클라이언트 입력 정보가 *유효*한지 확인하세요.

* CloudScript는 원자적이지 않으므로 호출 순서가 중요합니다: **AddUserVirtualCurrency**는 성공하고 **RevokeInventoryItem**은 실패할 수 있습니다.

<Tip>
  이 프로세스에서는 *보상 없이* 무언가를 빼앗는 것보다 플레이어에게 획득하지 *않은* 무언가를 주는 것이 일반적으로 더 좋습니다.
</Tip>

이 CloudScript 함수는 클라이언트에서 접근할 수 있습니다.

```csharp theme={null}
void SellItem()
{
    PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest
    {
        // This must match "SellItem" from the "handlers.SellItem = ..." line in the CloudScript file
        FunctionName = "SellItem",
        FunctionParameter = new Dictionary<string, string>{
            // This is a hex-string value from the GetUserInventory result
            { "soldItemInstanceId", "sellItemInstanceId" },
            // Which redeemable virtual currency should be used in your game
            { "requestedVcType", "AU" },
        }
    }, LogSuccess, LogFailure);
}
```


## Related topics

- [PlayFab Inventory API](/ko/services/playfab/economy-monetization/economy-v2/inventory/index.md)
- [PlayFab Economy v2 문서](/ko/services/playfab/economy-monetization/index.md)
- [Player Inventory 빠른 시작](/ko/services/playfab/economy-monetization/economy-v2/inventory/quickstart.md)
- [Economy(레거시) 빠른 시작](/ko/services/playfab/economy-monetization/economy/quickstart.md)
- [플레이어 사용자 지정 속성을 활용한 고급 세그멘테이션](/ko/services/playfab/live-service-management/game-configuration/segmentation/advanced-segmentation.md)
