Virtual Currency - Overview
The Beamable Currency feature allows game makers to manage in-game currencies for purchasing and economic systems. Virtual Currencies provide a flexible economic foundation for your game, enabling players to earn, spend, and manage various types of in-game money. The Virtual Currency system in Beamable is built on top of the Content System. Currencies exist out of the box in the Beamable SDK as a content type.

Managing Currencies
Currencies can be configured from the Content Manager Editor, with optional parameters for Starting Amount for new players, and "Write Self" under Client Permissions (meaning the client can modify their own currencies). Here is an example of a currency as seen in the inspector:

The currencies for a given player can be managed in several ways, depending on your use case.
Client-Authoritative (Unity code): If your game does not include networked multiplayer and can tolerate cheating, allowing the client to read/write their own currencies is the simplest option. Examples of this can be seen in the bellow. Note that your currency must have "Write Self" enabled.
Server-Authoritative (Microservice): A much more secure way to handle currency modifications is via a Microservice. In this scenario, the client is not able to modify their currencies directly, it is handled on the server. You can check the Microservices section for more information.
Portal (Development): Player currencies can also be modified through the Portal; Note that this should only be used during development or to make corrections to a player account.
Virtual Currency API
The Beamable API provides helper functions to subscribe to changes in the currency, and modify the currency (if the client can write itself). The basis for the Currency system is built on the InventoryService, which is built on the Content service. If you're not familiar with how Content operations work, you can read about them in the Content section.
The main API components are InventoryUpdateBuilder which builds a list of one or more currency operations to execute, and InventoryService which executes the currency operations.
Note: This Beamable system handles player inventory and also player currency.
Adding Currency
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | public async void AddCurrency()
{
// Resolve Reference
var currencyContentPrimary = await _currencyRefPrimary.Resolve();
// List the operations
InventoryUpdateBuilder inventoryUpdateBuilder = new InventoryUpdateBuilder();
inventoryUpdateBuilder.CurrencyChange(currencyContentPrimary.Id, +1);
// Execute the operations
await _beamContext.Inventory.Update(inventoryUpdateBuilder).Then(obj =>
{
Debug.Log($"AddCurrency() success.");
});
}
|
Removing Currency
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | public async void RemoveCurrency()
{
// Resolve Reference
var currencyContentPrimary = await _currencyRefPrimary.Resolve();
// List the operations
InventoryUpdateBuilder inventoryUpdateBuilder = new InventoryUpdateBuilder();
inventoryUpdateBuilder.CurrencyChange(currencyContentPrimary.Id, -1);
// Execute the operations
await _beamContext.Inventory.Update(inventoryUpdateBuilder).Then(obj =>
{
Debug.Log($"RemoveCurrency() success.");
});
}
|
Sample Code
This sample code will subscribe to changes in the available currencies, and the player's owned currencies. It also provides methods to add, remove, and trade between two currencies.
InventoryCurrencyExample.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202 | using System.Collections.Generic;
using System.Linq;
using Beamable.Common.Api.Inventory;
using Beamable.Common.Content;
using Beamable.Common.Inventory;
using Beamable.Examples.Shared;
using UnityEngine;
using UnityEngine.Events;
namespace Beamable.Examples.Services.InventoryService.InventoryCurrencyExample
{
[System.Serializable]
public class InventoryCurrencyExampleEvent : UnityEvent<InventoryCurrencyExampleData> { }
/// <summary>
/// Demonstrates <see cref="InventoryService"/>.
/// </summary>
public class InventoryCurrencyExample : MonoBehaviour
{
// Events ---------------------------------------
[HideInInspector]
public InventoryCurrencyExampleEvent OnRefreshed = new InventoryCurrencyExampleEvent();
// Fields ---------------------------------------
[SerializeField] private CurrencyRef _currencyRefPrimary = null;
[SerializeField] private CurrencyRef _currencyRefSecondary = null;
private BeamContext _beamContext;
private const int CurrencyDeltaPerClick = 1;
private const string ContentType = "currency";
private readonly InventoryCurrencyExampleData _inventoryCurrencyExampleData = new
InventoryCurrencyExampleData();
// Unity Methods --------------------------------
protected void Start()
{
Debug.Log($"Start()");
SetupBeamable();
}
// Methods --------------------------------------
private async void SetupBeamable()
{
_beamContext = await BeamContext.Default.Instance;
Debug.Log($"_beamContext.PlayerId = {_beamContext.PlayerId}");
_inventoryCurrencyExampleData.CurrencyContentPrimary =
await _currencyRefPrimary.Resolve();
_inventoryCurrencyExampleData.CurrencyContentSecondary =
await _currencyRefSecondary.Resolve();
// All currencies (Available in game)
_beamContext.Api.ContentService.Subscribe(ContentService_OnChanged);
// Filtered currencies (Owned by current player)
_beamContext.Api.InventoryService.Subscribe(ContentType, InventoryService_OnChanged);
}
public void Refresh()
{
string refreshLog = $"Refresh() ...\n" +
$"\n * ContentType = {ContentType}" +
$"\n * ContentCurrencyNames.Count =
{_inventoryCurrencyExampleData.ContentCurrencyNames.Count}" +
$"\n * InventoryCurrencyNames.Count =
{_inventoryCurrencyExampleData.InventoryCurrencyNames.Count}\n\n";
//Debug.Log(refreshLog);
OnRefreshed?.Invoke(_inventoryCurrencyExampleData);
}
public async void AddPrimaryCurrency()
{
InventoryUpdateBuilder inventoryUpdateBuilder = new InventoryUpdateBuilder();
inventoryUpdateBuilder.CurrencyChange(_inventoryCurrencyExampleData.CurrencyContentPrimary.Id,
CurrencyDeltaPerClick);
// Add
await _beamContext.Api.InventoryService.Update(inventoryUpdateBuilder).Then(obj =>
{
Debug.Log($"#1. PLAYER AddPrimaryCurrency2() success.");
});
}
public async void RemovePrimaryCurrency()
{
InventoryUpdateBuilder inventoryUpdateBuilder = new InventoryUpdateBuilder();
// Remove
inventoryUpdateBuilder.CurrencyChange(_inventoryCurrencyExampleData.CurrencyContentPrimary.Id,
-CurrencyDeltaPerClick);
await _beamContext.Api.InventoryService.Update(inventoryUpdateBuilder).Then(obj =>
{
Debug.Log($"#2. PLAYER RemovePrimaryCurrency() success.");
});
}
public async void TradePrimaryToSecondary()
{
InventoryUpdateBuilder inventoryUpdateBuilder = new InventoryUpdateBuilder();
// Remove Primary
inventoryUpdateBuilder.CurrencyChange(_inventoryCurrencyExampleData.CurrencyContentPrimary.Id,
-CurrencyDeltaPerClick);
// Add Secondary
inventoryUpdateBuilder.CurrencyChange(_inventoryCurrencyExampleData.CurrencyContentSecondary.Id,
CurrencyDeltaPerClick);
await _beamContext.Api.InventoryService.Update(inventoryUpdateBuilder).Then(obj =>
{
Debug.Log($"#3. PLAYER TradePrimaryToSecondary() success.");
});
}
public async void TradeSecondaryToPrimary()
{
InventoryUpdateBuilder inventoryUpdateBuilder = new InventoryUpdateBuilder();
// Remove Secondary
inventoryUpdateBuilder.CurrencyChange(_inventoryCurrencyExampleData.CurrencyContentSecondary.Id,
-CurrencyDeltaPerClick);
// Add Primary
inventoryUpdateBuilder.CurrencyChange(_inventoryCurrencyExampleData.CurrencyContentPrimary.Id,
CurrencyDeltaPerClick);
await _beamContext.Api.InventoryService.Update(inventoryUpdateBuilder).Then(obj =>
{
Debug.Log($"#4. PLAYER TradeSecondaryToPrimary() success.");
});
}
// Event Handlers -------------------------------
private void ContentService_OnChanged(ClientManifest clientManifest)
{
_inventoryCurrencyExampleData.IsChangedContentService = true;
// Filter to ONLY CurrencyContent
List<ClientContentInfo> clientContentInfos = clientManifest.entries.Where((clientContentInfo, i)
=>
ExampleProjectHelper.IsMatchingClientContentInfo(clientContentInfo, ContentType)).ToList();
Debug.Log($"GAME - ContentService_OnChanged, " +
$"currencies.Count = {clientContentInfos.Count}");
_inventoryCurrencyExampleData.ContentCurrencyNames.Clear();
foreach (ClientContentInfo clientContentInfo in clientContentInfos)
{
string currencyName =
ExampleProjectHelper.GetDisplayNameFromContentId(clientContentInfo.contentId);
string currencyNameFormatted = $"{currencyName}";
_inventoryCurrencyExampleData.ContentCurrencyNames.Add(currencyNameFormatted);
}
// Alphabetize
_inventoryCurrencyExampleData.ContentCurrencyNames.Sort();
Refresh();
}
private void InventoryService_OnChanged(InventoryView inventoryView)
{
_inventoryCurrencyExampleData.IsChangedInventoryService = true;
Debug.Log($"PLAYER - InventoryService_OnChanged, " +
$"currencies.Count = {inventoryView.currencies.Count}");
_inventoryCurrencyExampleData.InventoryCurrencyNames.Clear();
foreach (KeyValuePair<string, long> kvp in inventoryView.currencies)
{
string currencyName = ExampleProjectHelper.GetDisplayNameFromContentId(kvp.Key);
string currencyNameFormatted = $"{currencyName} x {kvp.Value}";
_inventoryCurrencyExampleData.InventoryCurrencyNames.Add(currencyNameFormatted);
}
// Alphabetize
_inventoryCurrencyExampleData.InventoryCurrencyNames.Sort();
Refresh();
}
}
}
|