Sunday, April 19, 2015

Loot Tables Code - Heroes' Journey

Hello all!

This is a continuation of my previous post, here, where I went over the design side of my Loot Tables. This post will go over the actual code I am using to implement the logic from the last post.

Code                                                                                   

The first thing we need are our variables: our List of entries, an int to store the total of all the weights, a List to hold the weight converted to percentages, and a List to hold the cumulative percentages that we check against.

private List<LootTableEntry> lootEntries = new List<LootTableEntry>(); //The sum of all of the entries' weights private int totalWeights = 0; //Item weights as overall percentages private List<int> weightPercentages = new List<int>(); //The cumulative percentage table to check against private List<int> cumulativePercentages = new List<int>();
I also made a method that makes it easier to add an entry,  I use this in child classes' constructors to build the Entry list.

protected void AddNewEntry(int probability, params BaseItem[] items) { lootEntries.Add(new LootTableEntry(probability, new List<BaseItem>(items))); }
Here is my LootTableEntry class for reference.

using UnityEngine; using System.Collections; using System.Collections.Generic; public class LootTableEntry { public List<BaseItem> Items = new List<BaseItem>(); public int Weight = 0; public LootTableEntry(int weight, List<BaseItem> items) { Weight = weight; Items = items; } }
After I add all of the entries I call a method that initializes the Loot Table (which fills out the other variables we declared earlier)

protected void InitTable() { GetTotalWeights(); GetWeightPercentages(); GetCumulativePercentages(); }
The first method called by InitTable is GetTotalWeights. This just iterates through all of the entries add gets a sum of all their weights.

