mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2026-01-04 23:10:08 +01:00
Merge pull request #498 from ricardoalcantara/mono_android_iap
Mono Android IAP Demo Project
This commit is contained in:
70
mono/android_iap/Android IAP with C#.csproj
Normal file
70
mono/android_iap/Android IAP with C#.csproj
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{FA89D4C3-B45B-49F1-A975-BF059CA82D86}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<OutputPath>.mono\temp\bin\$(Configuration)</OutputPath>
|
||||
<RootNamespace>AndroidIAPwithC</RootNamespace>
|
||||
<AssemblyName>Android IAP with C#</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
<GodotProjectGeneratorVersion>1.0.0.0</GodotProjectGeneratorVersion>
|
||||
<BaseIntermediateOutputPath>.mono\temp\obj</BaseIntermediateOutputPath>
|
||||
<IntermediateOutputPath>$(BaseIntermediateOutputPath)\$(Configuration)</IntermediateOutputPath>
|
||||
<ApiConfiguration Condition=" '$(Configuration)' != 'ExportRelease' ">Debug</ApiConfiguration>
|
||||
<ApiConfiguration Condition=" '$(Configuration)' == 'ExportRelease' ">Release</ApiConfiguration>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ExportDebug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ExportRelease|AnyCPU' ">
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;TOOLS;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies">
|
||||
<Version>1.0.0</Version>
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<Reference Include="GodotSharp">
|
||||
<HintPath>$(ProjectDir)\.mono\assemblies\$(ApiConfiguration)\GodotSharp.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<HintPath>$(ProjectDir)\.mono\assemblies\$(ApiConfiguration)\GodotSharpEditor.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GodotGooglePlayBilling\BillingResult.cs" />
|
||||
<Compile Include="GodotGooglePlayBilling\GooglePlayBilling.cs" />
|
||||
<Compile Include="GodotGooglePlayBilling\GooglePlayBillingUtils.cs" />
|
||||
<Compile Include="GodotGooglePlayBilling\Purchase.cs" />
|
||||
<Compile Include="GodotGooglePlayBilling\PurchasesResult.cs" />
|
||||
<Compile Include="GodotGooglePlayBilling\SkuDetails.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
19
mono/android_iap/Android IAP with C#.sln
Normal file
19
mono/android_iap/Android IAP with C#.sln
Normal file
@@ -0,0 +1,19 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android IAP with C#", "Android IAP with C#.csproj", "{FA89D4C3-B45B-49F1-A975-BF059CA82D86}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||
{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||
{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||
{FA89D4C3-B45B-49F1-A975-BF059CA82D86}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
67
mono/android_iap/GodotGooglePlayBilling/BillingResult.cs
Normal file
67
mono/android_iap/GodotGooglePlayBilling/BillingResult.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Android_Iap.GodotGooglePlayBilling
|
||||
{
|
||||
// https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode
|
||||
public enum BillingResponseCode
|
||||
{
|
||||
// The request has reached the maximum timeout before Google Play responds.
|
||||
ServiceTimeout = -3,
|
||||
|
||||
// Requested feature is not supported by Play Store on the current device.
|
||||
FeatureNotSupported = -2,
|
||||
|
||||
// Play Store service is not connected now - potentially transient state.
|
||||
ServiceDisconnected = -1,
|
||||
|
||||
// Success
|
||||
Ok = 0,
|
||||
|
||||
// User pressed back or canceled a dialog
|
||||
UserCanceled = 1,
|
||||
|
||||
// Network connection is down
|
||||
ServiceUnavailable = 2,
|
||||
|
||||
// Billing API version is not supported for the type requested
|
||||
BillingUnavailable = 3,
|
||||
|
||||
// Requested product is not available for purchase
|
||||
ItemUnavailable = 4,
|
||||
|
||||
// Invalid arguments provided to the API.
|
||||
DeveloperError = 5,
|
||||
|
||||
// Fatal error during the API action
|
||||
Error = 6,
|
||||
|
||||
// Failure to purchase since item is already owned
|
||||
ItemAlreadyOwned = 7,
|
||||
|
||||
// Failure to consume since item is not owned
|
||||
ItemNotOwned = 8,
|
||||
}
|
||||
|
||||
public class BillingResult
|
||||
{
|
||||
public BillingResult() { }
|
||||
public BillingResult(Dictionary billingResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
Status = (int)billingResult["status"];
|
||||
ResponseCode = (billingResult.Contains("response_code") ? (BillingResponseCode)billingResult["response_code"] : BillingResponseCode.Ok);
|
||||
DebugMessage = (billingResult.Contains("debug_message") ? (string)billingResult["debug_message"] : null);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
GD.Print("BillingResult: ", ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public int Status { get; set; }
|
||||
public BillingResponseCode ResponseCode { get; set; }
|
||||
public string DebugMessage { get; set; }
|
||||
}
|
||||
}
|
||||
165
mono/android_iap/GodotGooglePlayBilling/GooglePlayBilling.cs
Normal file
165
mono/android_iap/GodotGooglePlayBilling/GooglePlayBilling.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using Godot.Collections;
|
||||
using Godot;
|
||||
|
||||
namespace Android_Iap.GodotGooglePlayBilling
|
||||
{
|
||||
public enum PurchaseType
|
||||
{
|
||||
InApp,
|
||||
Subs
|
||||
}
|
||||
|
||||
public class GooglePlayBilling : Node
|
||||
{
|
||||
[Signal] public delegate void Connected();
|
||||
[Signal] public delegate void Disconnected();
|
||||
[Signal] public delegate void ConnectError(int code, string message);
|
||||
[Signal] public delegate void SkuDetailsQueryCompleted(Array skuDetails);
|
||||
[Signal] public delegate void SkuDetailsQueryError(int code, string message);
|
||||
[Signal] public delegate void PurchasesUpdated(Array purchases);
|
||||
[Signal] public delegate void PurchaseError(int code, string message);
|
||||
[Signal] public delegate void PurchaseAcknowledged(string purchaseToken);
|
||||
[Signal] public delegate void PurchaseAcknowledgementError(int code, string message);
|
||||
[Signal] public delegate void PurchaseConsumed(string purchaseToken);
|
||||
[Signal] public delegate void PurchaseConsumption_error(int code, string message, string purchaseToken);
|
||||
|
||||
[Export] public bool AutoReconnect { get; set; }
|
||||
[Export] public bool AutoConnect { get; set; }
|
||||
|
||||
private Object _payment;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
if (Engine.HasSingleton("GodotGooglePlayBilling"))
|
||||
{
|
||||
GD.Print("GodotGooglePlayBilling HasSingleton");
|
||||
_payment = Engine.GetSingleton("GodotGooglePlayBilling");
|
||||
// These are all signals supported by the API
|
||||
// You can drop some of these based on your needs
|
||||
_payment.Connect("connected", this, nameof(OnGodotGooglePlayBilling_connected)); // No params
|
||||
_payment.Connect("disconnected", this, nameof(OnGodotGooglePlayBilling_disconnected)); // No params
|
||||
_payment.Connect("connect_error", this, nameof(OnGodotGooglePlayBilling_connect_error)); // Response ID (int), Debug message (string)
|
||||
_payment.Connect("sku_details_query_completed", this, nameof(OnGodotGooglePlayBilling_sku_details_query_completed)); // SKUs (Array of Dictionary)
|
||||
_payment.Connect("sku_details_query_error", this, nameof(OnGodotGooglePlayBilling_sku_details_query_error)); // Response ID (int), Debug message (string), Queried SKUs (string[])
|
||||
_payment.Connect("purchases_updated", this, nameof(OnGodotGooglePlayBilling_purchases_updated)); // Purchases (Array of Dictionary)
|
||||
_payment.Connect("purchase_error", this, nameof(OnGodotGooglePlayBilling_purchase_error)); // Response ID (int), Debug message (string)
|
||||
_payment.Connect("purchase_acknowledged", this, nameof(OnGodotGooglePlayBilling_purchase_acknowledged)); // Purchase token (string)
|
||||
_payment.Connect("purchase_acknowledgement_error", this, nameof(OnGodotGooglePlayBilling_purchase_acknowledgement_error)); // Response ID (int), Debug message (string), Purchase token (string)
|
||||
_payment.Connect("purchase_consumed", this, nameof(OnGodotGooglePlayBilling_purchase_consumed)); // Purchase token (string)
|
||||
_payment.Connect("purchase_consumption_error", this, nameof(OnGodotGooglePlayBilling_purchase_consumption_error)); // Response ID (int), Debug message (string), Purchase token (string)
|
||||
|
||||
if (AutoConnect) StartConnection();
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print("GPB: Android IAP support is not enabled. Make sure you have enabled 'Custom Build' and the GodotGooglePlayBilling plugin in your Android export settings! IAP will not work.");
|
||||
}
|
||||
}
|
||||
|
||||
#region GooglePlayBilling Methods
|
||||
|
||||
public void StartConnection() => _payment?.Call("startConnection");
|
||||
|
||||
public void EndConnection() => _payment?.Call("endConnection");
|
||||
|
||||
public BillingResult Purchase(string sku)
|
||||
{
|
||||
if (_payment == null) return null;
|
||||
var result = (Dictionary)_payment.Call("purchase", sku);
|
||||
return new BillingResult(result);
|
||||
}
|
||||
|
||||
public void QuerySkuDetails(string[] querySkuDetails, PurchaseType type) => _payment?.Call("querySkuDetails", querySkuDetails, $"{type}".ToLower());
|
||||
|
||||
public bool IsReady() => (_payment?.Call("isReady") as bool?) ?? false;
|
||||
|
||||
public PurchasesResult QueryPurchases(PurchaseType purchaseType)
|
||||
{
|
||||
if (_payment == null) return null;
|
||||
var result = (Dictionary)_payment.Call("queryPurchases", $"{purchaseType}".ToLower());
|
||||
return new PurchasesResult(result);
|
||||
}
|
||||
|
||||
public void AcknowledgePurchase(string purchaseToken) => _payment?.Call("acknowledgePurchase", purchaseToken);
|
||||
|
||||
public void ConsumePurchase(string purchaseToken) => _payment?.Call("consumePurchase", purchaseToken);
|
||||
|
||||
#endregion
|
||||
|
||||
#region GodotGooglePlayBilling Signals
|
||||
|
||||
private void OnGodotGooglePlayBilling_connected()
|
||||
{
|
||||
GD.Print("GodotGooglePlayBilling Connected");
|
||||
EmitSignal(nameof(Connected));
|
||||
}
|
||||
|
||||
private async void OnGodotGooglePlayBilling_disconnected()
|
||||
{
|
||||
GD.Print("GodotGooglePlayBilling Disconnected");
|
||||
EmitSignal(nameof(Disconnected));
|
||||
|
||||
if (AutoReconnect)
|
||||
{
|
||||
await ToSignal(GetTree().CreateTimer(10), "timeout");
|
||||
StartConnection();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGodotGooglePlayBilling_connect_error(int code, string message)
|
||||
{
|
||||
GD.Print($"GodotGooglePlayBilling ConnectError {code}: {message}");
|
||||
EmitSignal(nameof(ConnectError), code, message);
|
||||
}
|
||||
|
||||
private void OnGodotGooglePlayBilling_sku_details_query_completed(Array skuDetails)
|
||||
{
|
||||
GD.Print($"GodotGooglePlayBilling SkuDetailsQueryCompleted {skuDetails}");
|
||||
EmitSignal(nameof(SkuDetailsQueryCompleted), skuDetails);
|
||||
}
|
||||
|
||||
private void OnGodotGooglePlayBilling_sku_details_query_error(int code, string message)
|
||||
{
|
||||
GD.Print($"SkuDetailsQueryError error {code}: {message}");
|
||||
EmitSignal(nameof(SkuDetailsQueryError), code, message);
|
||||
}
|
||||
|
||||
private void OnGodotGooglePlayBilling_purchases_updated(Array purchases)
|
||||
{
|
||||
GD.Print($"GodotGooglePlayBilling PurchasesUpdated {purchases}");
|
||||
EmitSignal(nameof(PurchasesUpdated), purchases);
|
||||
}
|
||||
|
||||
private void OnGodotGooglePlayBilling_purchase_error(int code, string message)
|
||||
{
|
||||
GD.Print($"GodotGooglePlayBilling PurchaseError {code}: {message}");
|
||||
EmitSignal(nameof(PurchaseError), code, message);
|
||||
}
|
||||
|
||||
private void OnGodotGooglePlayBilling_purchase_acknowledged(string purchaseToken)
|
||||
{
|
||||
GD.Print($"GodotGooglePlayBilling PurchaseAcknowledged {purchaseToken}");
|
||||
EmitSignal(nameof(PurchaseAcknowledged), purchaseToken);
|
||||
}
|
||||
|
||||
private void OnGodotGooglePlayBilling_purchase_acknowledgement_error(int code, string message)
|
||||
{
|
||||
GD.Print($"GodotGooglePlayBilling PurchaseAcknowledgementError error {code}: {message}");
|
||||
EmitSignal(nameof(PurchaseAcknowledgementError), code, message);
|
||||
}
|
||||
|
||||
private void OnGodotGooglePlayBilling_purchase_consumed(string purchaseToken)
|
||||
{
|
||||
GD.Print($"GodotGooglePlayBilling PurchaseConsumed successfully: {purchaseToken}");
|
||||
EmitSignal(nameof(PurchaseConsumed), purchaseToken);
|
||||
}
|
||||
|
||||
private void OnGodotGooglePlayBilling_purchase_consumption_error(int code, string message, string purchaseToken)
|
||||
{
|
||||
GD.Print($"GodotGooglePlayBilling PurchaseConsumption_error error {code}: {message}, purchase token: {purchaseToken}");
|
||||
EmitSignal(nameof(PurchaseConsumption_error), code, message, purchaseToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Android_Iap.GodotGooglePlayBilling
|
||||
{
|
||||
public static class GooglePlayBillingUtils
|
||||
{
|
||||
public static Purchase[] ConvertPurchaseDictionaryArray(Array arrPurchases)
|
||||
{
|
||||
if (arrPurchases == null) return null;
|
||||
var purchases = new Purchase[arrPurchases.Count];
|
||||
for (int i = 0; i < arrPurchases.Count; i++)
|
||||
{
|
||||
purchases[i] = new Purchase((Dictionary)arrPurchases[i]);
|
||||
}
|
||||
|
||||
return purchases;
|
||||
}
|
||||
|
||||
public static SkuDetails[] ConvertSkuDetailsDictionaryArray(Array arrSkuDetails)
|
||||
{
|
||||
if (arrSkuDetails == null) return null;
|
||||
var skusDetails = new SkuDetails[arrSkuDetails.Count];
|
||||
for (int i = 0; i < arrSkuDetails.Count; i++)
|
||||
{
|
||||
skusDetails[i] = new SkuDetails((Dictionary)arrSkuDetails[i]);
|
||||
}
|
||||
|
||||
return skusDetails;
|
||||
}
|
||||
}
|
||||
}
|
||||
74
mono/android_iap/GodotGooglePlayBilling/Purchase.cs
Normal file
74
mono/android_iap/GodotGooglePlayBilling/Purchase.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Android_Iap.GodotGooglePlayBilling
|
||||
{
|
||||
// https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState
|
||||
public enum PurchaseState
|
||||
{
|
||||
UnspecifiedState = 0,
|
||||
Purchased = 1,
|
||||
Pending = 2
|
||||
}
|
||||
|
||||
public class Purchase
|
||||
{
|
||||
public Purchase() { }
|
||||
|
||||
public Purchase(Dictionary purchase)
|
||||
{
|
||||
foreach (var key in purchase.Keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case "order_id":
|
||||
OrderId = (string)purchase[key];
|
||||
break;
|
||||
case "package_name":
|
||||
PackageName = (string)purchase[key];
|
||||
break;
|
||||
case "purchase_state":
|
||||
PurchaseState = (PurchaseState)purchase[key];
|
||||
break;
|
||||
case "purchase_time":
|
||||
PurchaseTime = Convert.ToInt64(purchase[key]);
|
||||
break;
|
||||
case "purchase_token":
|
||||
PurchaseToken = (string)purchase[key];
|
||||
break;
|
||||
case "signature":
|
||||
Signature = (string)purchase[key];
|
||||
break;
|
||||
case "sku":
|
||||
Sku = (string)purchase[key];
|
||||
break;
|
||||
case "is_acknowledged":
|
||||
IsAcknowledged = (bool)purchase[key];
|
||||
break;
|
||||
case "is_auto_renewing":
|
||||
IsAutoRenewing = (bool)purchase[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
GD.Print("Error: ", purchase[key], " -> ", ex.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public string OrderId { get; set; }
|
||||
public string PackageName { get; set; }
|
||||
public PurchaseState PurchaseState { get; set; }
|
||||
public long PurchaseTime { get; set; }
|
||||
public string PurchaseToken { get; set; }
|
||||
public string Signature { get; set; }
|
||||
public string Sku { get; set; }
|
||||
public bool IsAcknowledged { get; set; }
|
||||
public bool IsAutoRenewing { get; set; }
|
||||
}
|
||||
}
|
||||
24
mono/android_iap/GodotGooglePlayBilling/PurchasesResult.cs
Normal file
24
mono/android_iap/GodotGooglePlayBilling/PurchasesResult.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Android_Iap.GodotGooglePlayBilling
|
||||
{
|
||||
public class PurchasesResult : BillingResult
|
||||
{
|
||||
public PurchasesResult() { }
|
||||
public PurchasesResult(Dictionary purchasesResult)
|
||||
: base(purchasesResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
Purchases = (purchasesResult.Contains("purchases") ? GooglePlayBillingUtils.ConvertPurchaseDictionaryArray((Array)purchasesResult["purchases"]) : null);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
GD.Print("PurchasesResult: ", ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public Purchase[] Purchases { get; set; }
|
||||
}
|
||||
}
|
||||
101
mono/android_iap/GodotGooglePlayBilling/SkuDetails.cs
Normal file
101
mono/android_iap/GodotGooglePlayBilling/SkuDetails.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Android_Iap.GodotGooglePlayBilling
|
||||
{
|
||||
public class SkuDetails
|
||||
{
|
||||
public SkuDetails() { }
|
||||
|
||||
public SkuDetails(Dictionary skuDetails)
|
||||
{
|
||||
foreach (var key in skuDetails.Keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case "sku":
|
||||
Sku = (string)skuDetails[key];
|
||||
break;
|
||||
case "title":
|
||||
Title = (string)skuDetails[key];
|
||||
break;
|
||||
case "description":
|
||||
Description = (string)skuDetails[key];
|
||||
break;
|
||||
case "price":
|
||||
Price = (string)skuDetails[key];
|
||||
break;
|
||||
case "price_currency_code":
|
||||
PriceCurrencyCode = (string)skuDetails[key];
|
||||
break;
|
||||
case "price_amount_micros":
|
||||
PriceAmountMicros = Convert.ToInt64(skuDetails[key]);
|
||||
break;
|
||||
case "free_trial_period":
|
||||
FreeTrialPeriod = (string)skuDetails[key];
|
||||
break;
|
||||
case "icon_url":
|
||||
IconUrl = (string)skuDetails[key];
|
||||
break;
|
||||
case "introductory_price":
|
||||
IntroductoryPrice = (string)skuDetails[key];
|
||||
break;
|
||||
case "introductory_price_amount_micros":
|
||||
IntroductoryPriceAmountMicros = Convert.ToInt64(skuDetails[key]);
|
||||
break;
|
||||
case "introductory_price_cycles":
|
||||
IntroductoryPriceCycles = (int)skuDetails[key];
|
||||
break;
|
||||
case "introductory_price_period":
|
||||
IntroductoryPricePeriod = (string)skuDetails[key];
|
||||
break;
|
||||
case "original_price":
|
||||
OriginalPrice = (string)skuDetails[key];
|
||||
break;
|
||||
case "original_price_amount_micros":
|
||||
OriginalPriceAmountMicros = Convert.ToInt64(skuDetails[key]);
|
||||
break;
|
||||
case "subscription_period":
|
||||
SubscriptionPeriod = (string)skuDetails[key];
|
||||
break;
|
||||
case "type":
|
||||
switch(skuDetails[key])
|
||||
{
|
||||
case "inapp":
|
||||
Type = PurchaseType.InApp;
|
||||
break;
|
||||
case "subs":
|
||||
Type = PurchaseType.Subs;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
GD.Print("Error: ", skuDetails[key], " -> ", ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Sku { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Price { get; set; }
|
||||
public string PriceCurrencyCode { get; set; }
|
||||
public long PriceAmountMicros { get; set; }
|
||||
public string FreeTrialPeriod { get; set; }
|
||||
public string IconUrl { get; set; }
|
||||
public string IntroductoryPrice { get; set; }
|
||||
public long IntroductoryPriceAmountMicros { get; set; }
|
||||
public int IntroductoryPriceCycles { get; set; }
|
||||
public string IntroductoryPricePeriod { get; set; }
|
||||
public string OriginalPrice { get; set; }
|
||||
public long OriginalPriceAmountMicros { get; set; }
|
||||
public string SubscriptionPeriod { get; set; }
|
||||
public PurchaseType Type { get; set; }
|
||||
}
|
||||
}
|
||||
232
mono/android_iap/Main.cs
Normal file
232
mono/android_iap/Main.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
using Android_Iap.GodotGooglePlayBilling;
|
||||
using Godot;
|
||||
using CoreGeneric = System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace Android_Iap
|
||||
{
|
||||
/*
|
||||
test skus
|
||||
android.test.purchased
|
||||
android.test.canceled
|
||||
android.test.refunded
|
||||
android.test.item_unavailable
|
||||
*/
|
||||
public class Main : Node2D
|
||||
{
|
||||
private readonly string[] ArrInAppProductsSKUs = new string[]
|
||||
{
|
||||
"android.test.purchased",
|
||||
"android.test.canceled",
|
||||
"android.test.refunded",
|
||||
"android.test.item_unavailable"
|
||||
};
|
||||
|
||||
|
||||
private Button _buyPotionButton;
|
||||
private Label _totalPotionsLabel;
|
||||
|
||||
private Panel _panel;
|
||||
private Label _processLabel;
|
||||
private Label _thanksLabel;
|
||||
|
||||
private ProgressBar _playerLife;
|
||||
private StyleBoxFlat _playerLifeStyleBoxFlat;
|
||||
|
||||
private GooglePlayBilling _googlePlayBilling;
|
||||
private int _totalPotion = 5;
|
||||
|
||||
CoreGeneric.Dictionary<string, string> _purchases = new CoreGeneric.Dictionary<string, string>();
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_googlePlayBilling = GetNode<GooglePlayBilling>("GooglePlayBilling");
|
||||
|
||||
_buyPotionButton = GetNode<Button>("VBoxContainer2/BuyPotionButton");
|
||||
_totalPotionsLabel = GetNode<Label>("VBoxContainer/Label");
|
||||
|
||||
_panel = GetNode<Panel>("Panel");
|
||||
_processLabel = GetNode<Label>("Panel/ProcessLabel");
|
||||
_thanksLabel = GetNode<Label>("Panel/ThanksLabel");
|
||||
|
||||
_playerLife = GetNode<ProgressBar>("Sprite/ProgressBar");
|
||||
|
||||
_playerLifeStyleBoxFlat = _playerLife.Get("custom_styles/fg") as StyleBoxFlat;
|
||||
_playerLifeStyleBoxFlat.BgColor = Colors.Red.LinearInterpolate(Colors.Green, 1);
|
||||
|
||||
_playerLife.Value = 1;
|
||||
|
||||
_panel.Hide();
|
||||
_processLabel.Hide();
|
||||
_thanksLabel.Hide();
|
||||
_buyPotionButton.Hide();
|
||||
_totalPotionsLabel.Text = $"{_totalPotion} Potions";
|
||||
}
|
||||
|
||||
public override void _Process(float delta)
|
||||
{
|
||||
if (_playerLife.Value > 0.5)
|
||||
{
|
||||
_playerLife.Value -= delta;
|
||||
}
|
||||
else if (_playerLife.Value > 0.2)
|
||||
{
|
||||
_playerLife.Value -= delta / 2;
|
||||
}
|
||||
else if (_playerLife.Value > 0.1)
|
||||
{
|
||||
_playerLife.Value -= delta / 4;
|
||||
}
|
||||
|
||||
_playerLifeStyleBoxFlat.BgColor = Colors.Red.LinearInterpolate(Colors.Green, Convert.ToSingle(_playerLife.Value));
|
||||
}
|
||||
|
||||
private void OnUsePotionButton_pressed()
|
||||
{
|
||||
if (_totalPotion > 0)
|
||||
{
|
||||
_totalPotion -= 1;
|
||||
_totalPotionsLabel.Text = $"{_totalPotion} Potions";
|
||||
_playerLifeStyleBoxFlat.BgColor = Colors.Red.LinearInterpolate(Colors.Green, Convert.ToSingle(_playerLife.Value));
|
||||
|
||||
_playerLife.Value += 20;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBuyPotionButton_pressed()
|
||||
{
|
||||
var result = _googlePlayBilling.Purchase("android.test.purchased");
|
||||
if (result != null && result.Status == (int)Error.Ok)
|
||||
{
|
||||
GD.Print("Bought");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print("Failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnButton1_pressed()
|
||||
{
|
||||
var result = _googlePlayBilling.Purchase("android.test.canceled");
|
||||
if (result != null && result.Status == (int)Error.Ok)
|
||||
{
|
||||
GD.Print("Bought");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print("Failed");
|
||||
}
|
||||
}
|
||||
private void OnButton2_pressed()
|
||||
{
|
||||
var result = _googlePlayBilling.Purchase("android.test.refunded");
|
||||
if (result != null && result.Status == (int)Error.Ok)
|
||||
{
|
||||
GD.Print("Bought");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print("Failed");
|
||||
}
|
||||
}
|
||||
private void OnButton3_pressed()
|
||||
{
|
||||
var result = _googlePlayBilling.Purchase("android.test.item_unavailable");
|
||||
if (result != null && result.Status == (int)Error.Ok)
|
||||
{
|
||||
GD.Print("Bought");
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print("Failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOkButton_pressed()
|
||||
{
|
||||
_panel.Hide();
|
||||
_processLabel.Hide();
|
||||
_thanksLabel.Hide();
|
||||
}
|
||||
|
||||
private void OnGooglePlayBilling_Connected()
|
||||
{
|
||||
_googlePlayBilling.QuerySkuDetails(ArrInAppProductsSKUs, PurchaseType.InApp);
|
||||
|
||||
var purchasesResult = _googlePlayBilling.QueryPurchases(PurchaseType.InApp);
|
||||
if (purchasesResult.Status == (int)Error.Ok)
|
||||
{
|
||||
foreach (var purchase in purchasesResult.Purchases)
|
||||
{
|
||||
_purchases.Add(purchase.PurchaseToken, purchase.Sku);
|
||||
// We only expect this SKU
|
||||
if (purchase.Sku == "android.test.purchased")
|
||||
{
|
||||
_googlePlayBilling.AcknowledgePurchase(purchase.PurchaseToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.Print($"Purchase query failed: {purchasesResult.ResponseCode} - {purchasesResult.DebugMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGooglePlayBilling_SkuDetailsQueryCompleted(Godot.Collections.Array arrSkuDetails)
|
||||
{
|
||||
var skuDetails = GooglePlayBillingUtils.ConvertSkuDetailsDictionaryArray(arrSkuDetails);
|
||||
foreach (var sku in skuDetails)
|
||||
{
|
||||
switch (sku.Sku)
|
||||
{
|
||||
// our fake potion
|
||||
case "android.test.purchased":
|
||||
_buyPotionButton.Text = $"Buy {sku.Price}";
|
||||
_buyPotionButton.Show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGooglePlayBilling_PurchasesUpdated(Godot.Collections.Array arrPurchases)
|
||||
{
|
||||
_panel.Show();
|
||||
_processLabel.Show();
|
||||
_thanksLabel.Hide();
|
||||
|
||||
var purchases = GooglePlayBillingUtils.ConvertPurchaseDictionaryArray(arrPurchases);
|
||||
|
||||
foreach (var purchase in purchases)
|
||||
{
|
||||
_purchases.Add(purchase.PurchaseToken, purchase.Sku);
|
||||
// We only expect this SKU
|
||||
if (purchase.Sku == "android.test.purchased")
|
||||
{
|
||||
_googlePlayBilling.AcknowledgePurchase(purchase.PurchaseToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGooglePlayBilling_PurchaseAcknowledged(string purchaseToken)
|
||||
{
|
||||
_googlePlayBilling.ConsumePurchase(purchaseToken);
|
||||
}
|
||||
|
||||
private void OnGooglePlayBilling_PurchaseConsumed(string purchaseToken)
|
||||
{
|
||||
if (_purchases[purchaseToken] == "android.test.purchased")
|
||||
{
|
||||
_totalPotion += 5;
|
||||
_totalPotionsLabel.Text = $"{_totalPotion} Potions";
|
||||
_purchases.Remove(purchaseToken);
|
||||
|
||||
_processLabel.Hide();
|
||||
_thanksLabel.Show();
|
||||
|
||||
}
|
||||
GD.Print("OnGooglePlayBilling_PurchaseConsumed ", purchaseToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
215
mono/android_iap/Main.tscn
Normal file
215
mono/android_iap/Main.tscn
Normal file
@@ -0,0 +1,215 @@
|
||||
[gd_scene load_steps=6 format=2]
|
||||
|
||||
[ext_resource path="res://icon.png" type="Texture" id=1]
|
||||
[ext_resource path="res://Main.cs" type="Script" id=2]
|
||||
[ext_resource path="res://GodotGooglePlayBilling/GooglePlayBilling.cs" type="Script" id=3]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id=1]
|
||||
bg_color = Color( 1, 0, 0, 1 )
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id=2]
|
||||
bg_color = Color( 0, 0, 0, 1 )
|
||||
|
||||
[node name="Main" type="Node2D"]
|
||||
script = ExtResource( 2 )
|
||||
|
||||
[node name="GooglePlayBilling" type="Node" parent="."]
|
||||
script = ExtResource( 3 )
|
||||
AutoConnect = true
|
||||
|
||||
[node name="Sprite" type="Sprite" parent="."]
|
||||
position = Vector2( 239.092, 225.037 )
|
||||
scale = Vector2( 2, 2 )
|
||||
texture = ExtResource( 1 )
|
||||
|
||||
[node name="ProgressBar" type="ProgressBar" parent="Sprite"]
|
||||
margin_left = -57.9025
|
||||
margin_top = -47.7944
|
||||
margin_right = 57.0975
|
||||
margin_bottom = -33.7944
|
||||
custom_styles/fg = SubResource( 1 )
|
||||
custom_styles/bg = SubResource( 2 )
|
||||
max_value = 1.0
|
||||
value = 0.5
|
||||
percent_visible = false
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
margin_left = 65.0538
|
||||
margin_top = 30.4056
|
||||
margin_right = 185.054
|
||||
margin_bottom = 88.4056
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer"]
|
||||
margin_right = 120.0
|
||||
margin_bottom = 14.0
|
||||
text = "1 Potion"
|
||||
align = 1
|
||||
__meta__ = {
|
||||
"_edit_lock_": true,
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="UsePotionButton" type="Button" parent="VBoxContainer"]
|
||||
margin_top = 18.0
|
||||
margin_right = 120.0
|
||||
margin_bottom = 58.0
|
||||
rect_min_size = Vector2( 120, 40 )
|
||||
text = "Use Potion"
|
||||
__meta__ = {
|
||||
"_edit_lock_": true,
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="VBoxContainer2" type="VBoxContainer" parent="."]
|
||||
margin_left = 340.826
|
||||
margin_top = 29.6986
|
||||
margin_right = 460.826
|
||||
margin_bottom = 87.6986
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer2"]
|
||||
margin_right = 121.0
|
||||
margin_bottom = 14.0
|
||||
text = "Get 5 Potions now!"
|
||||
align = 1
|
||||
__meta__ = {
|
||||
"_edit_lock_": true,
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="BuyPotionButton" type="Button" parent="VBoxContainer2"]
|
||||
margin_top = 18.0
|
||||
margin_right = 121.0
|
||||
margin_bottom = 58.0
|
||||
rect_min_size = Vector2( 120, 40 )
|
||||
text = "Buy"
|
||||
__meta__ = {
|
||||
"_edit_lock_": true,
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="VBoxContainer3" type="VBoxContainer" parent="."]
|
||||
margin_left = 373.353
|
||||
margin_top = 143.543
|
||||
margin_right = 494.353
|
||||
margin_bottom = 289.543
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer3"]
|
||||
margin_right = 121.0
|
||||
margin_bottom = 14.0
|
||||
text = "Other Items"
|
||||
align = 1
|
||||
__meta__ = {
|
||||
"_edit_lock_": true,
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Button1" type="Button" parent="VBoxContainer3"]
|
||||
margin_top = 18.0
|
||||
margin_right = 121.0
|
||||
margin_bottom = 58.0
|
||||
rect_min_size = Vector2( 120, 40 )
|
||||
text = "Canceled Item"
|
||||
__meta__ = {
|
||||
"_edit_lock_": true,
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Button2" type="Button" parent="VBoxContainer3"]
|
||||
margin_top = 62.0
|
||||
margin_right = 121.0
|
||||
margin_bottom = 102.0
|
||||
rect_min_size = Vector2( 120, 40 )
|
||||
text = "Refunded Item"
|
||||
__meta__ = {
|
||||
"_edit_lock_": true,
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Button3" type="Button" parent="VBoxContainer3"]
|
||||
margin_top = 106.0
|
||||
margin_right = 121.0
|
||||
margin_bottom = 146.0
|
||||
rect_min_size = Vector2( 120, 40 )
|
||||
text = "Unavailable Item"
|
||||
__meta__ = {
|
||||
"_edit_lock_": true,
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
visible = false
|
||||
self_modulate = Color( 1, 1, 1, 0.666667 )
|
||||
margin_left = 114.414
|
||||
margin_top = 38.6777
|
||||
margin_right = 379.414
|
||||
margin_bottom = 235.678
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="ProcessLabel" type="Label" parent="Panel"]
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -88.0
|
||||
margin_top = -8.5
|
||||
margin_right = 88.0
|
||||
margin_bottom = 8.5
|
||||
text = "Processing..."
|
||||
align = 1
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="ThanksLabel" type="Label" parent="Panel"]
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -88.0
|
||||
margin_top = -8.5
|
||||
margin_right = 88.0
|
||||
margin_bottom = 8.5
|
||||
text = "Thanks for your purchase"
|
||||
align = 1
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="OkButton" type="Button" parent="Panel"]
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
margin_left = -48.0
|
||||
margin_top = -52.0919
|
||||
margin_right = 48.0
|
||||
margin_bottom = -19.0919
|
||||
text = "Ok"
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
[connection signal="Connected" from="GooglePlayBilling" to="." method="OnGooglePlayBilling_Connected"]
|
||||
[connection signal="PurchaseAcknowledged" from="GooglePlayBilling" to="." method="OnGooglePlayBilling_PurchaseAcknowledged"]
|
||||
[connection signal="PurchaseConsumed" from="GooglePlayBilling" to="." method="OnGooglePlayBilling_PurchaseConsumed"]
|
||||
[connection signal="PurchasesUpdated" from="GooglePlayBilling" to="." method="OnGooglePlayBilling_PurchasesUpdated"]
|
||||
[connection signal="SkuDetailsQueryCompleted" from="GooglePlayBilling" to="." method="OnGooglePlayBilling_SkuDetailsQueryCompleted"]
|
||||
[connection signal="pressed" from="VBoxContainer/UsePotionButton" to="." method="OnUsePotionButton_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer2/BuyPotionButton" to="." method="OnBuyPotionButton_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer3/Button1" to="." method="OnButton1_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer3/Button2" to="." method="OnButton2_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer3/Button3" to="." method="OnButton3_pressed"]
|
||||
[connection signal="pressed" from="Panel/OkButton" to="." method="OnOkButton_pressed"]
|
||||
25
mono/android_iap/Properties/AssemblyInfo.cs
Normal file
25
mono/android_iap/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Reflection;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
|
||||
[assembly: AssemblyTitle("Android IAP with C#")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
||||
21
mono/android_iap/README.md
Normal file
21
mono/android_iap/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Android IAP with C#
|
||||
|
||||
A simple Android IAP game. This demo shows how to buy a product and consume it for game development in Godot, including
|
||||
[Android in-app purchases](https://docs.godotengine.org/en/latest/tutorials/platform/android_in_app_purchases.html).
|
||||
|
||||
Language: [C#](https://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/index.html)
|
||||
|
||||
Renderer: GLES 2
|
||||
|
||||
Note: There is a GDScript version available, but it's a bit simpler [here](https://github.com/godotengine/godot-demo-projects/tree/master/mobile/android_iap).
|
||||
|
||||
## How does it work?
|
||||
|
||||
You have to use potions to heal the fake Player and you have only 5 potions left, you can just buy more to keep healing the Player. The purchase is fake and won't charge anything, it uses the fake ids provided by Google.
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
7
mono/android_iap/default_env.tres
Normal file
7
mono/android_iap/default_env.tres
Normal file
@@ -0,0 +1,7 @@
|
||||
[gd_resource type="Environment" load_steps=2 format=2]
|
||||
|
||||
[sub_resource type="ProceduralSky" id=1]
|
||||
|
||||
[resource]
|
||||
background_mode = 2
|
||||
background_sky = SubResource( 1 )
|
||||
BIN
mono/android_iap/icon.png
Normal file
BIN
mono/android_iap/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
34
mono/android_iap/icon.png.import
Normal file
34
mono/android_iap/icon.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.png"
|
||||
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
35
mono/android_iap/project.godot
Normal file
35
mono/android_iap/project.godot
Normal file
@@ -0,0 +1,35 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=4
|
||||
|
||||
_global_script_classes=[ ]
|
||||
_global_script_class_icons={
|
||||
|
||||
}
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Android IAP with C#"
|
||||
config/description="A simple Android IAP game. This demo shows how to buy a product and consume it."
|
||||
run/main_scene="res://Main.tscn"
|
||||
config/icon="res://icon.png"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/width=512
|
||||
window/size/height=300
|
||||
window/stretch/mode="2d"
|
||||
window/stretch/aspect="keep"
|
||||
|
||||
[rendering]
|
||||
|
||||
quality/driver/driver_name="GLES2"
|
||||
vram_compression/import_etc=true
|
||||
vram_compression/import_etc2=false
|
||||
environment/default_environment="res://default_env.tres"
|
||||
1
mono/android_iap/screenshots/.gdignore
Normal file
1
mono/android_iap/screenshots/.gdignore
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
BIN
mono/android_iap/screenshots/1.jpg
Normal file
BIN
mono/android_iap/screenshots/1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
mono/android_iap/screenshots/2.jpg
Normal file
BIN
mono/android_iap/screenshots/2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
mono/android_iap/screenshots/3.jpg
Normal file
BIN
mono/android_iap/screenshots/3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
mono/android_iap/screenshots/4.jpg
Normal file
BIN
mono/android_iap/screenshots/4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Reference in New Issue
Block a user