596 lines
23 KiB
C#
596 lines
23 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using System;
|
|
using System.Linq;
|
|
|
|
namespace BulletHellTemplate
|
|
{
|
|
/// <summary>
|
|
/// Manages the monetization system, including currencies and purchased items.
|
|
/// It ensures that the system is initialized and persists across scenes.
|
|
/// </summary>
|
|
public class MonetizationManager : MonoBehaviour
|
|
{
|
|
[Header("Currencies List")]
|
|
[Tooltip("List of all the currencies in the game.")]
|
|
public List<Currency> currencies = new List<Currency>();
|
|
|
|
// Lists to store purchased items
|
|
private List<PurchasedCharacter> purchasedCharacters = new List<PurchasedCharacter>();
|
|
private List<PurchasedIcon> purchasedIcons = new List<PurchasedIcon>();
|
|
private List<PurchasedFrame> purchasedFrames = new List<PurchasedFrame>();
|
|
private List<PurchasedShopItem> purchasedShopItems = new List<PurchasedShopItem>();
|
|
private List<PurchasedInventoryItem> purchasedInventoryItems = new List<PurchasedInventoryItem>();
|
|
|
|
private bool isInitialized = false; // Tracks if the manager is initialized
|
|
public static MonetizationManager Singleton;
|
|
|
|
private void Awake()
|
|
{
|
|
if (Singleton == null)
|
|
{
|
|
Singleton = this;
|
|
DontDestroyOnLoad(gameObject); // Prevent destruction across scenes
|
|
InitializeManager();
|
|
}
|
|
else
|
|
{
|
|
Destroy(gameObject); // Ensure only one instance exists
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the MonetizationManager and loads purchased items from PlayerPrefs.
|
|
/// </summary>
|
|
private void InitializeManager()
|
|
{
|
|
LoadPurchasedItems();
|
|
isInitialized = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns whether the MonetizationManager has been initialized.
|
|
/// </summary>
|
|
/// <returns>True if initialized, false otherwise.</returns>
|
|
public bool IsInitialized()
|
|
{
|
|
return isInitialized;
|
|
}
|
|
|
|
public static event Action<string, int> OnCurrencyChanged;
|
|
|
|
/// <summary>
|
|
/// Sets the currency amount locally and updates it in PlayerPrefs and the backend.
|
|
/// </summary>
|
|
/// <param name="currency">The currency identifier.</param>
|
|
/// <param name="amount">The new amount to set.</param>
|
|
public static void SetCurrency(string currency, int amount)
|
|
{
|
|
PlayerPrefs.SetInt(currency, amount);
|
|
PlayerPrefs.Save();
|
|
|
|
OnCurrencyChanged?.Invoke(currency, amount);
|
|
|
|
if (BackendManager.Singleton.CheckInitialized())
|
|
{
|
|
Singleton.StartCoroutine(CallUpdateCurrencyCoroutine(currency, amount));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine that calls the backend to update currency asynchronously.
|
|
/// </summary>
|
|
private static IEnumerator CallUpdateCurrencyCoroutine(string currency, int amount)
|
|
{
|
|
var task = BackendManager.Singleton.UpdateCurrencies(currency, amount);
|
|
yield return new WaitUntil(() => task.IsCompleted);
|
|
|
|
if (task.IsFaulted)
|
|
{
|
|
Debug.LogError($"Failed to update currency {currency}: " + task.Exception);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"Currency {currency} successfully updated in Backend. Amount: {amount}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine that saves conventional purchased items using SavePurchasedItemsAsync
|
|
/// and then saves each inventory item individually using SavePurchasedInventoryItemAsync.
|
|
/// </summary>
|
|
private static IEnumerator CallSavePurchasedItemsCoroutine()
|
|
{
|
|
// 1) Save conventional purchased items (Characters, Icons, Frames, ShopItems)
|
|
var task = BackendManager.Singleton.SavePurchasedItemsAsync();
|
|
yield return new WaitUntil(() => task.IsCompleted);
|
|
|
|
if (task.IsFaulted)
|
|
{
|
|
Debug.LogError($"Failed to save purchased items for user: " + task.Exception);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("Purchased items (characters/icons/frames/shop) saved successfully.");
|
|
}
|
|
|
|
foreach (var invItem in Singleton.purchasedInventoryItems)
|
|
{
|
|
var invTask = BackendManager.Singleton.SavePurchasedInventoryItemAsync(invItem);
|
|
yield return new WaitUntil(() => invTask.IsCompleted);
|
|
|
|
if (invTask.IsFaulted)
|
|
{
|
|
Debug.LogError($"Failed to save purchased inventory item {invItem.uniqueItemGuid}: "
|
|
+ invTask.Exception);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"Inventory item saved: {invItem.uniqueItemGuid} -> {invItem.itemId}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the currency amount from PlayerPrefs.
|
|
/// </summary>
|
|
/// <param name="currency">The currency identifier.</param>
|
|
/// <returns>The amount of the specified currency.</returns>
|
|
public static int GetCurrency(string currency)
|
|
{
|
|
return PlayerPrefs.GetInt(currency);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unlocks an item from the Battle Pass and saves the associated rewards.
|
|
/// </summary>
|
|
/// <param name="item">The BattlePassItem to be unlocked.</param>
|
|
public void BattlePassUnlockItem(BattlePassItem item)
|
|
{
|
|
// Add characters to purchased list if not already purchased
|
|
if (item.rewardType == BattlePassItem.RewardType.CharacterReward)
|
|
{
|
|
foreach (var character in item.characterData)
|
|
{
|
|
if (!IsCharacterPurchased(character.characterId.ToString()))
|
|
{
|
|
purchasedCharacters.Add(new PurchasedCharacter { characterId = character.characterId.ToString() });
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add icons to purchased list if not already purchased
|
|
if (item.rewardType == BattlePassItem.RewardType.IconReward)
|
|
{
|
|
if (!IsIconPurchased(item.iconReward.iconId.ToString()))
|
|
{
|
|
purchasedIcons.Add(new PurchasedIcon { iconId = item.iconReward.iconId.ToString() });
|
|
}
|
|
}
|
|
|
|
// Add frames to purchased list if not already purchased
|
|
if (item.rewardType == BattlePassItem.RewardType.FrameReward)
|
|
{
|
|
if (!IsFramePurchased(item.frameReward.frameId.ToString()))
|
|
{
|
|
purchasedFrames.Add(new PurchasedFrame { frameId = item.frameReward.frameId.ToString() });
|
|
}
|
|
}
|
|
|
|
// Add inventory items to purchased list if not already purchased
|
|
if (item.rewardType == BattlePassItem.RewardType.InventoryItemReward)
|
|
{
|
|
foreach (var inventoryItem in item.inventoryItems)
|
|
{
|
|
if (inventoryItem != null)
|
|
{
|
|
string shortId = GenerateShortID(5);
|
|
PurchasedInventoryItem newItem = new PurchasedInventoryItem
|
|
{
|
|
uniqueItemGuid = shortId,
|
|
itemId = inventoryItem.itemId,
|
|
itemLevel = 0,
|
|
upgrades = new Dictionary<int, int>()
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add currency reward
|
|
if (item.rewardType == BattlePassItem.RewardType.CurrencyReward)
|
|
{
|
|
var currencyName = item.currencyReward.currency.coinID;
|
|
var amount = item.currencyReward.amount;
|
|
int currentAmount = GetCurrency(currencyName);
|
|
SetCurrency(currencyName, currentAmount + amount);
|
|
}
|
|
|
|
SavePurchasedItems(item.rewardType == BattlePassItem.RewardType.InventoryItemReward);
|
|
|
|
Debug.Log($"Battle Pass item unlocked: {item.itemTitle}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Purchases a ShopItem, unlocking its associated characters, icons, frames, and inventory items.
|
|
/// </summary>
|
|
/// <param name="item">The ShopItem to be purchased.</param>
|
|
public void PurchaseItem(ShopItem item)
|
|
{
|
|
if (IsShopItemPurchased(item.itemId))
|
|
{
|
|
Debug.LogWarning("Item already purchased.");
|
|
return;
|
|
}
|
|
|
|
// Add characters to purchased list if not already purchased
|
|
foreach (var character in item.characterData)
|
|
{
|
|
if (!IsCharacterPurchased(character.characterId.ToString()))
|
|
{
|
|
purchasedCharacters.Add(new PurchasedCharacter { characterId = character.characterId.ToString() });
|
|
}
|
|
}
|
|
|
|
// Add icons
|
|
foreach (var icon in item.icons)
|
|
{
|
|
if (!IsIconPurchased(icon.iconId.ToString()))
|
|
{
|
|
purchasedIcons.Add(new PurchasedIcon { iconId = icon.iconId.ToString() });
|
|
}
|
|
}
|
|
|
|
// Add frames
|
|
foreach (var frame in item.frames)
|
|
{
|
|
if (!IsFramePurchased(frame.frameId.ToString()))
|
|
{
|
|
purchasedFrames.Add(new PurchasedFrame { frameId = frame.frameId.ToString() });
|
|
}
|
|
}
|
|
|
|
// Add InventoryItems
|
|
if (item.inventoryItems != null)
|
|
{
|
|
foreach (var inventoryItem in item.inventoryItems)
|
|
{
|
|
if (inventoryItem != null)
|
|
{
|
|
string shortId = GenerateShortID(5);
|
|
PurchasedInventoryItem newItem = new PurchasedInventoryItem
|
|
{
|
|
uniqueItemGuid = shortId,
|
|
itemId = inventoryItem.itemId,
|
|
itemLevel = 0,
|
|
upgrades = new Dictionary<int, int>()
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the shop item to purchased list
|
|
purchasedShopItems.Add(new PurchasedShopItem { itemId = item.itemId });
|
|
|
|
// Save purchased items
|
|
SavePurchasedItems(item.inventoryItems != null);
|
|
Debug.Log($"Purchased item: {item.itemTitle}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Purchases an individual InventoryItem with a shorter unique ID (5 characters).
|
|
/// </summary>
|
|
/// <param name="itemId">The itemId of the ScriptableObject representing the item.</param>
|
|
public void PurchaseInventoryItem(string itemId)
|
|
{
|
|
// Optional: check if duplicates are allowed
|
|
bool alreadyOwned = purchasedInventoryItems.Any(pi => pi.itemId == itemId);
|
|
if (alreadyOwned)
|
|
{
|
|
Debug.Log($"ItemId {itemId} is already owned. Skipping purchase.");
|
|
return;
|
|
}
|
|
|
|
// Generate a short ID of 5 characters
|
|
string shortId = GenerateShortID(5);
|
|
|
|
PurchasedInventoryItem newItem = new PurchasedInventoryItem
|
|
{
|
|
uniqueItemGuid = shortId,
|
|
itemId = itemId,
|
|
itemLevel = 0,
|
|
upgrades = new Dictionary<int, int>()
|
|
};
|
|
|
|
purchasedInventoryItems.Add(newItem);
|
|
SavePurchasedItems(true);
|
|
|
|
Debug.Log($"Purchased new item {itemId} with ShortID {shortId}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a short ID with alphanumeric characters (A-Z, 0-9).
|
|
/// Default length is 5.
|
|
/// </summary>
|
|
private string GenerateShortID(int length = 5)
|
|
{
|
|
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
System.Random random = new System.Random();
|
|
char[] buffer = new char[length];
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
buffer[i] = chars[random.Next(chars.Length)];
|
|
}
|
|
return new string(buffer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a character is purchased.
|
|
/// </summary>
|
|
public bool IsCharacterPurchased(string characterId)
|
|
{
|
|
return purchasedCharacters.Any(character => character.characterId == characterId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if an icon is purchased.
|
|
/// </summary>
|
|
public bool IsIconPurchased(string iconId)
|
|
{
|
|
return purchasedIcons.Any(icon => icon.iconId == iconId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a frame is purchased.
|
|
/// </summary>
|
|
public bool IsFramePurchased(string frameId)
|
|
{
|
|
return purchasedFrames.Any(frame => frame.frameId == frameId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if an inventory item is already purchased by matching itemId.
|
|
/// </summary>
|
|
public bool IsInventoryItemPurchased(string itemId)
|
|
{
|
|
return purchasedInventoryItems.Any(inventoryItem => inventoryItem.itemId == itemId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a shop item is purchased.
|
|
/// </summary>
|
|
public bool IsShopItemPurchased(string itemId)
|
|
{
|
|
return purchasedShopItems.Any(shopItem => shopItem.itemId == itemId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the icon of a specific currency if found.
|
|
/// </summary>
|
|
public Sprite GetCurrencyIcon(string currency)
|
|
{
|
|
foreach (Currency item in MonetizationManager.Singleton.currencies)
|
|
{
|
|
if (item.coinID == currency)
|
|
{
|
|
return item.icon;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
public void PurchaseInventoryItemNoImmediateSave(string itemId)
|
|
{
|
|
bool alreadyOwned = purchasedInventoryItems.Any(pi => pi.itemId == itemId);
|
|
if (alreadyOwned)
|
|
return;
|
|
|
|
string shortId = GenerateShortID(5);
|
|
PurchasedInventoryItem newItem = new PurchasedInventoryItem
|
|
{
|
|
uniqueItemGuid = shortId,
|
|
itemId = itemId,
|
|
itemLevel = 0,
|
|
upgrades = new Dictionary<int, int>()
|
|
};
|
|
|
|
purchasedInventoryItems.Add(newItem);
|
|
Debug.Log($"Purchased (no immediate save) new item {itemId} with ShortID {shortId}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves purchased items to PlayerPrefs, then calls the backend to store them.
|
|
/// It also saves each inventory item individually.
|
|
/// </summary>
|
|
public void SavePurchasedItems(bool isInventoryItem)
|
|
{
|
|
// 1) Convert all purchased item lists to JSON
|
|
string charactersJson = JsonUtility.ToJson(new PurchasedCharacterList(purchasedCharacters));
|
|
string iconsJson = JsonUtility.ToJson(new PurchasedIconList(purchasedIcons));
|
|
string framesJson = JsonUtility.ToJson(new PurchasedFrameList(purchasedFrames));
|
|
string inventoryItemsJson = JsonUtility.ToJson(new PurchasedInventoryItemList(purchasedInventoryItems));
|
|
string shopItemsJson = JsonUtility.ToJson(new PurchasedShopItemList(purchasedShopItems));
|
|
|
|
// 2) Save to PlayerPrefs
|
|
PlayerPrefs.SetString("PurchasedCharacters", charactersJson);
|
|
PlayerPrefs.SetString("PurchasedIcons", iconsJson);
|
|
PlayerPrefs.SetString("PurchasedFrames", framesJson);
|
|
PlayerPrefs.SetString("PurchasedInventoryItems", inventoryItemsJson);
|
|
PlayerPrefs.SetString("PurchasedShopItems", shopItemsJson);
|
|
PlayerPrefs.Save();
|
|
|
|
// 3) If the backend is available, call the coroutine to save them
|
|
if (BackendManager.Singleton.CheckInitialized() && isInventoryItem)
|
|
{
|
|
Singleton.StartCoroutine(CallSavePurchasedItemsCoroutine());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads purchased items from PlayerPrefs into the lists at startup.
|
|
/// </summary>
|
|
private void LoadPurchasedItems()
|
|
{
|
|
// Load from PlayerPrefs
|
|
string charactersJson = PlayerPrefs.GetString(
|
|
"PurchasedCharacters",
|
|
JsonUtility.ToJson(new PurchasedCharacterList(new List<PurchasedCharacter>()))
|
|
);
|
|
string iconsJson = PlayerPrefs.GetString(
|
|
"PurchasedIcons",
|
|
JsonUtility.ToJson(new PurchasedIconList(new List<PurchasedIcon>()))
|
|
);
|
|
string framesJson = PlayerPrefs.GetString(
|
|
"PurchasedFrames",
|
|
JsonUtility.ToJson(new PurchasedFrameList(new List<PurchasedFrame>()))
|
|
);
|
|
string inventoryItemsJson = PlayerPrefs.GetString(
|
|
"PurchasedInventoryItems",
|
|
JsonUtility.ToJson(new PurchasedInventoryItemList(new List<PurchasedInventoryItem>()))
|
|
);
|
|
string shopItemsJson = PlayerPrefs.GetString(
|
|
"PurchasedShopItems",
|
|
JsonUtility.ToJson(new PurchasedShopItemList(new List<PurchasedShopItem>()))
|
|
);
|
|
|
|
purchasedCharacters = JsonUtility.FromJson<PurchasedCharacterList>(charactersJson).purchasedCharacters;
|
|
purchasedIcons = JsonUtility.FromJson<PurchasedIconList>(iconsJson).purchasedIcons;
|
|
purchasedFrames = JsonUtility.FromJson<PurchasedFrameList>(framesJson).purchasedFrames;
|
|
purchasedInventoryItems = JsonUtility.FromJson<PurchasedInventoryItemList>(inventoryItemsJson).purchasedInventoryItems;
|
|
purchasedShopItems = JsonUtility.FromJson<PurchasedShopItemList>(shopItemsJson).purchasedShopItems;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the lists of purchased items with the new data, then saves them.
|
|
/// </summary>
|
|
public void UpdatePurchasedItems(
|
|
List<PurchasedCharacter> characters,
|
|
List<PurchasedIcon> icons,
|
|
List<PurchasedFrame> frames,
|
|
List<PurchasedShopItem> shopItems,
|
|
List<PurchasedInventoryItem> inventoryItems
|
|
)
|
|
{
|
|
Debug.Log($"Updating purchased items. Characters: {characters.Count}, Icons: {icons.Count}, Frames: {frames.Count}, ShopItems: {shopItems.Count}, Inventory: {inventoryItems.Count}");
|
|
|
|
purchasedCharacters = new List<PurchasedCharacter>(characters);
|
|
purchasedIcons = new List<PurchasedIcon>(icons);
|
|
purchasedFrames = new List<PurchasedFrame>(frames);
|
|
purchasedInventoryItems = new List<PurchasedInventoryItem>(inventoryItems);
|
|
purchasedShopItems = new List<PurchasedShopItem>(shopItems);
|
|
|
|
SavePurchasedItems(false);
|
|
}
|
|
|
|
// Additional helper methods to set purchased item lists individually
|
|
public void SetPurchasedCharacters(List<PurchasedCharacter> characters) => SetPurchasedList(ref purchasedCharacters, characters);
|
|
public void SetPurchasedIcons(List<PurchasedIcon> icons) => SetPurchasedList(ref purchasedIcons, icons);
|
|
public void SetPurchasedFrames(List<PurchasedFrame> frames) => SetPurchasedList(ref purchasedFrames, frames);
|
|
public void SetPurchasedInventoryItems(List<PurchasedInventoryItem> items) => SetPurchasedList(ref purchasedInventoryItems, items);
|
|
public void SetPurchasedShopItems(List<PurchasedShopItem> shopItems) => SetPurchasedList(ref purchasedShopItems, shopItems);
|
|
|
|
private void SetPurchasedList<T>(ref List<T> targetList, List<T> newList)
|
|
{
|
|
targetList = new List<T>(newList);
|
|
SavePurchasedItems(false);
|
|
}
|
|
|
|
// Getter methods for purchased items
|
|
public List<PurchasedCharacter> GetPurchasedCharacters() => new List<PurchasedCharacter>(purchasedCharacters);
|
|
public List<PurchasedIcon> GetPurchasedIcons() => new List<PurchasedIcon>(purchasedIcons);
|
|
public List<PurchasedFrame> GetPurchasedFrames() => new List<PurchasedFrame>(purchasedFrames);
|
|
public List<PurchasedInventoryItem> GetPurchasedInventoryItems() => new List<PurchasedInventoryItem>(purchasedInventoryItems);
|
|
public List<PurchasedShopItem> GetPurchasedShopItems() => new List<PurchasedShopItem>(purchasedShopItems);
|
|
}
|
|
|
|
// Classes for serializing purchased items
|
|
[System.Serializable]
|
|
public class PurchasedCharacter
|
|
{
|
|
public string characterId;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class PurchasedIcon
|
|
{
|
|
public string iconId;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class PurchasedFrame
|
|
{
|
|
public string frameId;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class PurchasedInventoryItem
|
|
{
|
|
public string uniqueItemGuid;
|
|
public string itemId;
|
|
public int itemLevel;
|
|
public Dictionary<int, int> upgrades;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class PurchasedShopItem
|
|
{
|
|
public string itemId;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class PurchasedCharacterList
|
|
{
|
|
public List<PurchasedCharacter> purchasedCharacters = new List<PurchasedCharacter>();
|
|
|
|
public PurchasedCharacterList(List<PurchasedCharacter> characters)
|
|
{
|
|
this.purchasedCharacters = characters;
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class PurchasedIconList
|
|
{
|
|
public List<PurchasedIcon> purchasedIcons = new List<PurchasedIcon>();
|
|
|
|
public PurchasedIconList(List<PurchasedIcon> icons)
|
|
{
|
|
this.purchasedIcons = icons;
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class PurchasedFrameList
|
|
{
|
|
public List<PurchasedFrame> purchasedFrames = new List<PurchasedFrame>();
|
|
|
|
public PurchasedFrameList(List<PurchasedFrame> frames)
|
|
{
|
|
this.purchasedFrames = frames;
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class PurchasedInventoryItemList
|
|
{
|
|
public List<PurchasedInventoryItem> purchasedInventoryItems = new List<PurchasedInventoryItem>();
|
|
|
|
public PurchasedInventoryItemList(List<PurchasedInventoryItem> inventoryItems)
|
|
{
|
|
this.purchasedInventoryItems = inventoryItems;
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class PurchasedShopItemList
|
|
{
|
|
public List<PurchasedShopItem> purchasedShopItems = new List<PurchasedShopItem>();
|
|
|
|
public PurchasedShopItemList(List<PurchasedShopItem> shopItems)
|
|
{
|
|
this.purchasedShopItems = shopItems;
|
|
}
|
|
}
|
|
}
|