AccelByte Blog: Insights on Game Development & Backend

How to Build a Custom Loot Box for AccelByte Gaming Services

Written by Toby Fang | Mar 28, 2023 6:06:13 PM

Our latest product update in Monetization has some very exciting new features that we can’t wait to share with you: loot box functionality which can also be customized using our gRPC framework. Loot Box is introduced as a new item type and can be created and configured in our Store. 

As usual, the customization capabilities will enable you to customize your loot box rewarding rules, so that you can design your own special rewarding mechanisms to attract more players’ interests. Read on to learn more.

What is AccelByte’s Gaming Loot Box?

AccelByte’s Loot Box is a new item type in our Store. Each loot box can contain a list of other items which each have their own probability of appearing when a loot box is opened. This can be used to grant players entitlements of one or more rewards from a predefined pool of rewards based on probabilities you set.

How it Works: Create and Configure a Custom Loot Box

When you create a loot box, you will need to choose a list of items to create a pool of items so that when players open a loot box, they will be rewarded based on a random draw from that pool.

You can add items one by one and assign a probability to each individually, or you may also choose to add a group of items together and assign a probability to that entire group. 

Creating an ‘Item Group’ is especially useful when you think a group of items have a similar rarity and thus each individual item within the group should share the same probability. For example, you can put items into groups like Common, Rare, and Ultra Rare, and assign each group a probability value. If 80% probability is assigned to Common group, that means there is an 80% chance that items from the Common group will be rewarded to a player when a loot box is opened.

Customization of Loot Box: Custom Roll Function

If you want more than our default rewarding mechanism by simply setting probabilities, you may also use our ‘Custom Roll Function’ to start your customizations using our gRPC service. When the function is enabled, AccelByte will call the gRPC server to determine which rewards to be granted when players open loot boxes. 

Here are just a few of the potential customization examples for your reference:

  1. After opening 10 loot boxes, a player will be guaranteed an item from the Ultra Rare group.
  2. For each player and each loot box, he/she will never be rewarded the same item.
  3. Players at different levels may have different odds for items in the loot box reward pool.

To achieve that, developers will only need to override a couple of gRPC methods in any language of their choice.

service LootBox {
 rpc RollLootBoxRewards(RollLootBoxRewardsRequest) returns (RollLootBoxRewardsResponse);
}

The following is an example Java implementation that illustrates how players are guaranteed with an Ultra Rare Item after opening 10 loot boxes (sample code only). 

@Slf4j
@GRpcService
public class LootboxServiceImplementation extends LootBoxGrpc.LootBoxImplBase {
   // For reference only, please use persistent storage to store this record in real world application
   private final Map<String, Integer> userRollCounts = new HashMap<>();


   @Override
   public void rollLootBoxRewards(RollLootBoxRewardsRequest request, StreamObserver<RollLootBoxRewardsResponse> responseObserver) {
   log.info("Received rollLootBoxRewards request");
   String userId = request.getUserId();
   LootBoxItemInfo itemInfo = request.getItemInfo();
   int numOfRewardsPerBox = itemInfo.getRewardCount();
   int rollsRequested = request.getQuantity();
  
   int totalWeight = 0;
   List<LootBoxRewardWeight> lootBoxRewardWeightList = new ArrayList<>();
   // accumulate rolling weight
   for (LootBoxRewardObject lootBoxReward : itemInfo.getLootBoxRewardsList()) {
       totalWeight += lootBoxReward.getWeight();
       lootBoxRewardWeightList.add(new LootBoxRewardWeight(totalWeight, lootBoxReward.getItemsList(), LootBoxRewardType.valueOf(lootBoxReward.getType())));
   }


   List<RewardObject> rewards = new ArrayList<>();
   int userRollCount = userRollCounts.getOrDefault(userId, 0);
   // finding an Ultra Rare reward based on rewards names
   LootBoxRewardObject ultraRareReward = itemInfo.getLootBoxRewardsList().stream().filter(lootBoxReward-> {lootboxReward.getName().equals("UltraRare")}).findFirst().orElseThrow(() -> new InternalException("Not Found"));


   // roll rewards
   for (int rollCount = 0; rollCount < rollsRequested; rollCount++) {
       if (userRollCount > 0 && userRollCount % 10 == 0) {
           // ultra rare item guaranteed
           rewards.add(randomUltraRareItem(ultraRareReward));
       } else {
           // roll based on probability group or return all rewards
           for (int rewardCount = 0; rewardCount < numOfRewardsPerBox; rewardCount++) {
               List<RewardObject> entitlementLootBoxRewards = rollLootBoxReward(totalWeight, lootBoxRewardWeightList).stream().map(this::toRewardObject).collect(Collectors.toList());
               rewards.addAll(entitlementLootBoxRewards);
           }
       }
       ++userRollCount;
   }
   userRollCounts.put(userId, userRollCount);


   RollLootBoxRewardsResponse response = RollLootBoxRewardsResponse
           .newBuilder()
           .addAllRewards(rewards)
           .build();


   responseObserver.onNext(response);
   responseObserver.onCompleted();
}




   private List<BoxItemObject> rollLootBoxReward(int totalWeight, List<LootBoxRewardWeight> lootBoxRewardWeights) {
       int num = new Random().nextInt(totalWeight) + 1; // random a number from [1, totalWeight]
       for (LootBoxRewardWeight lootBoxRewardWeight : lootBoxRewardWeights) {
           if (num > lootBoxRewardWeight.finalWight) {
               continue;
           }


           List<BoxItemObject> itemsList = lootBoxRewardWeight.lootBoxItems;
           if (LootBoxRewardType.PROBABILITY_GROUP.equals(lootBoxRewardWeight.type)) {
               int randomNum = new Random().nextInt(itemsList.size()); // random a number from [0, reward count)
               return Collections.singletonList(itemsList.get(randomNum));
           } else {
               return itemsList;
           }
       }


       throw new Error();
   }


   private RewardObject randomUltraRareItem(LootBoxRewardObject lootBoxRewardObject) {
       List<BoxItemObject> ultraRareItemList = lootBoxRewardObject.getItemsList();
       int randomNum = new Random().nextInt(ultraRareItemList.size());
       BoxItemObject randomUltraRareBoxItem = ultraRareItemList.get(randomNum);


       return RewardObject.newBuilder()
               .setCount(randomUltraRareBoxItem.getCount())
               .setItemId(randomUltraRareBoxItem.getItemId())
               .setItemSku(randomUltraRareBoxItem.getItemSku()).build();
   }


   private RewardObject toRewardObject(BoxItemObject boxItemObject) {
       return RewardObject.newBuilder()
               .setCount(boxItemObject.getCount())
               .setItemId(boxItemObject.getItemId())
               .setItemSku(boxItemObject.getItemSku()).build();
   }
}

Now you have one more method to leverage a direct relationship with your players by customizing how they interact with your in-game stores! With AccelByte, developers can own relationships throughout the player experience. Our commerce collection of services serves as the backbone for a publishing platform or in-game store, from product catalogs, purchases, and fulfillment to digital ownership. 

Interested in learning more about our solutions? Request a demo here.