Summary
In one of my courses at UQAT, we were tasked with conducting research on a topic of our choice in a team of 5. Since everyone had different preferences, we combined all our ideas to create something. Our topic focused on microtransactions and loot boxes, and we developed a prototype about it. I personally took care of the development of the microtransaction prototype, while my teammates worked on other parts of the project: a card game, animations, avatars, and research about economics.
You can read for yourself the 100-page document in french, but since it was primarily a research paper, we didn’t really focus on polishing the prototype. My part is the third section that starts at the page 25 (“L’intégration d’un système de microtransaction dans un jeu vidéo“). With that said, I have not made the prototype downloadable, but explanations and images are available below.
The shop
Since the shop doesn’t have any major complexity in its development, I’ll start here. I wanted to simulate buying credits from a store like Fortnite or Overwatch. For the payment part, it obviously doesn’t really work, but I have managed a prototype that acted like we were filling in the blanks. By clicking the blanks, it automatically fills up, then you can “accept” the transaction to add your credit. Click the images to expand them.



The Loot Boxes
We have two types of loot boxes in our game: one containing only cosmetics, purchasable with Credits, and another containing everything, available for purchase with Credits and Gold. The implementation of loot boxes occurred in several steps, including the creation of the interface, loot generation, random selection and precise data integration.


In the menu, players access the “Loot box” tab with two options: one at 300 Credits for cosmetics and another at 175 Credits (or 100 Gold) for cards and cosmetics. The distinction lies in the type of guaranteed rewards. The 300 Credits loot box ensures cosmetics, while the 175 Credits (or 100 Gold) one primarily offers cards, with a rare chance of obtaining cosmetics. This variety provides players with choices based on their gameplay preferences.
Weight system
We created Scriptable Objects in Unity representing our loot, with weights affecting the probability of obtaining each item. The unboxing formula follows a step-by-step process, determining rarity, selecting corresponding cards, and then drawing one at random. The weights balance the system to ensure a fair and satisfying experience for the player. My colleague, Anthony, was our economics designer who calculated the weight of each item.
Here, you can see in order the steps that we took to add and drop loot from our table.






The Loot Box code itself
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class LootBox : MonoBehaviour
{
[Header("Numbers")]
[SerializeField] int maxWeight = 101;
[ReadOnly] [SerializeField] int randomNumber;
[Header("Cards in the box")]
public List<Cartes> CartesList;
[ReadOnly] [SerializeField] List<Cartes> allPossibleItems;
[ReadOnly] [SerializeField] List<Cartes> rarestPossibleItems;
[ReadOnly] [SerializeField] Cartes droppedItem;
public void OpenLootBox()
{
InstantiateCartes(new Vector2(0, 0));
}
// -----------------------------------------------------------------------------------------------------------------------------
// InstantiateCartes (It is in this function that the card should be added to the player)
// -----------------------------------------------------------------------------------------------------------------------------
public void InstantiateCartes(Vector2 spawnPosition)
{
Cartes droppedItem = getDroppedItem();
if (droppedItem != null)
{
Debug.Log("add le Cartes ici \n" + droppedItem);
// add card to player here <-----------------
}
}
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Code that does the random draw in the Cartesbox
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Cartes getDroppedItem()
{
randomNumber = Random.Range(1, maxWeight); // 1-100 PICK RANDOM WEIGHT
allPossibleItems = new List<Cartes>(); // List of all items that could be dropped according to the randomNumber
rarestPossibleItems = new List<Cartes>(); // List of the rarest items that could be dropped according to allPossibleItems
// -----------------------------------------------------------------------------------------------------------------------------
// ADD allPossibleItems ITEM FROM RANDOM
// -----------------------------------------------------------------------------------------------------------------------------
foreach (Cartes items in CartesList)
{
if (randomNumber <= items.weight)
{
allPossibleItems.Add(items);
//return droppedItem; // -------- allows you to drop several items at the same time
}
}
// -----------------------------------------------------------------------------------------------------------------------------
// IF THERE IS RARITY IN THE LIST allPossibleItems, ADD IT IN THE LIST rarestPossibleItems, ELSE IF ...
// -----------------------------------------------------------------------------------------------------------------------------
if (allPossibleItems.FindAll(items => items.rarete.Contains("UltraRare")).Count > 0)
{
//Debug.Log(allPossibleItems.FindAll(items => items.rarete.Contains("UlraRare")).Count + " UlraRare");
foreach (Cartes items in allPossibleItems)
{
if (items.rarete == "UltraRare")
{
rarestPossibleItems.Add(items);
}
}
}
else if (allPossibleItems.FindAll(items => items.rarete.Contains("Legendary")).Count > 0)
{
//Debug.Log(allPossibleItems.FindAll(items => items.rarete.Contains("Legendary")).Count + " Legendary");
foreach (Cartes items in allPossibleItems)
{
if (items.rarete == "Legendary")
{
rarestPossibleItems.Add(items);
}
}
}
else if (allPossibleItems.FindAll(items => items.rarete.Contains("Epic")).Count > 0)
{
//Debug.Log(allPossibleItems.FindAll(items => items.rarete.Contains("Epic")).Count + " Epic");
foreach (Cartes items in allPossibleItems)
{
if (items.rarete == "Epic")
{
rarestPossibleItems.Add(items);
}
}
}
else if (allPossibleItems.FindAll(items => items.rarete.Contains("Rare")).Count > 0)
{
//Debug.Log(allPossibleItems.FindAll(items => items.rarete.Contains("Rare")).Count + " Rare");
foreach (Cartes items in allPossibleItems)
{
if (items.rarete == "Rare")
{
rarestPossibleItems.Add(items);
}
}
}
else if (allPossibleItems.FindAll(items => items.rarete.Contains("Uncommon")).Count > 0)
{
//Debug.Log(allPossibleItems.FindAll(items => items.rarete.Contains("Uncommon")).Count + " Uncommon");
foreach (Cartes items in allPossibleItems)
{
if (items.rarete == "Uncommon")
{
rarestPossibleItems.Add(items);
}
}
}
else if (allPossibleItems.FindAll(items => items.rarete.Contains("Common")).Count > 0)
{
//Debug.Log(allPossibleItems.FindAll(items => items.rarete.Contains("Common")).Count + " Common");
foreach (Cartes items in allPossibleItems)
{
if (items.rarete == "Common")
{
rarestPossibleItems.Add(items);
}
}
}
// -----------------------------------------------------------------------------------------------------------------------------
// TAKE ONE OF THE ITEMS IN THE rarestPossibleItems LIST AND DROP IT
// -----------------------------------------------------------------------------------------------------------------------------
if (rarestPossibleItems.Count > 0)
{
droppedItem = rarestPossibleItems[Random.Range(0, rarestPossibleItems.Count)];
return droppedItem;
}
Debug.Log("No Cartes dropped");
return null;
}
}