Practical Implementation Examples
Real-world implementation patterns for different game types and scenarios. Each example includes complete code, explanations of design decisions, and variations for different use cases based on the actual Ultimate Dice Roll Toolkit codebase.
Table of Contents
Basic Implementation Patterns
Simple Combat System
A straightforward combat implementation using basic dice rolls and modifiers:
Basic Attack Resolution
public class SimpleCombat : MonoBehaviour
{
[SerializeField] private DiceConfiguration attackDice; // d20 for attack rolls
[SerializeField] private DiceConfiguration damageDice; // Weapon damage dice
public class CombatResult
{
public bool hit;
public int damage;
public string description;
}
public CombatResult ExecuteAttack(int attackBonus, int targetAC)
{
var result = new CombatResult();
// Simple attack roll using DiceAPI
int attackRoll = DiceAPI.Roll(20, attackBonus);
result.hit = attackRoll >= targetAC;
if (result.hit)
{
// Roll damage on successful hit
RollResult damageResult = DiceAPI.RollDetailed(damageDice);
result.damage = damageResult.totalResult;
result.description = $"Hit! {attackRoll} vs AC {targetAC}, {result.damage} damage";
}
else
{
result.damage = 0;
result.description = $"Miss! {attackRoll} vs AC {targetAC}";
}
return result;
}
}
Skill Check Integration
Implementing skill checks with target numbers and degrees of success:
Skill Check System
public class SkillCheckSystem : MonoBehaviour
{
[System.Serializable]
public class SkillData
{
public string skillName;
public DiceConfiguration skillDice;
public int proficiencyBonus;
}
public SkillData[] characterSkills;
public TargetResult PerformSkillCheck(string skillName, int difficulty)
{
var skill = System.Array.Find(characterSkills, s => s.skillName == skillName);
if (skill == null)
{
Debug.LogError($"Skill {skillName} not found");
return null;
}
// Create target configuration
var targetConfig = ScriptableObject.CreateInstance<DiceConfiguration>();
targetConfig.sides = skill.skillDice.sides;
targetConfig.modifier = skill.skillDice.modifier + skill.proficiencyBonus;
targetConfig.targetConfig.enabled = true;
targetConfig.targetConfig.value = difficulty;
targetConfig.targetConfig.comparison = ComparisonType.GreaterEqual;
// Perform the skill check
TargetResult result = DiceAPI.RollTarget(targetConfig);
// Log the result
string outcome = result.isSuccess ? "SUCCESS" : "FAILURE";
Debug.Log($"{skillName} check: {result.rollResult.totalResult} vs DC {difficulty} = {outcome}");
return result;
}
}
Loot Generation System
Using weighted dice to create balanced loot distribution:
Loot Quality System
public class LootGenerator : MonoBehaviour
{
public enum LootRarity { Common, Uncommon, Rare, Epic, Legendary }
[SerializeField] private DiceConfiguration lootRollConfig;
public class LootDrop
{
public string itemName;
public LootRarity rarity;
public int quantity;
public float goldValue;
}
public LootDrop GenerateLoot(int playerLevel, float luckModifier = 0f)
{
var loot = new LootDrop();
// Use weighted dice for rarity distribution
var rarityConfig = ScriptableObject.CreateInstance<DiceConfiguration>();
rarityConfig.sides = 100;
rarityConfig.modifier = Mathf.RoundToInt(luckModifier * 10);
rarityConfig.weightConfig.enabled = true;
rarityConfig.weightConfig.algorithm = WeightAlgorithm.BiasLow; // Favor common items
int rarityRoll = DiceAPI.Roll(rarityConfig);
// Determine rarity based on roll
loot.rarity = rarityRoll switch
{
>= 95 => LootRarity.Legendary,
>= 85 => LootRarity.Epic,
>= 70 => LootRarity.Rare,
>= 40 => LootRarity.Uncommon,
_ => LootRarity.Common
};
// Generate quantity using drop mechanics for consistent results
var quantityResult = DiceAPI.RollDrop(6, 4, 1, DropType.Lowest);
loot.quantity = quantityResult.totalResult;
// Scale value by level and rarity
int baseValue = playerLevel * (int)loot.rarity;
loot.goldValue = baseValue * loot.quantity;
loot.itemName = GenerateItemName(loot.rarity);
return loot;
}
private string GenerateItemName(LootRarity rarity)
{
string[] prefixes = { "Worn", "Simple", "Fine", "Masterwork", "Legendary" };
string[] items = { "Sword", "Shield", "Potion", "Ring", "Amulet" };
string prefix = prefixes[(int)rarity];
int itemIndex = DiceAPI.Roll(items.Length) - 1;
return $"{prefix} {items[itemIndex]}";
}
}
Advanced Game Systems
RPG Combat Engine
Complex combat system with advantage/disadvantage and conditional rules:
Advanced Combat with Rules
public class AdvancedCombat : MonoBehaviour
{
[SerializeField] private ConditionalRule criticalHitRule;
[SerializeField] private ConditionalRule fumbleRule;
public class CombatAction
{
public string attackerName;
public string weaponName;
public bool hasAdvantage;
public bool hasDisadvantage;
public int attackBonus;
public DiceConfiguration weaponDamage;
}
public class DetailedCombatResult
{
public bool hit;
public bool critical;
public bool fumble;
public int attackRoll;
public int damage;
public string[] triggeredRules;
public string fullDescription;
}
public DetailedCombatResult ExecuteAdvancedAttack(CombatAction action, int targetAC)
{
var result = new DetailedCombatResult();
// Create attack configuration
var attackConfig = ScriptableObject.CreateInstance<DiceConfiguration>();
attackConfig.sides = 20;
attackConfig.modifier = action.attackBonus;
// Apply advantage/disadvantage
if (action.hasAdvantage && !action.hasDisadvantage)
{
attackConfig.rollType = RollType.Advantage;
}
else if (action.hasDisadvantage && !action.hasAdvantage)
{
attackConfig.rollType = RollType.Disadvantage;
}
// Execute attack with conditional rules
var rules = new ConditionalRule[] { criticalHitRule, fumbleRule };
var (attackResult, nextModifier) = DiceAPI.RollWithRules(attackConfig, rules);
result.attackRoll = attackResult.totalResult;
result.hit = result.attackRoll >= targetAC;
result.critical = attackResult.naturalResult == 20;
result.fumble = attackResult.naturalResult == 1;
if (result.hit)
{
// Base damage
var damageResult = DiceAPI.RollDetailed(action.weaponDamage);
result.damage = damageResult.totalResult;
// Critical hit doubles damage
if (result.critical)
{
var critDamage = DiceAPI.RollDetailed(action.weaponDamage);
result.damage += critDamage.totalResult;
}
}
// Build description
string advantageText = action.hasAdvantage ? " (Advantage)" :
action.hasDisadvantage ? " (Disadvantage)" : "";
string hitText = result.hit ? "HIT" : "MISS";
string specialText = result.critical ? " CRITICAL!" : result.fumble ? " FUMBLE!" : "";
result.fullDescription = $"{action.attackerName} attacks with {action.weaponName}{advantageText}: " +
$"{result.attackRoll} vs AC {targetAC} = {hitText}{specialText}";
if (result.hit)
{
result.fullDescription += $" for {result.damage} damage";
}
return result;
}
}
Crafting and Quality
Sophisticated crafting system using multiple roll types and quality determination:
Quality-Based Crafting
public class CraftingSystem : MonoBehaviour
{
public enum ItemQuality { Poor, Common, Good, Excellent, Masterwork }
[System.Serializable]
public class Recipe
{
public string itemName;
public int difficultyClass;
public DiceConfiguration skillRequired;
public int materialQuality; // 1-5 scale
}
public class CraftingResult
{
public bool success;
public ItemQuality quality;
public int skillRoll;
public int qualityRoll;
public string resultDescription;
}
public CraftingResult AttemptCrafting(Recipe recipe, int crafterSkillLevel)
{
var result = new CraftingResult();
// Main skill check
var skillConfig = recipe.skillRequired;
var skillCheck = DiceAPI.RollTarget(skillConfig, recipe.difficultyClass,
ComparisonType.GreaterEqual);
result.success = skillCheck.isSuccess;
result.skillRoll = skillCheck.rollResult.totalResult;
if (result.success)
{
// Quality determination using drop mechanics (4d6 drop lowest for consistency)
var qualityResult = DiceAPI.RollDrop(6, 4, 1, DropType.Lowest,
recipe.materialQuality);
result.qualityRoll = qualityResult.totalResult;
// Determine quality tier
result.quality = result.qualityRoll switch
{
>= 20 => ItemQuality.Masterwork,
>= 17 => ItemQuality.Excellent,
>= 14 => ItemQuality.Good,
>= 11 => ItemQuality.Common,
_ => ItemQuality.Poor
};
result.resultDescription = $"Successfully crafted {result.quality} {recipe.itemName}! " +
$"(Skill: {result.skillRoll} vs DC {recipe.difficultyClass}, " +
$"Quality: {result.qualityRoll})";
}
else
{
result.quality = ItemQuality.Poor;
result.resultDescription = $"Failed to craft {recipe.itemName}. " +
$"(Skill: {result.skillRoll} vs DC {recipe.difficultyClass})";
}
return result;
}
}
Procedural Content
Seeded procedural generation for consistent world creation:
Seeded World Generation
public class ProceduralGenerator : MonoBehaviour
{
[System.Serializable]
public class WorldSeed
{
public string seedString;
public int dungeonSize;
public int treasureCount;
public float dangerLevel;
}
public class GeneratedRoom
{
public Vector2Int position;
public string roomType;
public int monsterCount;
public bool hasTreasure;
public string description;
}
public GeneratedRoom[] GenerateWorld(WorldSeed worldSeed)
{
// Initialize seeded generation for consistent results
DiceAPI.InitializeSeed(worldSeed.seedString);
var rooms = new List<GeneratedRoom>();
// Generate room layout
for (int i = 0; i < worldSeed.dungeonSize; i++)
{
var room = new GeneratedRoom();
room.position = new Vector2Int(i % 5, i / 5);
// Room type determination using weighted distribution
var roomTypeConfig = ScriptableObject.CreateInstance<DiceConfiguration>();
roomTypeConfig.sides = 100;
roomTypeConfig.weightConfig.enabled = true;
roomTypeConfig.weightConfig.algorithm = WeightAlgorithm.BiasLow; // More common rooms
int roomTypeRoll = DiceAPI.Roll(roomTypeConfig);
room.roomType = roomTypeRoll switch
{
>= 90 => "Boss Chamber",
>= 75 => "Treasure Room",
>= 60 => "Trap Room",
>= 30 => "Monster Den",
_ => "Empty Chamber"
};
// Monster population using dice pools
if (room.roomType == "Monster Den" || room.roomType == "Boss Chamber")
{
int poolSize = room.roomType == "Boss Chamber" ? 8 : 5;
var monsterPool = DiceAPI.RollPool(6, poolSize, 4); // Count 4+ as monsters
room.monsterCount = monsterPool.totalResult;
}
// Treasure placement using exploding dice for rare valuable finds
if (room.roomType == "Treasure Room")
{
var treasureRoll = DiceAPI.RollExploding(10, 3); // Exploding d10, max 3 explosions
room.hasTreasure = treasureRoll.totalResult >= 15; // High-value threshold
}
room.description = GenerateRoomDescription(room);
rooms.Add(room);
}
return rooms.ToArray();
}
private string GenerateRoomDescription(GeneratedRoom room)
{
var description = $"{room.roomType} at {room.position}";
if (room.monsterCount > 0)
{
description += $" with {room.monsterCount} monsters";
}
if (room.hasTreasure)
{
description += " containing valuable treasure";
}
return description;
}
}
Specialized Use Cases
Opposed Roll Contests
Implementing competitive rolls using the OpposedRollConfig system:
Contest Resolution System
public class ContestSystem : MonoBehaviour
{
[SerializeField] private OpposedRollConfig armWrestlingConfig;
[SerializeField] private OpposedRollConfig ridingContestConfig;
public OpposedResult ExecuteContest(string contestType, int player1Bonus, int player2Bonus)
{
OpposedRollConfig config = contestType switch
{
"Arm Wrestling" => armWrestlingConfig,
"Horse Racing" => ridingContestConfig,
_ => armWrestlingConfig // Default
};
// Configure bonuses for this specific contest
config.attacker.modifier = player1Bonus;
config.defender.modifier = player2Bonus;
// Execute the opposed roll
OpposedResult result = DiceAPI.RollOpposed(config);
// Detailed logging
string winnerName = result.winner switch
{
ContestResult.Attacker => config.attacker.sideName,
ContestResult.Defender => config.defender.sideName,
_ => "Nobody (Tie)"
};
Debug.Log($"{contestType} Contest Result:");
Debug.Log($"{config.attacker.sideName}: {result.attackerResult}");
Debug.Log($"{config.defender.sideName}: {result.defenderResult}");
Debug.Log($"Winner: {winnerName} (Margin: {result.margin})");
return result;
}
}
Multi-Dice Sessions
Complex scenarios requiring multiple dice rolled simultaneously:
Combat Session Management
public class CombatSessionManager : MonoBehaviour
{
[SerializeField] private DiceConfiguration attackDice;
[SerializeField] private DiceConfiguration mainDamage;
[SerializeField] private DiceConfiguration bonusDamage;
public SessionResult ExecuteFullAttack(string attackerName)
{
// Create a session for complete attack resolution
var combatSession = DiceAPI.CreateSession($"{attackerName} Full Attack");
combatSession.Description = "Complete attack with main and bonus damage";
combatSession.UseSessionModifiers = true;
// Add all dice to the session
combatSession.AddDice(attackDice, "Attack Roll");
combatSession.AddDice(mainDamage, "Main Damage");
combatSession.AddDice(bonusDamage, "Bonus Damage");
// Execute entire session at once
SessionResult result = DiceAPI.RollSession(combatSession);
// Process results
var attackResult = result.GetResult("Attack Roll");
var mainDamageResult = result.GetResult("Main Damage");
var bonusDamageResult = result.GetResult("Bonus Damage");
Debug.Log($"{attackerName} Full Attack Results:");
Debug.Log($"Attack: {attackResult.totalResult}");
Debug.Log($"Main Damage: {mainDamageResult.totalResult}");
Debug.Log($"Bonus Damage: {bonusDamageResult.totalResult}");
Debug.Log($"Total Damage: {mainDamageResult.totalResult + bonusDamageResult.totalResult}");
return result;
}
}
Dynamic Rule Systems
Advanced conditional rules that modify gameplay dynamically:
Dynamic Combat Rules
public class DynamicRuleSystem : MonoBehaviour
{
[SerializeField] private ConditionalRule criticalRule;
[SerializeField] private ConditionalRule heroicMomentRule;
[SerializeField] private ConditionalRule comboRule;
public class RuleProcessingResult
{
public RollResult baseRoll;
public int finalResult;
public string[] activatedRules;
public int accumulatedModifier;
public string description;
}
public RuleProcessingResult ProcessWithDynamicRules(DiceConfiguration baseDice,
string context = "")
{
var result = new RuleProcessingResult();
// Determine which rules apply based on context
var applicableRules = GetApplicableRules(context);
// Process roll with conditional rules
var (rollResult, nextModifier) = DiceAPI.RollWithRules(baseDice, applicableRules);
result.baseRoll = rollResult;
result.finalResult = rollResult.totalResult;
result.accumulatedModifier = nextModifier;
// Track which rules were activated
var activatedRulesList = new List<string>();
// Check critical hit
if (rollResult.naturalResult == 20)
{
activatedRulesList.Add("Critical Success");
}
// Check for heroic moments (high roll + good context)
if (rollResult.totalResult >= 18 && context.Contains("dramatic"))
{
activatedRulesList.Add("Heroic Moment");
}
// Check for combo potential
if (nextModifier > 0)
{
activatedRulesList.Add("Combo Setup");
}
result.activatedRules = activatedRulesList.ToArray();
// Build comprehensive description
result.description = $"Roll: {rollResult.totalResult}";
if (result.activatedRules.Length > 0)
{
result.description += $" (Rules: {string.Join(", ", result.activatedRules)})";
}
if (result.accumulatedModifier != 0)
{
result.description += $" [+{result.accumulatedModifier} next roll]";
}
return result;
}
private ConditionalRule[] GetApplicableRules(string context)
{
var rules = new List<ConditionalRule>();
// Always include critical rule
rules.Add(criticalRule);
// Context-specific rules
if (context.Contains("combat"))
{
rules.Add(comboRule);
}
if (context.Contains("dramatic"))
{
rules.Add(heroicMomentRule);
}
return rules.ToArray();
}
}
Performance and Optimization
Configuration Management
Efficient configuration caching and reuse patterns:
Configuration Cache System
public class DiceConfigurationManager : MonoBehaviour
{
private static Dictionary<string, DiceConfiguration> configCache =
new Dictionary<string, DiceConfiguration>();
// Common configurations
public static DiceConfiguration GetD20Config()
{
return GetOrCreateConfig("d20", () => {
var config = ScriptableObject.CreateInstance<DiceConfiguration>();
config.sides = 20;
config.modifier = 0;
config.rollType = RollType.Normal;
return config;
});
}
public static DiceConfiguration GetAdvantageD20Config()
{
return GetOrCreateConfig("d20_advantage", () => {
var config = ScriptableObject.CreateInstance<DiceConfiguration>();
config.sides = 20;
config.modifier = 0;
config.rollType = RollType.Advantage;
return config;
});
}
public static DiceConfiguration GetWeaponDamageConfig(int diceSides, int modifier)
{
string key = $"weapon_{diceSides}_{modifier}";
return GetOrCreateConfig(key, () => {
var config = ScriptableObject.CreateInstance<DiceConfiguration>();
config.sides = diceSides;
config.modifier = modifier;
config.rollType = RollType.Normal;
return config;
});
}
private static DiceConfiguration GetOrCreateConfig(string key,
System.Func<DiceConfiguration> factory)
{
if (!configCache.ContainsKey(key))
{
configCache[key] = factory();
}
return configCache[key];
}
// Cleanup method for memory management
public static void ClearCache()
{
foreach (var config in configCache.Values)
{
if (config != null)
{
DestroyImmediate(config);
}
}
configCache.Clear();
}
}
Statistical Validation
Using the probability engine for game balance validation:
Balance Validation System
public class BalanceValidator : MonoBehaviour
{
public class BalanceReport
{
public string testName;
public float successRate;
public float averageResult;
public bool isBalanced;
public string recommendation;
}
public BalanceReport ValidateWeaponBalance(DiceConfiguration weaponConfig, int targetAC)
{
var report = new BalanceReport();
report.testName = $"Weapon vs AC {targetAC}";
// Analyze hit probability
var hitProbability = DiceAPI.CalculateProbability(
weaponConfig, targetAC, ComparisonType.GreaterEqual, 50000);
report.successRate = hitProbability.percentage;
report.averageResult = hitProbability.average;
// Balance evaluation
if (report.successRate >= 60f && report.successRate <= 80f)
{
report.isBalanced = true;
report.recommendation = "Weapon balance is within acceptable range";
}
else if (report.successRate < 60f)
{
report.isBalanced = false;
report.recommendation = "Weapon may be too weak - consider increasing attack bonus";
}
else
{
report.isBalanced = false;
report.recommendation = "Weapon may be too strong - consider adjusting modifiers";
}
Debug.Log($"Balance Report: {report.testName}");
Debug.Log($"Hit Rate: {report.successRate:F1}%");
Debug.Log($"Average Roll: {report.averageResult:F1}");
Debug.Log($"Recommendation: {report.recommendation}");
return report;
}
public void ValidateMultipleWeapons(DiceConfiguration[] weapons, int[] targetACs)
{
foreach (var weapon in weapons)
{
foreach (var ac in targetACs)
{
var report = ValidateWeaponBalance(weapon, ac);
// Store or process report as needed
}
}
}
}
Seeded Generation
Ensuring consistent results across game sessions:
Seeded Game Session
public class SeededGameSession : MonoBehaviour
{
[SerializeField] private string sessionSeed = "GameSession2024";
[SerializeField] private bool useSeededGeneration = true;
public class SessionState
{
public string currentSeed;
public int rollCount;
public List<int> rollHistory;
}
private SessionState currentSession;
void Start()
{
InitializeSession();
}
public void InitializeSession()
{
currentSession = new SessionState();
currentSession.rollHistory = new List<int>();
if (useSeededGeneration)
{
// Initialize with deterministic seed
DiceAPI.InitializeSeed(sessionSeed);
currentSession.currentSeed = sessionSeed;
Debug.Log($"Session initialized with seed: {sessionSeed}");
}
else
{
// Use system random
DiceAPI.InitializeSystemRandom();
currentSession.currentSeed = "System Random";
Debug.Log("Session using system random generation");
}
}
public int MakeSeededRoll(int sides, int modifier = 0)
{
int result = DiceAPI.Roll(sides, modifier);
// Track roll for session replay
currentSession.rollHistory.Add(result);
currentSession.rollCount++;
Debug.Log($"Roll #{currentSession.rollCount}: d{sides}+{modifier} = {result}");
return result;
}
public bool ValidateSessionReplay()
{
if (!useSeededGeneration)
{
Debug.LogWarning("Cannot validate replay without seeded generation");
return false;
}
// Re-initialize with same seed
var originalHistory = new List<int>(currentSession.rollHistory);
DiceAPI.InitializeSeed(sessionSeed);
// Re-execute all rolls
bool isValid = true;
for (int i = 0; i < originalHistory.Count; i++)
{
int replayResult = DiceAPI.Roll(20); // Example: assuming d20 rolls
if (replayResult != originalHistory[i])
{
Debug.LogError($"Replay validation failed at roll {i + 1}");
isValid = false;
break;
}
}
Debug.Log($"Session replay validation: {(isValid ? "PASSED" : "FAILED")}");
return isValid;
}
}