The Currencies sub-asmdef of com.cupkekgames.resources provides a global, save-data-scoped wallet keyed by currency id.
CupkekGames.Resources.CurrenciesCupkekGames.Resources.Currenciescom.cupkekgames.data, com.cupkekgames.services| Type | Role |
|---|---|
Currency (struct) | (string Id, long Amount) transient pair — used as drop result, event payload, or transactional descriptor |
CurrencyDefinitionSO | Per-currency ScriptableObject — display name, icon, max amount, format string |
CurrencyCatalog | AssetCatalog<CurrencyDefinitionSO>. Register the catalog asset with _catalogId = "Currencies" (see CurrencyConstants.CurrenciesCatalogId) |
Wallet | IData runtime store. Get / Set / Add / Spend / Has / CanAfford + OnChanged(id, old, new) event |
CurrencyConstants | Holds the canonical "Currencies" catalog id |
CurrencyDefinitionSO per currency — Create → CupkekGames/Resources/Currency Definition. Set display name, icon, optional max amount, format string (e.g. "N0" for 1,240).CurrencyCatalog asset — Create → CupkekGames/Resources/Catalog/Currencies. Set _catalogId = "Currencies". Drag the definition assets into its database.ServiceRegistrySO so it registers itself on scene/init load.The asset name of each CurrencyDefinitionSO doubles as its id. Match the asset name to the stable id you'll use everywhere (Gold.asset → Wallet.Get("Gold")).
using CupkekGames.Resources.Currencies;
// Wallet lives on your save data (or wherever player-scoped state goes).
public class MyGameSaveData : IGameSaveData, IData
{
public Wallet Wallet = new();
// ...
}
// Earn:
saveData.Wallet.Add("Gold", 50); // OnChanged fires (id, old, new)
// Spend:
if (saveData.Wallet.Spend("Gold", 100))
BuyItem();
else
ShowInsufficientFunds();
// Query:
long gold = saveData.Wallet.Get("Gold");
bool affordable = saveData.Wallet.CanAfford("Diamond", 5);Wallet uses long storage so idle-game-scale balances don't overflow.
Subscribe to Wallet.OnChanged from your binder. The event fires on Set, Add, and successful Spend:
saveData.Wallet.OnChanged += (id, oldValue, newValue) =>
{
var label = chipsByCurrency[id];
AnimateCountUp(label, oldValue, newValue);
};No polling required.
DropTable.Evaluate returns List<DropResult> where each result has a CatalogKey + Amount. A drop entry pointing at Currencies/Gold (catalog "Currencies", key "Gold") with Chance: 1.0, MinAmount: 10, MaxAmount: 50 is valid markup — the consumer just routes by result.Key.Catalog:
foreach (var result in drops)
{
if (result.Key.Catalog == CurrencyConstants.CurrenciesCatalogId)
wallet.Add(result.Key.Key, result.Amount);
else
inventory.AddItem(...);
}No changes to DropTable are needed — it's already polymorphic over catalog id.
Wallet implements IData:
CloneData() → deep copy of the balance dictionary (no service-locator calls — safe for the default→actual reset pattern documented under DataSO workflows)Validate() → always returns true (no invariants beyond "Dictionary is well-formed")OnAfterDeserialize() → no-op (Wallet stores primitives only)Serialize via your project's IDataSerializer (e.g. com.cupkekgames.newtonsoft).
Each CurrencyDefinitionSO carries a FormatString field (.NET format string, default "N0"). Use it for label display:
var def = currencyCatalog.GetValue("Gold");
string display = def.Beautify(saveData.Wallet.Get("Gold")); // "1,240"This keeps formatting per-currency (e.g. "1,234 Gold" vs "1.234M Energy") without UI code knowing the rules.
Settings
Theme
Light
Contrast
Material
Dark
Dim
Material Dark
System
Sidebar(Light & Contrast only)
Font Family
DM Sans
Wix
Inclusive Sans
AR One Sans
Direction