Selecting Values

On this page, you'll learn how to select values from your Probability List of items. Whether you're a game designer, coder, or both, this guide will help you and your team decide how RNGNeeds returns your values.


Selecting Values

Whether you design your Probability Lists in the inspector, or set it's values, probabilities, and other properties in code, selecting values is a simple process.

Selecting values

public ProbabilityList<string> myProbabilityList;

List<string> results = myProbabilityList.PickValues();

This will initiate the selection process and return values based on the current setup of the list, which includes factors like pick counts, selection method, repeat prevention, and more.

Consider the following example: Consider a game where the success of a player's action is determined by a dice roll. In this scenario, the game designer has the freedom to define the dice roll mechanics directly in the inspector.

Dice Roll 2D6

They can set the number of items in the list, which represent the sides of the dice, and the desired Pick Count, which represents the number of dice rolled. This flexibility allows the designer to easily adjust the game's RNG elements, tailoring the gameplay experience to their vision.

Now let's look at the code:

Dice Rolls set up by the designer

public ProbabilityList<int> dice;

int sumOfRolls = dice.PickValues().Sum();

The code only cares about the sum of the rolls. This way, the designer may decide if the dice roll is a 1D6, 2D10, or anything they desire. For example, if the designer adds three items to the list, with values of 1, 2 and 3, and sets the Pick Count to 2, they will effectively create a 2D3 dice roll.

Alternatively, the coder may take control of the dice, overriding the setup of the list:

Dice Rolls set by the coder

public ProbabilityList<int> dice;

// This will pick only one value
int sumOfRolls = dice.PickValues(1).Sum();

// and is equivalent to this
int result = dice.PickValue();

// This will configure the list to randomly select 2 to 4 values
dice.LinkPickCounts = false;
dice.PickCountMin = 2;
dice.PickCountMax = 4;
int sumOfRolls = dice.PickValues().Sum();

// and is equivalent to this, without the need to configure pick counts manually
int sumOfRolls = dice.PickValues(2, 4).Sum();

Understanding Returns

One of the primary objectives of RNGNeeds from its inception was performance. To achieve this, we've designed the system to minimize GC Allocations wherever possible. As a result, some selection methods return a set of values as a reference to an internal list, rather than creating a new List.

Internally, after the random selection of values is completed, the results are first written to the List's Pick History. From the beginning of the selection process up to this point, the system only deals with indices. The actual result is produced by returning either the LastPickedValue or LastPickedValues property, depending on the method used. These properties fetch the results from the history of picked indices, mapping them to the actual values stored within the list.

In essence, all value selections pass through history.

Methods that return a single value

These methods produce a singular result:

  • T PickValue()
  • bool TryPickValue(out T value)
  • (T Value, int Index) PickValueWithIndex()
  • bool TryPickValueWithIndex(out T Value, out int Index)

They are straightforward - they return the T value directly via the LastPickedValue property immediately after the selection. The only exception arises when a selection fails due to picking a disabled item from the list, in which case these methods return the default value for the type. To ascertain the success of a pick, you can utilize the TryPick versions. Learn more about how disabled items function later on this page.

Methods that return a shared list of values

These methods return a reference to a list owned by the Probability List:

  • List<T> PickValues()
  • List<T> PickValues(int pickCount)
  • List<T> PickValues(int pickCountMin, int pickCountMax)

These methods refrain from creating a new List. Instead, they return a reference to a private List<T> owned by the Probability List.

However, since the list is shared between picks, and you're only receiving the reference, the subsequent pick will alter its contents. Consider the following example:

Results in a shared list

public ProbabilityList<int> myProbabilityList;

var firstResult = myProbabilityList.PickValues(3);
            
// firstResult now contains a set of picked values, let's say 3, 5, 7

Debug.Log(firstResult[0]);  // outputs '3'

var secondResult = myProbabilityList.PickValues(3);

// secondResult contains new values, let's say 1, 2, 5

// However, firstResult is still a reference to the same list as secondResult
// so the values 3, 5, 7 are overwritten

Debug.Log(firstResult[0]);  // outputs '1'

Methods that fill Lists