private void GetTotalWeights() { totalWeights = 0; //Add all of the items' Weights together foreach (LootTableEntry entry in lootEntries) { totalWeights += entry.Weight; } }
The second method is GetWeightPercentages. This iterates through all of the entries again and divides their weights by the total to get their actual percentages.

private void GetWeightPercentages() { weightPercentages.Clear(); for (int i = 0; i < lootEntries.Count; i++ ) { //Get the probability of the item based upon the total probability int percent = Mathf.RoundToInt(((float)lootEntries[i].Weight / (float)totalWeights) * 100); weightPercentages.Add(percent); } }
The last method InitTable calls is the GetCumulativePercentages. This is where I build the list to check against. The first thing I do is add a 0 to the list as the first element, this will give me the lower bounds for checking the first range. Next, I go through each element in the weightPercentages List. On each element add it to the running total and add the new total to the cumulatvePercentages List. The final element should bring the total up to 100.

private void GetCumulativePercentages() { cumulativePercentages.Clear(); int cumulativePercent = 0; cumulativePercentages.Add(cumulativePercent); for (int i = 0; i < weightPercentages.Count; i++) { //Add the new item's probability cumulativePercent += weightPercentages[i]; //add the new percent as the key, and the index of the associated entry as the value cumulativePercentages.Add(cumulativePercent); } }
Here is an example LootTable that I based the logic examples off of.

using UnityEngine; using System.Collections; public class LT_Test : BaseLootTable { public LT_Test() { AddNewEntry(5, new AT_SimpleClothJerkin(), new AHD_SimpleLeatherCap()); AddNewEntry(15, new WM_SimpleLongSword()); AddNewEntry(60, null); InitTable(); } }
After a battle I need to get which Loot Entry the player will receive. I have a method called GetLoot that picks a random number and checks the ranges in the cumulativePercentages list. Then it returns the Entry in the lootEntries List that has the 'found' index.

public List<BaseItem> GetLoot() { int check = Random.Range(1, 101); for (int i = 0; i < cumulativePercentages.Count - 1; i++) { //Check if the random number we generated is between this set of values if (check > cumulativePercentages[i] && check <= cumulativePercentages[i + 1]) { return lootEntries[i].Items; } } return new List<BaseItem>(); }
And here is the full BaseLootTable class

using UnityEngine; using System.Collections; using System.Collections.Generic; public class BaseLootTable { private List<LootTableEntry> lootEntries = new List<LootTableEntry>(); //The sum of all of the entries' weights private int totalWeights = 0; //Item weights as overall percentages private List<int> weightPercentages = new List<int>(); //The cumulative percentage table to check against private List<int> cumulativePercentages = new List<int>(); protected void InitTable() { GetTotalWeights(); GetWeightPercentages(); GetCumulativePercentages(); } public List<BaseItem> GetLoot() { int check = Random.Range(1, 101); for (int i = 0; i < cumulativePercentages.Count - 1; i++) { //Check if the random number we generated is between this set of values if (check > cumulativePercentages[i] && check <= cumulativePercentages[i + 1]) { return lootEntries[i].Items; } } return new List<BaseItem>(); } private void GetTotalWeights() { totalWeights = 0; //Add all of the items' Weights together foreach (LootTableEntry entry in lootEntries) { totalWeights += entry.Weight; } } private void GetWeightPercentages() { weightPercentages.Clear(); for (int i = 0; i < lootEntries.Count; i++ ) { //Get the probability of the item based upon the total probability int percent = Mathf.RoundToInt(((float)lootEntries[i].Weight / (float)totalWeights) * 100); weightPercentages.Add(percent); } } private void GetCumulativePercentages() { cumulativePercentages.Clear(); int cumulativePercent = 0; cumulativePercentages.Add(cumulativePercent); for (int i = 0; i < weightPercentages.Count; i++) { //Add the new item's probability cumulativePercent += weightPercentages[i]; //add the new percent as the key, and the index of the associated entry as the value cumulativePercentages.Add(cumulativePercent); } } protected void AddNewEntry(int probability, params BaseItem[] items) { lootEntries.Add(new LootTableEntry(probability, new List<BaseItem>(items))); } }
That is how I implemented my Loot Tables, I'm sure there are better ways of doing it, but this works fine for this game. Anyway, I hope this helps someone develop a loot system for their game.

Loot Tables Overview - Heroes' Journey

Hello everyone!

In this post I wanted to go over how I designed the loot tables in Heroes' Journey. This was one area where I was having a lot of trouble coming up with a good way to implement random loot at the end of battle. So, I turned to the Internet in hopes of finding examples of how other games/people had approached this before. I found a lot of good examples of loot systems, like the ones here at Gamasutra, the Game Development Stack Exchange, and several others. I really liked the table style implementation, like the main example in the Gamasutra article, and decided to go with a variation of that one. I'll go through the logic first, then show a code example of how I implemented that logic.

I was going to do this as one long post, but it being a little too long. Instead I'll do an overview of the logic behind my Loot Tables, and in the next post go over how I actually implemented it in code. This can be found here.

Logic                                                                                  

The first thing that is needed is a list that holds the entries to our loot table. My entries consist of a probability (weight) and an item collection (this allows me to drop sets of items). For this example loot table we will have the following entries:


If you noticed the Weights do not add up to 100. As the linked tutorials explain, these numbers are more to show how probable that entry is to be dropped compared to the other entries in the list. We'll get the 0%-100% percentages next. To do this all we have to do is get the sum of all of the weights, then divide each of the weights by the total. Next multiply by 100, and round it to get a whole number percentage.


This means we have a 6% chance to get the Simple Cloth Jerkin/Leather Cap set, a 19% chance to get a Simple Long Sword, and a 75% chance to get nothing. So we have the different probabilities of each of our entries, now we need to put them into a table that we can check our random number against. To do this you start at 0, and for each entry just add that percent to the running total. The last one should always end up at 100.


This table gives us an upper and lower bound to check our random number against. Say we got a random number of 17. So we have 0 - 6 is the armor set, 7 - 25 is the long sword, and 26 - 100 is nothing. We can see that the random number would fall in the 7 - 25 range, therefore we would get the Simple Long Sword as our drop.


This way also allows you to order the entries which ever way you'd like. If you wanted the rarer items to be at the lower end of the 0 - 100 range then you just add them to the list first. In this case, since the lower the number the rarer the drop, anything you take off the random number will increase your chance of getting a rarer item.

That is the logic I am using for determining which drop the player will get at the end of the battle.

Saturday, April 18, 2015

Update on Heroes' Journey

Hello everyone!

I wanted to give you an update on what has happened on Heroes' Journey since my last post. In my last post I went over the Characteristics (Abilities/Attributes/Stats) that I am using in the game and I only had the basic damage calculations done. I have made a lot more progress since then. Here are just a couple brief descriptions of what has been implemented:

  • Battle now supports using skills (which apply Buffs/Debuffs), as well as basic damage and skipping your turn. 
  • Skills now use Action Points to use, and you regenerate AP at the beginning of each turn
  • Damage calculations now take into account modifiers from armor, weapons, buffs, and debuffs
  • Attacks/Skills now support the ability to target different groups, like Self, Friend, or Foe.
  • If you are victorious you now receive gold, XP, and loot based upon the enemy types that were defeated. 
  • All of the base classes created for Enemies, all Item types, Skills, Buffs, and Debuffs
I have a little more work too for dispersing gold and loot, since when I initially wrote it I didn't take into account that the player party will be pooling gold and loot to make it less of a hassle to manage your items. That should be a pretty quick fix, then I can start creating enemies, items, skills, and so on so I can actually start testing the system under different circumstances.

I wanted to go over how I am handling the loot tables, but I plan on doing that as a separate post, since I have a feeling it will be a little lengthy.

Another thing I wanted to throw out there is that I started using Microsoft OneNote since my last post that has helped a lot for organization and motivation. It's free, and I recommend giving it a shot. 

The program is comprised of Notebooks, Sections, and Pages. Notebooks hold Sections and Sections hold Pages. This is how I set it up:

Notebook: Project/Game Name
Sections: Major components of the game. For instance, Mechanics, Story, Work Log, Project Timeline, and so on.
Pages: A breakdown of the above subcategories, This is where all of the actual details are written down.

I started doing my daily work logs in OneNote and it has helped a lot for keeping track of what I did on what day, and what persistent issues I need to tackle.

For instance, if I have two items on my To-Do list on one day and get one done it would look like this:



The next day I work on the game I copy over the tasks that are not complete, and add any new items I run in to as well:



This allows me to have a rolling log of tasks that need to be completed, and if need be I can check what changed between one day and the next and figure out what all I did on a given day.

Another nice feature is you can share it across multiple devices/users so it makes it really easy to collaborate with team members, or takes notes from your phone when you get an idea.

Anyway, catch ya in the next post!