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 UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Extension;
using PlayFab;
using PlayFab.ClientModels;
using PlayFab.EconomyModels;
/// <summary>
/// Unity behavior that implements the the Unity IAP Store interface.
/// Attach as an asset to your Scene.
/// </summary>
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 readonly PlayFabEconomyAPIAsync s_economyAPI = new();
private static IStoreController s_storeController;
// TODO: This callback is for illustrations purposes, you should create one that fits your needs
public delegate void PlayFabProcessPurchaseCallback(PurchaseProcessingResult result);
/// <summary>
/// Event that is triggered when a purchase is processed.
/// </summary>
/// <remarks>
/// TODO: Subscribe to this event in your game code to handle purchase results.
/// </remarks>
public event PlayFabProcessPurchaseCallback PlayFabProcessPurchaseEvent;
/// <summary>
/// True if the Store Controller, extensions, and Catalog are set.
/// </summary>
public bool IsInitialized => s_storeController != null
&& _googlePlayCatalog != null
&& _storefrontCatalog != null;
// Start is called before the first frame update.
public void Start()
{
Login();
}
/// <summary>
/// Attempts to log the player in via the Android Device ID.
/// </summary>
private void Login()
{
// TODO: it is better to use LoginWithGooglePlayGamesService or a similar platform-specific login method for final game code.
// SystemInfo.deviceUniqueIdentifier 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.LoginWithCustomID(new LoginWithCustomIDRequest()
{
CreateAccount = true,
CustomId = SystemInfo.deviceUniqueIdentifier
}, result => RefreshIAPItems(), PlayFabSampleUtil.OnPlayFabError);
}
/// <summary>
/// Queries the PlayFab Economy Catalog V2 for updated listings
/// and then fills the local catalog objects.
/// </summary>
private async void RefreshIAPItems()
{
_googlePlayCatalog = new Dictionary<string, PlayFab.EconomyModels.CatalogItem>();
SearchItemsRequest googlePlayCatalogRequest = new()
{
Count = 50,
Filter = "AlternateIds/any(t: t/type eq 'GooglePlay')"
};
SearchItemsResponse googlePlayCatalogResponse;
do
{
googlePlayCatalogResponse = await s_economyAPI.SearchItemsAsync(googlePlayCatalogRequest);
Debug.Log("Search response: " + JsonUtility.ToJson(googlePlayCatalogResponse));
foreach (PlayFab.EconomyModels.CatalogItem item in googlePlayCatalogResponse.Items)
{
_googlePlayCatalog.Add(item.Id, item);
}
} while (!string.IsNullOrEmpty(googlePlayCatalogResponse.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 s_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 s_economyAPI.GetItemsAsync(itemsCatalogRequest);
foreach (PlayFab.EconomyModels.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();
}
/// <summary>
/// Initializes the Unity IAP system for the Google Play Store.
/// </summary>
private void InitializePurchasing()
{
if (IsInitialized) return;
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance(AppStore.GooglePlay));
foreach (PlayFab.EconomyModels.CatalogItem item in _googlePlayCatalog.Values)
{
string googlePlayItemId = item.AlternateIds.FirstOrDefault(item => item.Type == "GooglePlay")?.Value;
if (!string.IsNullOrWhiteSpace(googlePlayItemId))
{
builder.AddProduct(googlePlayItemId, ProductType.Consumable);
}
}
UnityPurchasing.Initialize(this, builder);
}
/// <summary>
/// Draw a debug IMGUI for testing examples.
/// Use UI Toolkit for your production game runtime UI instead.
/// </summary>
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 (PlayFab.EconomyModels.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 (PlayFab.EconomyModels.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"
)))
{
Task.Run(() => PlayFabPurchaseItemById(item.Id));
}
}
}
/// <summary>
/// Integrates game purchasing with the Unity IAP API.
/// </summary>
public void BuyProductById(string productId)
{
if (!IsInitialized)
{
Debug.LogError("IAP Service is not initialized!");
return;
}
s_storeController.InitiatePurchase(productId);
}
/// <summary>
/// Purchases a PlayFab inventory item by ID.
/// See the <see cref="PlayFabEconomyAPIAsync"/> class for details on error handling
/// and calling patterns.
/// </summary>
async public Task<bool> PlayFabPurchaseItemById(string itemId)
{
if (!IsInitialized)
{
Debug.LogError("IAP Service is not initialized!");
return false;
}
_lastAPICallResult = new();
Debug.Log("Player buying product " + itemId);
if (string.IsNullOrEmpty(_purchaseIdempotencyId))
{
_purchaseIdempotencyId = Guid.NewGuid().ToString();
}
GetItemRequest getVillagerStoreRequest = new()
{
AlternateId = new CatalogAlternateId()
{
Type = "FriendlyId",
Value = "villagerstore"
}
};
GetItemResponse getStoreResponse = await s_economyAPI.GetItemAsync(getVillagerStoreRequest);
if (getStoreResponse == null || string.IsNullOrEmpty(getStoreResponse?.Item?.Id))
{
_lastAPICallResult.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()
{
Amount = 1,
Item = new InventoryItemReference()
{
Id = itemId
},
PriceAmounts = new List<PurchasePriceAmount>
{
new()
{
Amount = price.Amount,
ItemId = price.ItemId
}
},
IdempotencyId = _purchaseIdempotencyId,
StoreId = getStoreResponse.Item.Id
};
PurchaseInventoryItemsResponse purchaseInventoryItemsResponse = await s_economyAPI.PurchaseInventoryItemsAsync(purchaseInventoryItemsRequest);
if (purchaseInventoryItemsResponse == null || purchaseInventoryItemsResponse?.TransactionIds.Count < 1)
{
_lastAPICallResult.Error = "Unable to purchase. Try again in a few minutes.";
return false;
}
_purchaseIdempotencyId = "";
_lastAPICallResult.Message = "Purchasing!";
return true;
}
private void OnRegistration(LoginResult result)
{
PlayFabSettings.staticPlayer.ClientSessionTicket = result.SessionTicket;
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
s_storeController = controller;
extensions.GetExtension<IGooglePlayStoreExtensions>().RestoreTransactions((result, error) => {
if (result)
{
Debug.LogWarning("Restore transactions succeeded.");
}
else
{
Debug.LogWarning("Restore transactions failed.");
}
});
}
public void OnInitializeFailed(InitializationFailureReason error)
{
Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
}
public void OnInitializeFailed(InitializationFailureReason error, string message)
{
Debug.Log("OnInitializeFailed InitializationFailureReason:" + error + message);
}
public void OnPurchaseFailed(UnityEngine.Purchasing.Product product, PurchaseFailureReason failureReason)
{
Debug.Log($"OnPurchaseFailed: FAIL. Product: '{product.definition.storeSpecificId}', PurchaseFailureReason: {failureReason}");
}
public void OnPurchaseFailed(UnityEngine.Purchasing.Product product, PurchaseFailureDescription failureDescription)
{
Debug.Log($"OnPurchaseFailed: FAIL. Product: '{product.definition.storeSpecificId}', PurchaseFailureReason: {failureDescription}");
}
/// <summary>
/// Callback for Store purchases. Subscribe to PlayFabProcessPurchaseEvent to handle the final PurchaseProcessingResult.
/// <see href="https://docs.unity3d.com/Packages/com.unity.purchasing@4.8/api/UnityEngine.Purchasing.PurchaseProcessingResult.html"/>
/// </summary>
/// <remarks>
/// This code does not account for purchases that were pending and are
/// delivered on application start. Production code should account for these cases.
/// </remarks>
/// <returns>Complete immediately upon error. Pending if PlayFab Economy is handling final processing and will trigger PlayFabProcessPurchaseEvent with the final result.</returns>
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
{
if (!IsInitialized)
{
Debug.LogWarning("Not initialized. Ignoring.");
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);
GooglePurchase purchasePayload = GooglePurchase.FromJson(purchaseEvent.purchasedProduct.receipt);
RedeemGooglePlayInventoryItemsRequest request = new()
{
Purchases = new List<GooglePlayProductPurchase>
{
new()
{
ProductId = purchasePayload.PayloadData?.JsonData?.productId,
Token = purchasePayload.PayloadData?.JsonData?.purchaseToken
}
}
};
PlayFabEconomyAPI.RedeemGooglePlayInventoryItems(request, result =>
{
Debug.Log("Processed receipt validation.");
if (result?.Failed.Count > 0)
{
Debug.Log($"Validation failed for {result.Failed.Count} receipts.");
Debug.Log(JsonUtility.ToJson(result.Failed));
PlayFabProcessPurchaseEvent?.Invoke(PurchaseProcessingResult.Pending);
}
else
{
Debug.Log("Validation succeeded!");
PlayFabProcessPurchaseEvent?.Invoke(PurchaseProcessingResult.Complete);
s_storeController.ConfirmPendingPurchase(purchaseEvent.purchasedProduct);
Debug.Log("Confirmed purchase with Google Marketplace.");
}
},
PlayFabSampleUtil.OnPlayFabError);
return PurchaseProcessingResult.Pending;
}
}
/// <summary>
/// Utility classes for the sample.
/// </summary>
public class PlayFabEconomyAPIAsyncResult
{
public string Error { get; set; } = null;
public string Message { get; set; } = null;
}
public static class PlayFabSampleUtil
{
public static void OnPlayFabError(PlayFabError error)
{
Debug.LogError(error.GenerateErrorReport());
}
}
/// <summary>
/// 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.
/// </summary>
public class PlayFabEconomyAPIAsync
{
/// <summary>
/// <see href="https://learn.microsoft.com/rest/api/playfab/economy/catalog/get-item"/>
/// </summary>
public Task<GetItemResponse> GetItemAsync(GetItemRequest request)
{
TaskCompletionSource<GetItemResponse> getItemAsyncTaskSource = new();
PlayFabEconomyAPI.GetItem(request, (response) => getItemAsyncTaskSource.SetResult(response), error =>
{
PlayFabSampleUtil.OnPlayFabError(error);
getItemAsyncTaskSource.SetResult(default);
});
return getItemAsyncTaskSource.Task;
}
/// <summary>
/// <see href="https://learn.microsoft.com/rest/api/playfab/economy/catalog/get-items"/>
/// </summary>
public Task<GetItemsResponse> GetItemsAsync(GetItemsRequest request)
{
TaskCompletionSource<GetItemsResponse> getItemsAsyncTaskSource = new();
PlayFabEconomyAPI.GetItems(request, (response) => getItemsAsyncTaskSource.SetResult(response), error =>
{
PlayFabSampleUtil.OnPlayFabError(error);
getItemsAsyncTaskSource.SetResult(default);
});
return getItemsAsyncTaskSource.Task;
}
/// <summary>
/// <see href="https://learn.microsoft.com/rest/api/playfab/economy/inventory/purchase-inventory-items"/>
/// </summary>
public Task<PurchaseInventoryItemsResponse> PurchaseInventoryItemsAsync(PurchaseInventoryItemsRequest request)
{
TaskCompletionSource<PurchaseInventoryItemsResponse> purchaseInventoryItemsAsyncTaskSource = new();
PlayFabEconomyAPI.PurchaseInventoryItems(request, (response) => purchaseInventoryItemsAsyncTaskSource.SetResult(response), error =>
{
PlayFabSampleUtil.OnPlayFabError(error);
purchaseInventoryItemsAsyncTaskSource.SetResult(default);
});
return purchaseInventoryItemsAsyncTaskSource.Task;
}
/// <summary>
/// <see href="https://learn.microsoft.com/rest/api/playfab/economy/catalog/search-items"/>
/// </summary>
public Task<SearchItemsResponse> SearchItemsAsync(SearchItemsRequest request)
{
TaskCompletionSource<SearchItemsResponse> searchItemsAsyncTaskSource = new();
PlayFabEconomyAPI.SearchItems(request, (response) => searchItemsAsyncTaskSource.SetResult(response), error =>
{
PlayFabSampleUtil.OnPlayFabError(error);
searchItemsAsyncTaskSource.SetResult(default);
});
return searchItemsAsyncTaskSource.Task;
}
}
[Serializable]
public class PurchaseJsonData
{
public string orderId;
public string packageName;
public string productId;
public string purchaseToken;
public long purchaseTime;
public int purchaseState;
}
[Serializable]
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>(payload.json);
return payload;
}
}
[Serializable]
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 항목 이미지 필드에 아이콘 이미지를 추가합니다.