Economy v2, Unity 및 Android 시작하기
Important
이제 Economy v2가 일반 공급됩니다. 지원 및 피드백을 받으려면 PlayFab 포럼으로 이동하세요.
이 자습서는 PlayFab, Unity + IAP 서비스 및 Android 과금 API를 사용하여 앱내 구매(IAP)를 설정하는 방법을 보여줍니다.
시작하기 전에
Android 청구 API 및 PlayFab은 함께 작동하여 클라이언트에 대한 IAP 환경을 제공합니다.
PlayMarket을 통해 귀하의 제품 ID와 가격을 설정함으로써 시작하세요. 처음에 모든 제품은 플레이어가 구매할 수 있는 디지털 엔터티인 얼굴이 없는 것이지만 PlayFab 플레이어에게는 의미가 없습니다.
그러한 주체를 유용하게 만들려면 PlayFab 품목 카탈로그에서 미러링해야 합니다. PlayFab은 얼굴 없는 엔터티를 번들, 컨테이너 및 개별 항목으로 변환합니다.
각각 다음과 같은 자신의 독특한 얼굴을 갖고있습니다.
- 타이틀
- 설명
- 태그
- 유형
- 이미지
- 특성
모든 상품은 ID 공유를 통해 마켓 상품과 연동됩니다.
구매할 수 있는 실제 현금 항목에 액세스하는 가장 좋은 방법은 GetItems를 사용하는 것입니다.
품목의 ID가 PlayFab과 외부 IAP 시스템 사이의 링크입니다. 그러므로 우리는 품목 ID를 IAP 서비스로 전달합니다.
이 지점에서 구입 프로세스가 시작됩니다. 플레이어가 IAP 인터페이스와 상호 작용하여, 구입에 성공하면 귀하는 영수증을 받습니다.
PlayFab은 영수증을 확인하고 구매를 등록하여 방금 구매한 항목을 PlayFab 플레이어에게 부여합니다.
고객 애플리케이션 설정
이 섹션에서는 PlayFab, UnityIAP 및 Android Billing API를 사용하여 IAP를 테스트하도록 애플리케이션을 구성하는 방법을 보여줍니다.
필수 구성 요소:
- Unity 프로젝트.
- 귀하의 타이틀과 협력하도록 구성된 가져온 PlayFab Unity SDK.
- Visual Studio 같은 편집기가 설치되고 Unity 프로젝트에서 작동하도록 구성되었습니다.
첫 번째 단계는 UnityIAP를 설정하는 것입니다:
- 서비스로 이동합니다.
- 서비스 탭이 선택되어 있는지 확인합니다.
- 귀하의 Unity 서비스 프로필 또는 조직을 선택합니다.
- 생성 버튼을 선택합니다.
- 다음, 앱내 구매(IAP) 서비스로 이동합니다.
교차 플랫폼 IAP 간소화 토글을 설정하여 서비스를 활성화합니다.
이어서 계속 버튼을 선택합니다.
플러그인 목록 페이지가 나타날 것입니다.
- 가져오기 버튼을 선택합니다.
Unity 설치 및 가져오기 절차를 모든 플러그인을 가져온 시점까지 계속하십시오.
- 플러그인이 설치되었는지 확인합니다.
- 그런 다음 AndroidIAPExample.cs라는 새 스크립트를 만듭니다.
다음 코드를 포함하는 AndroidIAPExample.cs
(자세한 설명은 코드 주석 참조)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Extension;
using PlayFab;
using PlayFab.ClientModels;
using PlayFab.EconomyModels;
using CatalogItem = PlayFab.EconomyModels.CatalogItem;
/**
* Unity behavior that implements the the Unity IAP Store interface.
*
* Attach as an asset to your Scene.
*/
public class AndroidIAPExample : MonoBehaviour, IDetailedStoreListener
{
// Bundles for sale on the Google Play Store.
private Dictionary<string, PlayFab.EconomyModels.CatalogItem> GooglePlayCatalog;
// In-game items for sale at the example vendor.
private Dictionary<string, PlayFab.EconomyModels.CatalogItem> StorefrontCatalog;
private string purchaseIdempotencyId = null;
private PlayFabEconomyAPIAsyncResult lastAPICallResult = null;
private static PlayFabEconomyAPIAsync economyAPI = new();
private static IStoreController m_StoreController;
/**
* True if the Store Controller, extensions, and Catalog are set.
*/
public bool IsInitialized
{
get
{
return m_StoreController != null
&& GooglePlayCatalog != null
&& StorefrontCatalog != null;
}
}
/**
* Returns false as this is just sample code.
*
* @todo Implement this functionality for your game.
*/
public bool UserHasExistingSave
{
get
{
return false;
}
}
/**
* Integrates game purchasing with the Unity IAP API.
*/
public void BuyProductByID(string productID)
{
if (!IsInitialized) throw new Exception("IAP Service is not initialized!");
m_StoreController.InitiatePurchase(productID);
}
/**
* Purchases a PlayFab inventory item by ID.
*
* @see the PlayFabEconomyAPIAsync class for details on error handling
* and calling patterns.
*/
async public Task<bool> PlayFabPurchaseItemByID(string itemID, PlayFabEconomyAPIAsyncResult result)
{
if (!IsInitialized) throw new Exception("IAP Service is not initialized!");
Debug.Log("Player buying product " + itemID);
if (string.IsNullOrEmpty(purchaseIdempotencyId))
{
purchaseIdempotencyId = Guid.NewGuid().ToString();
}
GetItemRequest getVillagerStoreRequest = new GetItemRequest()
{
AlternateId = new CatalogAlternateId()
{
Type = "FriendlyId",
Value = "villagerstore"
}
};
GetItemResponse getStoreResponse = await economyAPI.getItemAsync(getVillagerStoreRequest);
if (getStoreResponse == null || string.IsNullOrEmpty(getStoreResponse?.Item?.Id))
{
result.error = "Unable to contact the store. Check your internet connection and try again in a few minutes.";
return false;
}
CatalogPriceAmount price = StorefrontCatalog.FirstOrDefault(item => item.Key == itemID).Value.PriceOptions.Prices.FirstOrDefault().Amounts.FirstOrDefault();
PurchaseInventoryItemsRequest purchaseInventoryItemsRequest = new PurchaseInventoryItemsRequest()
{
Amount = 1,
Item = new InventoryItemReference()
{
Id = itemID
},
PriceAmounts = new List<PurchasePriceAmount>
{
new PurchasePriceAmount()
{
Amount = price.Amount,
ItemId = price.ItemId
}
},
IdempotencyId = purchaseIdempotencyId,
StoreId = getStoreResponse.Item.Id
};
PurchaseInventoryItemsResponse purchaseInventoryItemsResponse = await economyAPI.purchaseInventoryItemsAsync(purchaseInventoryItemsRequest);
if (purchaseInventoryItemsResponse == null || purchaseInventoryItemsResponse?.TransactionIds.Count < 1)
{
result.error = "Unable to purchase. Try again in a few minutes.";
return false;
}
purchaseIdempotencyId = "";
result.message = "Purchasing!";
return true;
}
private void InitializePurchasing()
{
if (IsInitialized) return;
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance(AppStore.GooglePlay));
foreach (CatalogItem item in GooglePlayCatalog.Values)
{
var googlePlayItemId = item.AlternateIds.FirstOrDefault(item => item.Type == "GooglePlay")?.Value;
if (!googlePlayItemId.IsUnityNull()) {
builder.AddProduct(googlePlayItemId, ProductType.Consumable);
}
}
UnityPurchasing.Initialize(this, builder);
}
/**
* Attempts to log the player in via the Android Device ID.
*/
private void Login()
{
// Best practice is to soft-login with a unique ID, then prompt the player to finish
// creating a PlayFab account in order to retrive cross-platform saves or other benefits.
if (UserHasExistingSave)
{
// @todo Integrate this with the save system.
LoginWithPlayFabRequest loginWithPlayFabRequest = new()
{
Username = "",
Password = ""
};
PlayFabClientAPI.LoginWithPlayFab(loginWithPlayFabRequest, OnRegistration, OnPlayFabError);
return;
}
// AndroidDeviceID will prompt for permissions on newer devices.
// Using a non-device specific GUID and saving to a local file
// is a better approach. PlayFab does allow you to link multiple
// Android device IDs to a single PlayFab account.
PlayFabClientAPI.LoginWithAndroidDeviceID(new LoginWithAndroidDeviceIDRequest()
{
CreateAccount = true,
AndroidDeviceId = SystemInfo.deviceUniqueIdentifier
}, result => {
RefreshIAPItems();
}, error => Debug.LogError(error.GenerateErrorReport()));
}
/**
* Draw a debug IMGUI for testing examples.
*
* Use UI Toolkit for your production game runtime UI instead.
*/
public void OnGUI()
{
// Support high-res devices.
GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(3, 3, 3));
if (!IsInitialized)
{
GUILayout.Label("Initializing IAP and logging in...");
return;
}
if (!string.IsNullOrEmpty(purchaseIdempotencyId)
&& (!string.IsNullOrEmpty(lastAPICallResult?.message)
|| !string.IsNullOrEmpty(lastAPICallResult?.error)))
{
GUILayout.Label(lastAPICallResult?.message + lastAPICallResult?.error);
}
GUILayout.Label("Shop for game currency bundles.");
// Draw a purchase menu for each catalog item.
foreach (CatalogItem item in GooglePlayCatalog.Values)
{
// Use a dictionary to select the proper language.
if (GUILayout.Button("Get " + (item.Title.ContainsKey("en-US") ? item.Title["en-US"] : item.Title["NEUTRAL"])))
{
BuyProductByID(item.AlternateIds.FirstOrDefault(item => item.Type == "GooglePlay").Value);
}
}
GUILayout.Label("Hmmm. (Translation: Welcome to my humble Villager store.)");
// Draw a purchase menu for each catalog item.
foreach (CatalogItem item in StorefrontCatalog.Values)
{
// Use a dictionary to select the proper language.
if (GUILayout.Button("Buy "
+ (item.Title.ContainsKey("en-US") ? item.Title["en-US"] : item.Title["NEUTRAL"]
+ ": "
+ item.PriceOptions.Prices.FirstOrDefault().Amounts.FirstOrDefault().Amount.ToString()
+ " Diamonds"
)))
{
PlayFabPurchaseItemByID(item.Id, lastAPICallResult);
}
}
}
private void OnRegistration(LoginResult result)
{
PlayFabSettings.staticPlayer.ClientSessionTicket = result.SessionTicket;
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
m_StoreController = controller;
}
public void OnInitializeFailed(InitializationFailureReason error)
{
Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
}
public void OnInitializeFailed(InitializationFailureReason error, string message)
{
Debug.Log("OnInitializeFailed InitializationFailureReason:" + error + message);
}
private void OnPlayFabError(PlayFabError error)
{
Debug.LogError(error.GenerateErrorReport());
}
public void OnPurchaseFailed(UnityEngine.Purchasing.Product product, PurchaseFailureReason failureReason)
{
Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}",
product.definition.storeSpecificId, failureReason));
}
public void OnPurchaseFailed(UnityEngine.Purchasing.Product product, PurchaseFailureDescription failureDescription)
{
Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}",
product.definition.storeSpecificId, failureDescription));
}
/**
* Callback for Store purchases.
*
* @note This code does not account for purchases that were pending and are
* delivered on application start. Production code should account for these
* cases.
*
* @see https://docs.unity3d.com/Packages/com.unity.purchasing@4.8/api/UnityEngine.Purchasing.PurchaseProcessingResult.html
*/
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
{
if (!IsInitialized)
{
return PurchaseProcessingResult.Complete;
}
if (purchaseEvent.purchasedProduct == null)
{
Debug.LogWarning("Attempted to process purchase with unknown product. Ignoring.");
return PurchaseProcessingResult.Complete;
}
if (string.IsNullOrEmpty(purchaseEvent.purchasedProduct.receipt))
{
Debug.LogWarning("Attempted to process purchase with no receipt. Ignoring.");
return PurchaseProcessingResult.Complete;
}
Debug.Log("Attempting purchase with receipt " + purchaseEvent.purchasedProduct.receipt.Serialize().ToString());
GooglePurchase purchasePayload = GooglePurchase.FromJson(purchaseEvent.purchasedProduct.receipt);
RedeemGooglePlayInventoryItemsRequest request = new()
{
Purchases = new List<GooglePlayProductPurchase> {
new GooglePlayProductPurchase() {
ProductId = purchasePayload.PayloadData?.JsonData?.productId,
Token = purchasePayload.PayloadData?.signature
}
}
};
RedeemGooglePlayInventoryItemsResponse redeemResponse = new();
PlayFabEconomyAPI.RedeemGooglePlayInventoryItems(request, result => {
redeemResponse = result;
Debug.Log("Processed receipt validation.");
},
error => Debug.Log("Validation failed: " + error.GenerateErrorReport()));
if (redeemResponse?.Failed.Count > 0)
{
Debug.Log("Validation failed for " + redeemResponse.Failed.Count + " receipts.");
Debug.Log(redeemResponse.Failed.Serialize().ToSafeString());
return PurchaseProcessingResult.Pending;
}
else
{
Debug.Log("Validation succeeded!");
}
return PurchaseProcessingResult.Complete;
}
/**
* Queries the PlayFab Economy Catalog V2 for updated listings
* and then fills the local catalog objects.
*/
private async void RefreshIAPItems()
{
GooglePlayCatalog = new Dictionary<string, PlayFab.EconomyModels.CatalogItem>();
SearchItemsRequest playCatalogRequest = new()
{
Count = 50,
Filter = "Platforms/any(platform: platform eq 'GooglePlay')"
};
SearchItemsResponse playCatalogResponse;
do
{
playCatalogResponse = await economyAPI.searchItemsAsync(playCatalogRequest);
Debug.Log("Search response: " + playCatalogResponse.Serialize().ToSafeString());
foreach (CatalogItem item in playCatalogResponse.Items)
{
GooglePlayCatalog.Add(item.Id, item);
}
} while (!string.IsNullOrEmpty(playCatalogResponse.ContinuationToken));
Debug.Log("Completed pulling from PlayFab Economy v2 googleplay Catalog: "
+ GooglePlayCatalog.Count()
+ " items retrieved");
StorefrontCatalog = new Dictionary<string, PlayFab.EconomyModels.CatalogItem>();
GetItemRequest storeCatalogRequest = new()
{
AlternateId = new CatalogAlternateId()
{
Type = "FriendlyId",
Value = "villagerstore"
}
};
GetItemResponse storeCatalogResponse;
storeCatalogResponse = await economyAPI.getItemAsync(storeCatalogRequest);
List<string> itemIds = new() { };
foreach (CatalogItemReference item in storeCatalogResponse.Item.ItemReferences)
{
itemIds.Add(item.Id);
}
GetItemsRequest itemsCatalogRequest = new()
{
Ids = itemIds
};
GetItemsResponse itemsCatalogResponse = await economyAPI.getItemsAsync(itemsCatalogRequest);
foreach (CatalogItem item in itemsCatalogResponse.Items)
{
StorefrontCatalog.Add(item.Id, item);
}
Debug.Log("Completed pulling from PlayFab Economy v2 villagerstore store: "
+ StorefrontCatalog.Count()
+ " items retrieved");
InitializePurchasing();
}
// Start is called before the first frame update.
public void Start()
{
Login();
}
// Update is called once per frame.
public void Update() { }
}
// Utility classes for the sample.
public class PlayFabEconomyAPIAsyncResult
{
public string error = null;
public string message = null;
}
/**
* Example Async wrapper for PlayFab API's.
*
* This is just a quick sample for example purposes.
*
* Write your own customer Logger implementation to log and handle errors
* for user-facing scenarios. Use tags and map which PlayFab errors require your
* game to handle GUI or gameplay updates vs which should be logged to crash and
* error reporting services.
*/
public class PlayFabEconomyAPIAsync
{
// @see https://learn.microsoft.com/en-us/rest/api/playfab/economy/catalog/get-item
private TaskCompletionSource<GetItemResponse> getItemAsyncTaskSource;
public void onGetItemRequestComplete(GetItemResponse response)
{
getItemAsyncTaskSource.SetResult(response);
}
public Task<GetItemResponse> getItemAsync(GetItemRequest request)
{
getItemAsyncTaskSource = new();
PlayFabEconomyAPI.GetItem(request, onGetItemRequestComplete, error => Debug.LogError(error.GenerateErrorReport()));
return getItemAsyncTaskSource.Task;
}
// @see https://learn.microsoft.com/en-us/rest/api/playfab/economy/catalog/get-items
private TaskCompletionSource<GetItemsResponse> getItemsAsyncTaskSource;
public void onGetItemsRequestComplete(GetItemsResponse response)
{
getItemsAsyncTaskSource.SetResult(response);
}
public Task<GetItemsResponse> getItemsAsync(GetItemsRequest request)
{
getItemsAsyncTaskSource = new();
PlayFabEconomyAPI.GetItems(request, onGetItemsRequestComplete, error => Debug.LogError(error.GenerateErrorReport()));
return getItemsAsyncTaskSource.Task;
}
// @see https://learn.microsoft.com/en-us/rest/api/playfab/economy/inventory/purchase-inventory-items
private TaskCompletionSource<PurchaseInventoryItemsResponse> purchaseInventoryItemsAsyncTaskSource;
public void OnPurchaseInventoryItemsRequestComplete(PurchaseInventoryItemsResponse response)
{
purchaseInventoryItemsAsyncTaskSource.SetResult(response);
}
public Task<PurchaseInventoryItemsResponse> purchaseInventoryItemsAsync(PurchaseInventoryItemsRequest request)
{
purchaseInventoryItemsAsyncTaskSource = new();
PlayFabEconomyAPI.PurchaseInventoryItems(request,
OnPurchaseInventoryItemsRequestComplete,
error => { Debug.LogError(error.GenerateErrorReport()); });
return purchaseInventoryItemsAsyncTaskSource.Task;
}
// @see https://learn.microsoft.com/en-us/rest/api/playfab/economy/catalog/search-items
private TaskCompletionSource<SearchItemsResponse> searchItemsAsyncTaskSource;
public void OnSearchItemsRequestComplete(SearchItemsResponse response)
{
searchItemsAsyncTaskSource.SetResult(response);
}
public Task<SearchItemsResponse> searchItemsAsync(SearchItemsRequest request) {
searchItemsAsyncTaskSource = new();
PlayFabEconomyAPI.SearchItems(request, OnSearchItemsRequestComplete, error => Debug.LogError(error.GenerateErrorReport()));
return searchItemsAsyncTaskSource.Task;
}
}
public class PurchaseJsonData
{
public string orderId;
public string packageName;
public string productId;
public long purchaseTime;
public int purchaseState;
public string purchaseToken;
}
public class PurchasePayloadData
{
public PurchaseJsonData JsonData;
public string signature;
public string json;
public static PurchasePayloadData FromJson(string json)
{
var payload = JsonUtility.FromJson<PurchasePayloadData>(json);
payload.JsonData = JsonUtility.FromJson<PurchaseJsonData>(json);
return payload;
}
}
public class GooglePurchase
{
public PurchasePayloadData PayloadData;
public string Store;
public string TransactionID;
public string Payload;
public static GooglePurchase FromJson(string json)
{
var purchase = JsonUtility.FromJson<GooglePurchase>(json);
// Only fake receipts are returned in Editor play.
if (Application.isEditor)
{
return purchase;
}
purchase.PayloadData = PurchasePayloadData.FromJson(purchase.Payload);
return purchase;
}
}
- 코드로 불리는 새 GameObject를 만듭니다.
AndroidIAPExample
구성 요소를 추가합니다(클릭 및 끌기 또는).- 장면을 저장합니다.
마지막으로 빌드 설정으로 이동합니다.
- 장면이 빌드의 장면 영역에 추가되었는지 확인합니다.
- Android 플랫폼을 선택합니다.
- 플레이어 설정 영역으로 이동합니다.
- 패키지 이름을 지정합니다.
참고 항목
PlayMarket 충돌을 피하기 위해 고유한 패키지 이름을 지으십시오.
마지막으로 평소와 같이 애플리케이션을 빌드하고 APK가 있는지 확인합니다.
테스트하려면 PlayMarket 및 PlayFab을 구성해야 합니다.
IAP용 PlayMarket 애플리케이션 설정
이 섹션은 PlayMarket 애플리케이션을 위한 IAP를 활성화하는 방법에 대해 설명합니다.
참고 항목
애플리케이션 자체를 설정하는 것은 이 자습서의 범위 밖입니다. 우리는 귀하가 어떤 애플리케이션을 이미 갖고 있고 그것이 적어도 알파 릴리스를 게시하도록 구성되어 있다고 간주합니다.
유용한 사항:
- 그 지점에 도달하려면 APK가 업로드되어 있어야 합니다. 이전 섹션에서 구성한 APK를 사용합니다.
- IAP 샌드박스를 사용하려면 APK를 알파 또는 베타 애플리케이션으로 업로드하세요.
- 콘텐츠 등급 구성에는 애플리케이션에서 IAP가 어떻게 활성화되는지에 대한 질문이 포함될 것입니다.
- PlayMarket은 게시자가 IAP를 사용하거나 테스트하는 것을 허용하지 않습니다. 테스트 목적으로 다른 Google 계정을 선택하고 알파/베타 빌드의 테스터로 추가합니다.
애플리케이션 빌드를 게시합니다.
메뉴에서 앱내 제품을 선택합니다.
- 판매자 계정을 요청하면 계정을 연결하거나 만드세요.
새 제품 추가 버튼을 선택합니다.
새 제품 화면에서 관리되는 제품을 선택합니다.
100diamonds
와(과) 같은 설명이 포함된 제품 ID를 지정합니다.계속을 선택합니다.
PlayMarket을 사용하려면 타이틀(1) 및 설명(2)(예:
100 Diamonds
및A pack of 100 diamonds to spend in-game
)을 입력해야 합니다.데이터 항목 데이터는 PlayFab 서비스에서만 제공되며 일치하는 데 ID만 필요합니다.
더 멀리 스크롤하여 가격 추가 버튼을 선택합니다.
"$0.99"와 같은 유효한 가격을 입력하십시오(가격이 각 국가/지역을 위해 어떻게 독립적으로 변환되는지 주목하세요).
적용 버튼을 선택합니다.
마지막으로 화면의 맨위로 되돌아가 품목의 상태를 활성으로 변경합니다.
라이선스 키를 저장하여 PlayFab을 PlayMarket과 연결합니다.
메뉴에서 서비스 & API로 이동합니다.
그 다음 Base64 버전의 키를 찾아 저장합니다.
그 다음 단계는 IAP 테스트를 활성화하는 것입니다. 알파 및 베타 빌드를 위해 샌드박스가 자동으로 활성화되지만, 우리는 앱을 테스트할 권한이 있는 계정을 설정할 필요가 있습니다:
- 홈으로 이동합니다.
- 왼쪽 메뉴에서 계정 세부 정보를 찾아 선택합니다.
- 라이선스 테스트 영역을 찾습니다.
- 테스트 계정이 목록에 있는지 확인합니다.
- 라이선스 테스트 응답이 RESPOND_NORMALLY로 설정되어 있는지 확인합니다.
설정값 적용을 잊지 마세요!
이 시점에서 Play Market 쪽 통합을 확립해야 합니다.
PlayFab 타이틀 설정
마지막 단계는 우리 제품을 반영하는 PlayFab 타이틀을 구성하고, Google 과금 API와 통합하는 것입니다.
- 추가 기능을 선택합니다.
- 이어서 Google 추가 기능을 선택합니다.
- 패키지 ID를 입력합니다.
- 이전 섹션에서 얻은 Google 앱 라이선스 키를 입력합니다.
- Google 설치 버튼을 선택하여 변경 사항을 적용합니다.
다음 단계는 100개의 다이아몬드 번들을 PlayFab에 반영하는 것입니다.
새 Economy Catalog(V2) 통화를 만듭니다.
타이틀을 편집하고 설명을 추가합니다(예:
Diamonds
,Our in-game currency of choice.
).사용자의 통화
diamonds
을(를) 더 쉽게 찾을 수 있도록 식별 ID를 추가하세요.저장 및 게시를 선택하여 변경을 완료합니다.
통화 목록에서 통화를 관찰합니다.
다음으로 새 Economy Catalog(V2) 번들을 만듭니다.
타이틀을 편집하고 설명을 추가합니다(예:
100 Diamonds Bundle
,A pack of 100 diamonds to spend in-game.
).{ "NEUTRAL": "100 Diamonds Bundle", "en-US": "100 Diamonds Bundle", "en-GB": "100 Diamonds Bundle", "de-DE": "100 Diamantenbüschel" }
참고 항목
이 데이터는 Play 마켓 항목 타이틀 및 설명과 관련이 없으며 독립적입니다.
콘텐츠 형식을 사용하여 번들을 구성할 수 있습니다(예:
appstorebundles
). 콘텐츠 형식은 ⚙️ > 타이틀 설정 > Economy(V2)에서 관리됩니다.디스플레이 속성에 지역화된 가격을 추가하여 실제 가격을 추적합니다.
{ "prices": [ "en-us": 0.99, "en-gb": 0.85, "de-de": 0.45 ] }
번들에 새 항목을 추가합니다. 필터에서 통화를 선택하고 이전 집합에서 만든 통화를 선택합니다. 이 번들에서 판매하려는 통화 금액과 일치하도록 수량을 설정합니다.
"GooglePlay" Marketplace에 대한 새 플랫폼을 추가합니다. GooglePlay Marketplace가 아직 없는 경우 Economy 설정 페이지에서 만들 수 있습니다. 이전 섹션에서 만든 Google Play 콘솔 제품 ID와 일치하도록 Marketplace ID를 설정합니다.
저장 및 게시를 선택하여 변경을 완료합니다.
번들 목록에서 번들을 관찰합니다.
다음으로, 플레이어가 인게임 NPC 공급업체를 나타내기 위해 PlayFab 스토어에서 통화를 사용할 수 있도록 인게임 구매를 설정할 수 있습니다.
- 새 Economy Catalog(V2) 항목을 만듭니다.
- 제목을 편집하고 설명을 추가하세요(예: "황금 검", "황금으로 만든 검").
- 지역화된 키워드를 추가하여 플레이어가 스토어에서 아이템을 찾는 데 도움을 줄 수 있습니다. API를 통해 나중에 검색할 수 있도록 항목을 구성하는 데 도움이 되는 태그 및 콘텐츠 형식을 추가합니다. 디스플레이 속성을 사용하여 아머(armor) 값, 예술 자산의 상대 경로 또는 게임에 저장해야 하는 기타 데이터와 같은 게임 데이터를 저장할 수 있습니다.
- 새 가격을 추가하고 이전 단계에서 만든 통화를 선택합니다. 금액은 기본으로 설정하려는 가격으로 설정합니다. 나중에 생성한 스토어에서 가격을 재정의할 수 있습니다.
- 저장 및 게시를 선택하여 변경을 완료합니다.
- 품목 목록의 품목을 확인합니다.
- 마지막으로 새 Economy Catalog(V2) 스토어를 만듭니다.
- 타이틀을 편집하고 설명을 추가합니다(예:
Villager Store
,A humble store run by a humble villager.
). - 식별 ID를 제공하여 검색을 더 쉽게 만듭니다(예:
villagerstore
). - 이전 단계에서 만든 항목을 저장소에 추가합니다. 스토어에 여러 항목을 추가하고 필요한 경우 기본 가격을 재정의할 수 있습니다.
- 저장 및 게시를 선택하여 변경을 완료합니다.
- 스토어 목록에서 스토어를 관찰합니다.
PlayFab 타이틀 설정을 완료했습니다.
테스트
테스트 목적으로 알파/베타 버전을 사용하여 앱을 다운로드하십시오.
- 테스트 계정과 진짜 Android 기기를 사용하십시오.
- 앱을 시작하면 IAP가 초기화되고, 품목을 나타내는 한 버튼이 보여야 합니다.
- 그 버튼을 선택합니다.
IAP 구매가 시작됩니다. 구매에 성공하는 시점까지 Google Play 지침을 따릅니다.
마지막으로, PlayFab 게임 관리자 대시보드에서 귀하의 타이틀로 가서 새 이벤트를 찾습니다.
구매가 제공되고 검증되었으며 PlayFab 에코시스템으로 연결되었는지 확인합니다.
UnityIAP 및 Android Billing API를 PlayFab 애플리케이션에 성공적으로 통합했습니다!
다음 단계
- 데모 IMGUI 디스플레이를 대체하는 구매용 Unity UI 도구 키트 인터페이스를 빌드합니다.
- PlayFab 오류를 처리하고 사용자에게 표시하는 사용자 지정 Unity 로거를 만듭니다.
- Unity UI에 표시할 PlayFab 항목 이미지 필드에 아이콘 이미지를 추가합니다.