Game Foundation tutorials
Creating a Game Item Definition
- Tip: A Game Item Definition is any type of data that represents a gameplay concept you want Game Foundation to manage. While Game Foundation already provides built-in concepts like Inventory Item, for any generic concept in your own custom logic (such as a Player Character), defining them as Game Items allows you to take advantage of Game Foundation capabilities. Furthermore, when you want the same concepts to be shared by multiple systems, you can define them as Game Items and then use ‘Reference Definition’ to inherit that definition in other systems.
You can define a “coin” in the Game Item catalog and have your Store and Wallet systems refer to that Game Item Definition when they want to know what a coin is.
Open the Game Item window (menu: Window → Game Foundation → Game Item). This is a tool for viewing and managing the contents of your Game Item catalog.
Click the + button to create a new Game Item and then enter a Display Name and an Id. The display name can be modified later, but the ID will be permanent.
- Tip: String IDs are the easiest way to access object instances managed by Game Foundation (the alternative approach is hash number). We recognize that using string ID may not be the best solution in some cases. We’ll provide an alternative solution in a future release.
Click the Create button. When a Game Item is created and added to the catalog, the Detail Definitions configuration options become available for that Game Item. A Detail in Game Foundation is a little bit of data added to a Game Item to describe it further, and details are often pertinent to specific systems.
Using the Wallet with Soft Currency
Creating a soft currency
- Tip: Soft Currency, also called regular currency or free currency, is a resource designed to be adequately accessible through normal gameplay, without having to make micro-transactions. We can keep track of these soft currencies with a virtual “wallet”.
Open the Inventory window (menu: Window → Game Foundation → Inventory). This is a tool for viewing and managing the contents of your Inventory catalog.
Click the + button to create a new inventory item and then enter a Display Name and an Id. The display name can be modified later, but the ID will be permanent.
Click the Create button. When an item is created and added to the catalog, the Categories and Detail Definitions configuration options become available for that item.
A currency item must contain a currency detail. Click the Add Details button and then select Currency Detail. The Currency Detail allow you to select the type of currency you want to use, but for this tutorial it can stay as the default value, Soft.
Adding a Currency Detail also automatically adds an Analytics Detail.
Currency items can be added to the Wallet at runtime. You can display currency items automatically in the player’s wallet at the start of the game by making the currency item a Default Item in the Wallet inventory. Click on the Inventories tab and select Wallet from the list of inventory items (the Main and Wallet inventories are built-in to Game Foundation and can’t be deleted; you can create additional custom inventories as needed).
Each inventory contains a list of Default Items which are automatically added to that inventory when it is first instantiated at runtime. In your Wallet, look under Other Available Items to find the Coin item, and click the Add To Default Items button next to it. This moves Coin to the Default Items list.
You can now set the quantity of the item to be added to the wallet when it is first created. In this case, the player will start with 100 coins in their wallet.
Spending currency in-game
Tip: You can also use the Transaction System to manage currency exchanges.
After creating your Coin and adding it to the Wallet in the previous tutorial sections, add the following script to a GameObject in a Scene:
using UnityEngine; using UnityEngine.GameFoundation; public class WalletTest : MonoBehaviour { void Awake() { // this data layer will not save any data, it is usually used for examples or tests IDataAccessLayer dataLayer = new MemoryDataLayer(GameFoundationSerializableData.Empty); // you always need to call Initialize once per session GameFoundation.Initialize(dataLayer); } void Start() { // get the coin item by its ID InventoryItem coinItem = Wallet.GetItem("coin"); Debug.LogFormat("coins in wallet at start: {0}", coinItem.quantity); coinItem.quantity -= 25; Debug.LogFormat("coins in wallet: {0}", coinItem.quantity); } }
Click Play. In the console, you’ll see the player’s wallet starting out with 100 coins in it. 25 coins are then removed, leaving them with 75 coins.
Creating and applying Stats
- Tip: In Game Foundation you can use Stats to track and manage any numeric values in your gameplay, such as the health of a character, damage point of a weapon, or how many times the player has beat the level. Extending the stat system will allow you to apply formulas to stats based on modifiers and player progression.
Open the Stat window (menu: Window → Game Foundation → Stat). This is a tool for viewing and managing the stats you want to adjust and persist at runtime.
For this example, we’ll use a sword with a Damage stat. Create the Damage stat by clicking the + button to create a new stat item with the following values:
- Display Name: Damage
- Id: damage
- Value Type: Int
Click Create.
- Tip: the numeric types currently supported are Float (C# System.Single) and Int (C# System.Int32). Once you choose which numeric type a stat is, it cannot be changed later.
Following the steps from the Creating a Game Item Definition tutorial, create a Sword GameItem.
Click Add Details to add Stat Details to the Sword.
In the Stat Details section, you’ll see a popup menu with your only stat, Damage, already selected. Click Add to add the Damage stat to your Sword, and then set the Default Value as 10.
Using what you've learned from the tutorials so far, do the following:
- Create a “Damage Increase” stat with a Value Type of Int.
- Create a “Damage Buff Spell” Game Item.
- Give the spell a “Damage Increase” stat with a default value of 1.
- Create a “Sword” Inventory Item with the Reference Definition set to Sword, and add it as a default item in the Main inventory.
Create a “Damage Buff Spell” Inventory Item with the Reference Definition set to Damage Buff Spell and also add it to the Main inventory’s default items.
Working with Stats at runtime
Now that we have a sword and a spell, let’s use those while playing the game. We’ll access the sword and spell in the inventory, and consume the spell in order to improve the damage stat of the sword:
// let's use a consumable scroll spell to buff our weapon
void Awake()
{
// this data layer will not save any data, it is usually used for examples or tests
IDataAccessLayer dataLayer = new MemoryDataLayer(GameFoundationSerializableData.Empty);
// you always need to call Initialize once per session
GameFoundation.Initialize(dataLayer);
}
void SharpenSword()
{
// get the sword from inventory
InventoryItem swordItem = Inventory.main.GetItem("sword");
// get the scroll spell from inventory
InventoryItem scrollItem = Inventory.main.GetItem("damageBuffSpell");
// get the sword’s current damage
int swordDamage = StatManager.GetIntValue(swordItem, "damage");
// find out how much the scroll spell will increase the damage
int damageIncrease = StatManager.GetIntValue(scrollItem, "damageIncrease");
// increase this sword’s damage permanently
StatManager.SetIntValue(swordItem, "damage", swordDamage + damageIncrease);
// consume the scroll spell by removing it from the inventory
Inventory.main.RemoveItem("damageBuffSpell");
}
Saving and loading the Game State
Now that you’re playing the game and your wallet values have changed, you’ll want to save your progress to make sure these changes persist when you return to the game. When you initialize the inventory manager (as you did in the previous tutorial), you should also pass in a persistence provider. That will automatically load the state that was last saved. When you’re ready to save or load the game state, you can use methods like these:
using UnityEngine;
using UnityEngine.GameFoundation;
public class SaveGame : MonoBehaviour
{
PersistenceDataLayer dataLayer;
void Awake()
{
// choose what format you want to use
JsonDataSerializer dataSerializer = new JsonDataSerializer();
// choose where to store the data
IDataPersistence localPersistence = new LocalPersistence("GFTutorial", dataSerializer);
// create a data access layer for Game Foundation and keep a reference to it
// to save your progression whenever you want
dataLayer = new PersistenceDataLayer(localPersistence);
// tell Game Foundation to initialize using the created data access layer
GameFoundation.Initialize(dataLayer);
}
public void Save()
{
// save Game Foundation's data
dataLayer.Save();
}
public void Load()
{
// to load a fresh set of data for Game Foundation you need to: unitialize it ...
GameFoundation.Uninitialize();
// ... and re-initialize it with the desired data access layer
GameFoundation.Initialize(dataLayer);
}
}
Asynchronous operations and promises
A Promise
is a lightweight object to easily track the completion, success or failure, of an asynchronous operation and its resulting value, if any.
To create, manage and recycle them you need to use a PromiseGenerator
.
When generating a Promise
, it will give you two objects:
- a
Completer
: use it in your asynchronous process to complete or reject thePromise
. - a
Deferred
: expose it to allow other systems, like your UI, to check yourPromise
state and wait for its completion as they please (polling, coroutine, ...). Note that you can use it toRelease
aPromise
to recycle it.
Here is the Save
function of the PersistenceDataLayer
as a use case on the process side:
public Deferred Save()
{
// ... Save specific logic here ...
// Creating the promise and its handles
m_PromiseGenerator.GetPromiseHandles(out var deferred, out var completer);
// Start your asynchronous process and provide the Completer to allow it to complete your process (either it is a success or a failure).
persistence.Save(data, completer.Resolve, completer.Reject);
// Return the handle to allow developpers to properly wait for completion.
return deferred;
}
And here is a code sample on the caller side using coroutine:
IEnumerator SaveGame()
{
// The datalayer assignation is ommitted since it isn't the focus here.
PersistenceDataLayer dataLayer = ...;
// Call your operation and keep a handle on it.
Deferred saveOperation = dataLayer.Save();
// Wait for your operation's completion.
yield return saveOperation.Wait();
// Release the promise to recycle it.
saveOperation.Release();
// Do your post save logic here ...
}
Defining Item details
- Tip: A Detail Definition is a predefined bit of data associated with a Game Item Definition. Details can be things like images, audio clips, metadata, etc. - anything that can be used to further identify an in-game item.
After doing the previous tutorials, you should be more comfortable with creating Game Items. Now let's create a new Game Item, "apple," and then assign some sprites to it. We'll use two sprites:
- A thumbnail, used when we want to show an apple in a UI list;
- An enlargement of the thumbnail, used when we want to show a full-screen description of the apple.
Create a Game Item Definition with the id "apple".
On that new Game Item add a Sprite Assets Detail.
In the Sprite Assets Detail, click the "+" button twice to add two sprite entries.
These should have default keys of "Sprite" and "Sprite (1)". Change the keys to "thumbnail" and "enlargement" instead. For both entries, select an appropriate Sprite using either the object picker or by dragging Sprites from the Project window.
Working with Detail Definitions at runtime
After following the steps in "Defining Item Details" above, the next thing you'd want to know is how to access that data in your game. Let's write some code to put the thumbnail sprite to use.
public Sprite GetAppleThumbnail()
{
GameItemDefinition appleDef =
CatalogManager.gameItemCatalog.GetGameItemDefinition("apple");
SpriteAssetsDetailDefinition appleSpriteDefs =
appleDef.GetDetailDefinition<SpriteAssetsDetailDefinition>();
// if the key doesn't exist, you'll get an error
if (!appleSpriteDefs.ContainsKey("thumbnail"))
{
Debug.Log("Apple definition is missing a thumbnail!");
return null;
}
return appleSpriteDefs.GetAsset("thumbnail");
}
Creating a custom Detail Definition
If none of the existing Detail Definition types work for a given Game Item, you can create a custom Detail Definition that inherits from BaseDetailDefinition.
To do this, you will need to create two Detail Definition classes: one for seeing the detail in the editor and one for using it in the runtime. Let's modify our sword's Detail Definition by adding a description detail with a custom detail class.
Create a script named DescriptionEditorDetailDefinition.cs in your Assets. Note there are two required methods in this script:
- The first returns a Display Name, which is what appears in the list of Details when you're editing your Game Items.
The second is for creating the runtime DescriptionDetailDefinition and is called automatically during GameFoundation initialization.
using UnityEngine.GameFoundation.CatalogManagement; public class DescriptionEditorDetailDefinition : BaseDetailDefinition { public override string DisplayName() { return "Description Detail"; } public override UnityEngine.GameFoundation.BaseDetailDefinition CreateRuntimeDefinition() { return new DescriptionDetailDefinition(); } }
Now we'll add a string field to store a description for each Game Item, and pass it to the constructor for the runtime DescriptionDetailDefinition. This allows your new Detail Definition type to appear in the list of available detail types in the Game Item editor.
using UnityEngine.GameFoundation.CatalogManagement; public class DescriptionEditorDetailDefinition : BaseDetailDefinition { public string description; public override string DisplayName() { return "Description Detail"; } public override UnityEngine.GameFoundation.BaseDetailDefinition CreateRuntimeDefinition() { return new DescriptionDetailDefinition(description); } }
Next, we'll create the runtime DescriptionDetailDefinition. It needs a constructor that accepts and sets all of the necessary fields. In this case, that is the string description. The runtime detail definition is what will be included in your sword's list of Detail Definitions during runtime.
using UnityEngine.GameFoundation; public class DescriptionDetailDefinition : BaseDetailDefinition { public string description; public DescriptionDetailDefinition(string newDescription) { description = newDescription; } }
After the Description Detail is added to a Game Item, you'll see new "Script" field in the description detail for that item. This field appears in all scripted classes.
Tip: You can hide the Script field with a custom editor. Create a DescriptionDetailDefinitionEditor.cs script that has the following code in it to remove the Script field from your description details:
using UnityEditor; using UnityEditor.GameFoundation; [CustomEditor(typeof(DescriptionDetailDefinition))] public class DescriptionDetailDefinitionEditor : BaseDetailDefinitionEditor { }
This particular script gives you a very simple version of Description Detail, but using this method to customize your editors can be very useful when adding complex information to Game Items.
Using the Transaction System
The Transaction System lets you convert one type of item into another: for example, game currency into power-ups.
The inputs and outputs for these transactions can range from a single item to combinations of different items with varying quantities.
You can predefine allowable transactions by attaching Purchasable Details to your game items. When a transaction is initiated, the Transaction System uses the Purchasable Detail's information to validate the selected input in the source inventory, and then grants the outputs to the destination inventory (or informs the player that they don't have enough items for the transaction).
Creating a Purchasable Detail
Transactions are defined within Game Foundation by attaching Purchasable Details to game items. Purchasable Details specify input/payment and output/payout requirements: players can pay with game items or currencies to receive different game items or to invoke callbacks.
Here, we'll use TransactionDetails to create a bundle that a player can purchase.
Create a new Game Item Definition (as you learned in the first tutorial, Creating a Game Item Definition). Call it "Ultimate Bundle".
In the new Game Item Definition window, click the Add Details button and then select Purchasable Detail. You can also select a game item from your Inventory window (menu: Window → Game Foundation → Inventory) and add the Purchasable Detail in the item window.
- In the Purchasable Detail, specify the default inventories that will be used to make payments and receive items.
- Add Payout Items: these are the items that will be sold in the Ultimate Bundle. Specify the item id and quantity of each item (For example: 1 rocket power-up, 2 bomb power-ups, and 500 game coins).
You also have the option to specify a target inventory for each item, but this is not required.
Add a key to represent the price point of this bundle. Store Items allow you to specify multiple payment options.
Add Payment Items to any price points you define, including quantity and id.
You also have the option to specify a source inventory for payment, but this is not required.
Handling a Transaction
Now that you've defined a transaction, you can add it to your game.
The TransactionManager class contains static helper methods to handle the logic for you. They can be invoked from anywhere and need just a bit of data to take care of your transactions.
- Handling a simple transaction
- Handling a transaction with inventory overrides
- Handling a transaction with success and failure callbacks
Handling a simple transaction
Get the PurchasableDetail for the transaction:
PurchasableDetailDefinition purchasableDetail = InventoryManager.catalog.GetItemDefinition("yourPurchasableGameItemId").GetDetailDefinition<PurchasableDetailDefinition>();
Query the Price and Payout from the PurchasableDetail and pass them to the transaction manager by invoking HandleTransaction.
This causes the input items to be deducted from the input inventory and the output items to be added to the output inventory. If the input inventory does not have enough items for the transaction, no action is taken on the inventories. Here, no input/output inventories are specified so they will default to main.2a. A receipt is generated which can be accessed via the success/fail callbacks that contains information about the transaction, including whether it succeeded or failed.
TransactionManager.HandleTransaction(purchasableDetail.GetPrice(), purchasableDetail.payout);
Handling a transaction with inventory overrides
Get the PurchasableDetail for the transaction:
PurchasableDetailDefinition purchasableDetail = InventoryManager.catalog.GetItemDefinition("yourPurchasableGameItemId").GetDetailDefinition<PurchasableDetailDefinition>();
Get references for the input and output inventories you want to use for the transaction:
Inventory inputInventory = InventoryManager.GetInventory("yourInputInventoryId"); Inventory outputInventory = InventoryManager.GetInventory("yourOutputInventoryId");
Query the Price and Payout from the PurchasableDetail and pass them to the transaction manager.
3a. When you pass specific inventories to the transaction manager, these will be used instead of the inventories specified in the Purchasable Detail.
3b. You can also pass a single inventory, either input or output. The other inventory type will then default to main.
TransactionManager.HandleTransaction(purchasableDetail.GetPrice(), purchasableDetail.payout, inputInventory, outputInventory);
Handling a transaction with success and failure callbacks
Set up your callback methods as shown below.
These methods take in a TransactionReceipt, which contains useful information about the transaction's details and whether it succeeded or failed.
HandleTransaction generates the transaction receipt and passes it to the callbacks when they are invoked. A useful way to use the callbacks is to have a receipt variable and to set it in the callbacks. Then, after invoking HandleTransaction, you can check MyReceipt for the transaction's information.public TransactionReceipt MyReceipt; public void SuccessCallback(TransactionReceipt receipt) { MyReceipt = receipt; Debug.Log("Success!"); } public void FailureCallback(TransactionReceipt receipt) { MyReceipt = receipt; Debug.Log("Failure..."); }
Get the PurchasableDetail for the transaction:
PurchasableDetailDefinition purchasableDetail = InventoryManager.catalog.GetItemDefinition("yourPurchasableGameItemId").GetDetailDefinition<PurchasableDetailDefinition>();
Query the Price and Payout from the PurchasableDetail and pass them to the transaction manager along with your callback methods.
When the callbacks are passed, a successful transaction invokes the SuccessCallback, and a failed transaction invokes the FailureCallback.TransactionReceipt receipt = TransactionManager.HandleTransaction(purchasableDetail, inputInventory, outputInventory, SuccessCallback, FailureCallback);
Tip: You can access specific parameters in methods with many optional parameters by specifying the name of the parameter, followed by a colon and then the value you want to pass in. The example in step 3 above can be written with specified parameters as follows:
TransactionReceipt receipt = TransactionManager.HandleTransaction(transactionDetail, onTransactionSuccess : SuccessCallback, onTransactionFail : FailureCallback);