Random Selector is a utility package, that provides an efficient, weight-based, random selector. The selector can be used to randomly spawn enemies, generate loot, etc. while being extremely efficient and fast.
The package includes advanced builder pattern APIs and preset configurations to make creating complex probability distributions easier and more intuitive.
Features
Random Selector includes the following features:
- Weighted randomness based on .Net’s Random class, Unity’s Random class, a thread-safe .Net Random variant, or any user-provided random number generator (such as a web-based generator)
- Builder Pattern API for fluent, chainable configuration of complex probability distributions
- Extension Methods for seamless integration between selectors and builders, enabling easy transformation and combination of existing selectors
- Preset Configurations for common distribution patterns (uniform, exponential, linear decay, normal, tiered rarity, Pareto, etc.)
- Statistics and Analytics system for tracking selection patterns, analyzing distributions, and debugging probability issues
- Unity Editor Tools including Distribution Tester and Performance Profiler for comprehensive testing and optimization
- Supports multiple usage scenarios, including:
- Editor-only
- Code-only
- Multi-level selections
- Builder pattern with method chaining
- Real-time statistics tracking
- Fully documented code base in C#
- Advanced probability manipulation methods (scaling, normalization, conditional adding)
- Performance profiling and memory usage analysis capabilities
Code base
The main class of the package is “RandomSelectorScript”. It provides a simple mechanism to select random items from a provided list of items. The class can optionally be provided an “IRandom” implementation, for greater control. Internally, the class implements the Alias Method algorithm – this results in an O(N²) one-time preparation, and then on O(1) complexity for random items selection.
The package includes several additional core classes:
- WeightedSelectorBuilder – A fluent API builder for constructing complex probability distributions with method chaining
- WeightedSelectorPresets – Static factory methods for creating common weighted selection patterns
- RandomSelectorExtensions – Extension methods for seamless integration between selectors and builders
- SelectionStatistics – Data structure for tracking selection patterns and frequencies
- SelectionAnalytics – Real-time analytics system for monitoring selection behavior
- SelectionAnalyticsUtility – Utility class for easy integration of analytics tracking
The package provides 3 implementations for the IRandom interface:
- SystemRandom – IRandom implementation for the System.Random class
- ThreadSafeRandom – IRandom implementation for a thread-safe, thread-random, System.Random class
- UnityEngineRandom – IRandom implementation for the UnityEngine.Random class
Using the RandomSelectorScript
The RandomSelectorScript can be used in multiple ways.
Unity Editor Only
As can be seen in the sample SpawnItemScript script and the sample EditorSample scene, the package can be used in an Editor-Only mode. In this mode, a MonoBehaviour implementation that contains an instance of RandomSelectorScript is used. The list of items, and their probabilities is filled from the Unity Editor.
A Spawn method can be used to select a random item, and a Unity Event can be used to respond to the item selection, as seen in the SetSelectedItemTextScript script.
Code Only
As an alternative, a code-only mode can be used to have more control, as seen in the sample SpawnItemByCodeScript script and the sample CodeSample scene. In this mode, an instance of RandomSelectorScript is created and populated with items via code. A Spawn method can again be used to select a random item, and respond to the selection.
Multi-Level selection
For a multi-level random selection can be achieved by nesting multiple RandomSelectorScript levels. As seen in the sample GetLootScript script and the sample LootSample scene, a RandomSelectorScript instance is used to select sub-RandomSelectorScript instances. In the sample, we used the main RandomSelectorScript instance for the rarity of the loot cards (common, rare or legendary) with different probabilities. Once the rarity is selected, a sub-RandomSelectorScript instance is used to select specific cards with different probabilities (e.g. Swordman, Spearman, etc.).
Editor Tools
Random Selector includes powerful Unity Editor tools to help you test, debug, and profile your weighted selection systems during development.
Distribution Tester
The Distribution Tester is accessed via Tools > Random Selector > Distribution Tester and provides comprehensive testing of your probability distributions.
Key Features
- Visual Distribution Testing: Create test configurations with weighted items and run thousands of selections to verify your probability distributions
- Real-Time Results Visualization: See selection results in both chart and table formats with color-coded items
- Deviation Analysis: Compare actual selection frequencies against expected probabilities with deviation indicators
- Multiple Random Providers: Test with UnityEngine.Random, System.Random (with seeds), or any custom IRandom implementation
- Export Capabilities: Export test results to CSV files or Unity’s debug log for further analysis
Using the Distribution Tester
- Configure Test Items: Add items with their respective weights and assign colors for visualization
- Set Test Parameters: Choose number of test selections (100 to 100,000) and random provider
- Run Tests: Execute distribution tests and analyze results in real-time
- Analyze Deviations: Review actual vs expected percentages with color-coded deviation indicators
- Export Results: Save detailed results for documentation or further analysis
Example Workflow
// The Distribution Tester helps verify configurations like:
var lootSelector = new WeightedSelectorBuilder<string>()
.AddItem("Common Loot", 50) // Should be ~50% of selections
.AddItem("Uncommon Loot", 30) // Should be ~30% of selections
.AddItem("Rare Loot", 15) // Should be ~15% of selections
.AddItem("Epic Loot", 4) // Should be ~4% of selections
.AddItem("Legendary Loot", 1) // Should be ~1% of selections
.Build();
// Use Distribution Tester to verify these percentages match reality
// across thousands of test selections
Performance Profiler
The Performance Profiler is accessed via Tools > Random Selector > Performance Profiler and provides detailed performance analysis of your Random Selector implementations.
Profiling Capabilities
- Selection Performance: Measure selections per second across different random providers
- Initialization Benchmarking: Compare setup times for different selector configurations
- Memory Usage Analysis: Track memory consumption patterns across various selector sizes
- Provider Comparison: Side-by-side performance comparison of UnityEngine.Random, System.Random, and ThreadSafeRandom
- Scalability Testing: Test performance with different numbers of items (10 to 1,000+)
- Custom Test Sizes: Configure small (1K), medium (10K), and large (100K) selection tests
Key Performance Metrics
- Selections Per Second: Raw selection throughput for each random provider
- Initialization Time: Time required to build alias tables and prepare selectors
- Memory Footprint: Memory usage for different selector configurations
- Scaling Characteristics: How performance changes with selector size
Using the Performance Profiler
- Configure Test Parameters: Set test sizes, item counts, and choose which random providers to benchmark
- Run Performance Tests: Execute comprehensive benchmarks across all configurations
- Analyze Results: Review detailed performance metrics in sortable tables
- Identify Bottlenecks: Find performance issues before they impact your game
- Compare Providers: Choose the best random provider for your specific use case
Performance Optimization Tips
- UnityEngine.Random typically offers the best performance for most Unity applications
- System.Random with seeds provides deterministic results for testing and debugging
- ThreadSafeRandom is essential for multi-threaded scenarios but has slight overhead
- Initialization cost is amortized over many selections – the Alias Method’s O(1) selection performance shines with frequent use
Additional Editor Utilities
Cache Management
Use Tools > Random Selector > Clear All Caches to clear internal caches when experiencing editor issues.
Integration with Unity Profiler
The Performance Profiler integrates with Unity’s built-in Profiler, providing detailed markers for:
RandomSelector.GetItem– Individual selection operationsRandomSelector.Initialize– Alias table constructionRandomSelector.AddItems– Item addition operations
These editor tools ensure your Random Selector configurations perform optimally and behave as expected, providing confidence in your weighted selection systems before deployment.
Builder Pattern API
The Random Selector package now includes a powerful builder pattern API that makes creating and configuring weighted selectors much more intuitive and flexible.
WeightedSelectorBuilder
The WeightedSelectorBuilder<T> class provides a fluent API for constructing complex probability distributions with method chaining. This approach offers several advantages:
- Readability: Chainable methods make the code self-documenting
- Flexibility: Easy to modify and experiment with different configurations
- Validation: Built-in validation ensures your configuration is correct
- Reusability: Builders can be cloned and modified for variations
Basic Usage
using RandomSelector;
// Create a simple weighted selector
var selector = new WeightedSelectorBuilder<string>()
.AddItem("Common Item", 70)
.AddItem("Rare Item", 20)
.AddItem("Legendary Item", 10)
.Build();
// Use the selector
var selectedItem = selector.GetItem();
Core Building Methods
The builder provides several methods for adding items:
- AddItem(T item, int weight) – Add a single item with specified weight
- AddItems(IEnumerable items, int weight) – Add multiple items with equal weights
- AddItems(IEnumerable> items) – Add from existing probability items
- AddItemWithPercentage(T item, float percentage) – Add item with percentage (0-100%)
- AddIf(T item, int weight, bool condition) – Conditionally add items
// Adding multiple items with equal weights
var selector = new WeightedSelectorBuilder<GameObject>()
.AddItems(commonEnemies, 50)
.AddItems(rareEnemies, 15)
.AddItem(bossEnemy, 5)
.AddIf(seasonalEnemy, 30, isHolidaySeason)
.Build();
Probability Adjustment Methods
Fine-tune your probability distributions:
- ScaleProbabilities(float multiplier) – Scale all weights proportionally
- NormalizeProbabilities(int targetSum) – Normalize weights to sum to target
- SetEqualProbabilities(int weight) – Set all items to equal weights
- AdjustItemProbability(T item, int newWeight) – Change weight of specific item
var selector = new WeightedSelectorBuilder<string>()
.AddItem("Item A", 10)
.AddItem("Item B", 20)
.AddItem("Item C", 30)
.ScaleProbabilities(2.0f) // Double all weights
.NormalizeProbabilities(100) // Normalize to sum to 100
.Build();
Configuration Methods
Configure advanced behavior:
- WithRandom(IRandom random) – Use custom random number generator
- WithSeed(int seed) – Set seed for deterministic results
- OnItemSelected(UnityAction callback) – Add selection event handler
var selector = new WeightedSelectorBuilder<string>()
.AddItem("Test Item", 1)
.WithSeed(12345) // Deterministic for testing
.OnItemSelected(item => Debug.Log($"Selected: {item}"))
.Build();
WeightedSelectorPresets
The WeightedSelectorPresets static class provides factory methods for creating common distribution patterns quickly and easily.
Common Distribution Patterns
Uniform Distribution
All items have equal probability:
var selector = WeightedSelectorPresets
.CreateUniform(new[] { "A", "B", "C", "D" })
.Build();
Exponential Decay
Items decrease in probability exponentially:
var selector = WeightedSelectorPresets
.CreateExponential(orderedItems, baseWeight: 100, decayFactor: 0.5f)
.Build();
// Results in weights like: 100, 50, 25, 12, 6, 3, 1...
Linear Decay
Items decrease in probability linearly:
var selector = WeightedSelectorPresets
.CreateLinearDecay(orderedItems, baseWeight: 100, decrement: 10)
.Build();
// Results in weights like: 100, 90, 80, 70, 60...
Normal Distribution (Bell Curve)
Items in the middle have higher probability:
var selector = WeightedSelectorPresets
.CreateNormal(items, peakWeight: 100, sigma: 0.3f)
.Build();
Tiered Rarity System
Perfect for loot systems with rarity tiers:
var selector = WeightedSelectorPresets
.CreateTiered(
commonItems: commonLoot,
uncommonItems: uncommonLoot,
rareItems: rareLoot,
legendaryItems: legendaryLoot,
tierRatios: new int[] { 70, 20, 8, 2 })
.Build();
Pareto Distribution (80/20 Rule)
20% of items get 80% of the probability weight:
var selector = WeightedSelectorPresets
.CreatePareto(items, highWeight: 80, lowWeight: 20)
.Build();
Percentage-Based
Define exact percentage chances:
var percentages = new Dictionary<string, float>
{
{ "Very Common", 50f },
{ "Common", 30f },
{ "Uncommon", 15f },
{ "Rare", 4f },
{ "Legendary", 1f }
};
var selector = WeightedSelectorPresets
.CreatePercentage(percentages, normalizeToHundred: true)
.Build();
Custom Mathematical Function
Use any mathematical function for weight distribution:
// Quadratic function example
var selector = WeightedSelectorPresets
.CreateCustom(items,
position => 100f * (1f - position * position),
minimumWeight: 1)
.Build();
Advanced Usage Examples
Dynamic Loot System with Seasonal Adjustments
// Base loot configuration
var lootBuilder = WeightedSelectorPresets
.CreateTiered(
commonItems: baseCommonLoot,
rareItems: baseRareLoot,
legendaryItems: baseLegendaryLoot)
.AddIf(holidayItem, 25, IsHolidaySeason())
.AddIf(eventItem, 15, IsSpecialEvent());
// Adjust for player level
if (playerLevel > 50)
{
lootBuilder.ScaleProbabilities(1.5f) // Better loot for high level
.AddItems(endgameLoot, 10);
}
var lootSelector = lootBuilder
.OnItemSelected(item => LogLootDrop(item))
.Build();
A/B Testing System
// Create multiple variants for testing
var variantA = new WeightedSelectorBuilder<Reward>()
.AddItems(rewards, 1)
.WithSeed(GetPlayerSeed())
.Build();
var variantB = WeightedSelectorPresets
.CreateExponential(rewards, 100, 0.7f)
.WithSeed(GetPlayerSeed())
.Build();
var selector = isTestGroupA ? variantA : variantB;
Adaptive Enemy Spawning
var spawnBuilder = new WeightedSelectorBuilder<EnemyType>()
.AddItems(basicEnemies, GetDifficultyWeight())
.AddItems(eliteEnemies, GetEliteSpawnRate());
// Adapt based on player performance
if (playerWinRate > 0.8f)
{
spawnBuilder.ScaleProbabilities(1.3f) // Increase difficulty
.AddItems(challengeEnemies, 20);
}
else if (playerWinRate < 0.3f)
{
spawnBuilder.AddItems(supportEnemies, 30); // Add easier enemies
}
var enemySelector = spawnBuilder.Build();
The builder pattern and presets make Random Selector much more powerful and easier to use, whether you need simple uniform distributions or complex multi-tiered systems with dynamic adjustments.
Extension Methods
Random Selector now includes powerful extension methods that enable seamless integration between existing selectors and the builder pattern, providing convenient ways to transform, combine, and analyze selectors.
Core Extension Methods
The RandomSelectorExtensions class provides several methods to work with existing selectors:
Converting to Builder Pattern
// Convert an existing selector to builder for modification
var existingSelector = new RandomSelectorScript<string>();
existingSelector.AddItem(10, "Item 1");
existingSelector.AddItem(20, "Item 2");
// Convert to builder and modify
var modifiedSelector = existingSelector
.ToBuilder()
.AddItem("New Item", 15)
.ScaleProbabilities(2.0f)
.Build();
Rebuilding with Modifications
// Rebuild an existing selector with new configuration
var updatedSelector = existingSelector.RebuildWith(builder =>
{
builder.AddItem("Bonus Item", 25)
.NormalizeProbabilities(100)
.WithSeed(12345);
});
Merging Multiple Selectors
var selectors = new[] { commonLootSelector, rareLootSelector, epicLootSelector };
var mergedSelector = selectors.Merge(builder =>
{
builder.AddItem("Special Merged Item", 5)
.ScaleProbabilities(1.2f);
});
Convenient Transformation Methods
The extension methods provide shortcuts for common transformations:
// Scale all weights by a multiplier
var scaledSelector = mySelector.WithScaledWeights(1.5f);
// Normalize weights to sum to a target (default 100)
var normalizedSelector = mySelector.WithNormalizedWeights(1000);
// Add new items to an existing selector
var extendedSelector = mySelector.WithAdditionalItems(newItems, 25);
// Remove specific items
var filteredSelector = mySelector.WithoutItems(itemsToRemove);
// Remove items matching a condition
var cleanedSelector = mySelector.WithoutItemsWhere(item => item.StartsWith("Temp_"));
// Set all items to equal weights
var uniformSelector = mySelector.WithEqualWeights(10);
// Change random strategy
var seedSelector = mySelector.WithSeed(54321);
var customRandomSelector = mySelector.WithRandomStrategy(new ThreadSafeRandom());
Weight Distribution Analysis
Extension methods also provide statistical analysis of your selectors:
// Get detailed statistics about weight distribution
var stats = mySelector.GetWeightStats();
Debug.Log($"Total Items: {stats.ItemCount}");
Debug.Log($"Weight Range: {stats.MinWeight}-{stats.MaxWeight}");
Debug.Log($"Is Uniform Distribution: {stats.IsUniform}");
Debug.Log($"Full Stats: {stats.ToString()}");
Selection Statistics and Analytics
Random Selector now includes comprehensive statistics tracking and analytics to help you understand selection patterns, verify distributions, and debug probability issues.
Statistics Tracking
The SelectionStatistics and SelectionAnalytics classes provide detailed tracking of selection patterns:
Basic Statistics Collection
// Enable statistics tracking on a selector
var selector = new RandomSelectorScript<string>();
selector.AddItem(70, "Common");
selector.AddItem(20, "Uncommon");
selector.AddItem(10, "Rare");
// Create analytics tracker
var analytics = new SelectionAnalytics<string>();
// Track selections
for (int i = 0; i < 1000; i++)
{
var item = selector.GetItem();
analytics.RecordSelection(item);
}
// Get detailed statistics
var stats = analytics.GetStatistics();
Console.WriteLine(stats.ToString());
Real-Time Monitoring
// Monitor selection patterns in real-time
var utility = new SelectionAnalyticsUtility<GameObject>();
utility.StartTracking(lootSelector);
// After some gameplay...
var currentStats = utility.GetCurrentStatistics();
if (currentStats.MostSelected.Frequency > 0.8f)
{
Debug.LogWarning("One item is being selected too frequently!");
}
Statistical Analysis Features
Selection Frequency Analysis
- Track how often each item is selected
- Calculate actual vs expected frequencies
- Identify distribution anomalies
- Monitor selection patterns over time
Performance Metrics
- Total selections made
- Number of unique items selected
- Most/least frequently selected items
- Average selections per item
- Distribution deviation analysis
Usage Example with Debugging
public class LootSystem : MonoBehaviour
{
[SerializeField] private RandomSelectorScript<LootItem> lootSelector;
private SelectionAnalytics<LootItem> analytics;
void Start()
{
analytics = new SelectionAnalytics<LootItem>();
// Subscribe to selection events for automatic tracking
lootSelector.OnItemSelected.AddListener(item =>
{
analytics.RecordSelection(item);
// Debug unusual patterns
var stats = analytics.GetStatistics();
if (stats.TotalSelections % 100 == 0) // Every 100 selections
{
ValidateDistribution(stats);
}
});
}
private void ValidateDistribution(SelectionStatistics stats)
{
if (stats.MostSelected.Frequency > 0.5f)
{
Debug.LogWarning($"Item '{stats.MostSelected.Item}' selected {stats.MostSelected.Frequency:P} of the time!");
}
}
}