If you wish to retain the values in lists, you can employ the methods that, instead of returning a shared list, append the values to the list you provide.

  • bool PickValues(List<T> listToFill)
  • bool PickValues(List<T> listToFill, int pickCount)
  • bool PickValues(List<T> listToFill, int pickCountMin, int pickCountMax)

These methods operate similarly, but instead of returning the shared list, they add values to the list you provide. They don't clear the list and add values via listToFill.AddRange(LastPickedValues). This approach is handy if you need to select multiple sets of results and allows you to manage the list creation.

Adding results to your list

public ProbabilityList<int> myProbabilityList;

List<int> myResults = new List<int>();

myProbabilityList.PickValues(myResults, 3);     // will add three values to your list
myProbabilityList.PickValues(myResults, 2, 5);  // will add an additional two to five values

Disabled Items

Disabling items in the list is a convenient method for preventing certain values from being picked during the selection process. This can be useful if you want to disable a value but are not yet ready to remove it from the list, for example, during the design phase when testing various outcomes. However, there are a couple of important concepts to understand when working with disabled items.

Consider the following example: there are three items in the list, each having a 33.3% probability. One of them is disabled.

Dsabled Item Effect
  • When selecting 100 values from this list, the resulting pick count may be lower, because each time the selection picks a disabled item, it will be ignored. There will probably be around 66 items picked, however, the probability of the two enabled items would seem correct - about 33% each.

  • To respect the desired pick count, you can enable the Maintain Pick Count feature. This will cause the selection process to continue until the desired pick count is reached. The result will be 100 items, however, the probability of the two enabled items will be close to 50% each.

Selecting multiple values from a list with disabled items

public ProbabilityList<string> myProbabilityList;

myProbabilityList.AddItem("Enabled Item 1", .33f);
myProbabilityList.AddItem("Enabled Item 2", .33f);
myProbabilityList.AddItem("Disabled Item", .33f, enabled: false);
myProbabilityList.NormalizeProbabilities();

// This will return a List<string> with about 66 values
var results = myProbabilityList.PickValues(100);

// This will return a List<string> with 100 values
myProbabilityList.MaintainPickCountIfDisabled = true;
var results = myProbabilityList.PickValues(100);

Selecting multiple values from a list with disabled items is quite easy to maintain, because the resulting List<T> will only contain successfully selected values.

However, when using methods that return a single value, such as .PickValue(), the outcome might depend on the Type your ProbabilityList holds. This is because .PickValue() has to return some value. If a disabled item would be picked from a ProbabilityList of reference types, the result would be a null and you could determine if the pick was successful with a null-check.

However, if you are working with value types, the result of an unsuccessful pick would be the default value of your Type. In some cases, the value returned from an unsuccessful pick might be indistinguishable from a real one. For example, with value types such as string, boolean, or int, an empty string, a false, or a 0 would be returned, respectively.

If you need to determine the success of a single value pick, you can use .TryPickValue(out T value)

Selecting a single value from a list with disabled items

if(myProbabilityList.TryPickValue(out var selectedValue))
{
    // do something with selectedValue
}

// Alternatively, you can also force the selection to pick an enabled item
myProbabilityList.MaintainPickCountIfDisabled = true;
var result = myProbabilityList.PickValue();

Disabling items can also be a powerful design choice that brings flexibility to modular selections. Please explore our Treasure Chest (WIP) sample for ideas on how to create a modular and easy-to-maintain selection systems.

Edge Cases with Disabled Items and Maintaining Pick Counts

There are two situations to keep in mind when working with disabled items.

  1. A single enabled item in the list - If there is only one enabled item in the list and Maintain Pick Counts is enabled, the selection process will be skipped, and a manually created list of items with the same value will be returned.

  2. Very small enabled probability - If all enabled items in the list represent a very small probability (for example, 0.0001%), Maintain Pick Count effectively orders the selection process to continue until the pick count is reached. This could result in a very long selection process, or even a hang (the process might never finish). RNGNeeds calculates a factor based on the total enabled probability and the number of items that should be picked. If a threshold is reached, the selection will be skipped and no values will be returned. In such a case, a warning is thrown in the console, notifying you of this edge case.