GameObjectChanceTable.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. namespace DunGen
  6. {
  7. /**
  8. * Lots of code rewriting since Unity doesn't support serializing generics
  9. */
  10. #region Helper Class
  11. [Serializable]
  12. public sealed class GameObjectChance
  13. {
  14. public GameObject Value = null;
  15. public float MainPathWeight = 1f;
  16. public float BranchPathWeight = 1f;
  17. public AnimationCurve DepthWeightScale = AnimationCurve.Linear(0, 1, 1, 1);
  18. public TileSet TileSet; // Only used at runtime - should probably move this elsewhere
  19. public GameObjectChance()
  20. : this(null, 1, 1, null)
  21. {
  22. }
  23. public GameObjectChance(GameObject value)
  24. : this(value, 1, 1, null)
  25. {
  26. }
  27. public GameObjectChance(GameObject value, float mainPathWeight, float branchPathWeight, TileSet tileSet)
  28. {
  29. Value = value;
  30. MainPathWeight = mainPathWeight;
  31. BranchPathWeight = branchPathWeight;
  32. TileSet = tileSet;
  33. }
  34. public float GetWeight(bool isOnMainPath, float normalizedDepth)
  35. {
  36. float weight = (isOnMainPath) ? MainPathWeight : BranchPathWeight;
  37. weight *= DepthWeightScale.Evaluate(normalizedDepth);
  38. return weight;
  39. }
  40. }
  41. #endregion
  42. /// <summary>
  43. /// A table containing weighted values to be picked at random
  44. /// </summary>
  45. [Serializable]
  46. public class GameObjectChanceTable
  47. {
  48. public List<GameObjectChance> Weights = new List<GameObjectChance>();
  49. public GameObjectChanceTable Clone()
  50. {
  51. GameObjectChanceTable newTable = new GameObjectChanceTable();
  52. foreach (var w in Weights)
  53. newTable.Weights.Add(new GameObjectChance(w.Value, w.MainPathWeight, w.BranchPathWeight, w.TileSet) { DepthWeightScale = w.DepthWeightScale });
  54. return newTable;
  55. }
  56. /// <summary>
  57. /// Is there at least one non-null value in the table?
  58. /// </summary>
  59. public bool HasAnyValidEntries()
  60. {
  61. bool hasValidEntries = false;
  62. foreach (var entry in Weights)
  63. {
  64. if (entry.Value != null)
  65. {
  66. hasValidEntries = true;
  67. break;
  68. }
  69. }
  70. return hasValidEntries;
  71. }
  72. /// <summary>
  73. /// Is there at least one non-null value in the table, given the appropriate input settings?
  74. /// </summary>
  75. /// <param name="isOnMainPath"></param>
  76. /// <param name="normalizedDepth"></param>
  77. /// <param name="previouslyChosen"></param>
  78. /// <param name="allowImmediateRepeats"></param>
  79. /// <param name="allowNullSelection"></param>
  80. /// <returns></returns>
  81. public bool HasAnyValidEntries(bool isOnMainPath, float normalizedDepth, GameObject previouslyChosen, bool allowImmediateRepeats, bool allowNullSelection = false)
  82. {
  83. if (allowNullSelection)
  84. return true;
  85. bool hasValidEntries = false;
  86. foreach (var entry in Weights)
  87. {
  88. if (entry.Value != null)
  89. {
  90. float weight = entry.GetWeight(isOnMainPath, normalizedDepth);
  91. if (weight > 0f && (allowImmediateRepeats || previouslyChosen != entry.Value))
  92. {
  93. hasValidEntries = true;
  94. break;
  95. }
  96. }
  97. }
  98. return hasValidEntries;
  99. }
  100. /// <summary>
  101. /// Does this chance table contain the specified GameObject?
  102. /// </summary>
  103. /// <param name="obj">The object to check</param>
  104. /// <returns>True if the GameObject is included in the chance table</returns>
  105. public bool ContainsGameObject(GameObject obj)
  106. {
  107. foreach (var weight in Weights)
  108. if (weight.Value == obj)
  109. return true;
  110. return false;
  111. }
  112. /// <summary>
  113. /// Picks an object from the table at random, taking weights into account
  114. /// </summary>
  115. /// <param name="random">The random number generator to use</param>
  116. /// <param name="isOnMainPath">Is this object to be spawn on the main path</param>
  117. /// <param name="normalizedDepth">The normalized depth (0-1) that this object is to be spawned at in the dungeon</param>
  118. /// <returns>A random value</returns>
  119. public GameObjectChance GetRandom(RandomStream random, bool isOnMainPath, float normalizedDepth, GameObject previouslyChosen, bool allowImmediateRepeats, bool removeFromTable = false, bool allowNullSelection = false)
  120. {
  121. float totalWeight = 0;
  122. foreach (var w in Weights)
  123. {
  124. if (w == null)
  125. continue;
  126. if (!allowNullSelection && w.Value == null)
  127. continue;
  128. if (!(allowImmediateRepeats || previouslyChosen == null || w.Value != previouslyChosen))
  129. continue;
  130. totalWeight += w.GetWeight(isOnMainPath, normalizedDepth);
  131. }
  132. float randomNumber = (float)(random.NextDouble() * totalWeight);
  133. foreach (var w in Weights)
  134. {
  135. if (w == null)
  136. continue;
  137. if (!allowNullSelection && w.Value == null)
  138. continue;
  139. if (w.Value == previouslyChosen && Weights.Count > 1 && !allowImmediateRepeats)
  140. continue;
  141. float weight = w.GetWeight(isOnMainPath, normalizedDepth);
  142. if (randomNumber < weight)
  143. {
  144. if(removeFromTable)
  145. Weights.Remove(w);
  146. return w;
  147. }
  148. randomNumber -= weight;
  149. }
  150. return null;
  151. }
  152. /// <summary>
  153. /// Picks an object at random from a collection of tables, taking weights into account
  154. /// </summary>
  155. /// <param name="random">The random number generator to use</param>
  156. /// <param name="isOnMainPath">Is this object to be spawn on the main path</param>
  157. /// <param name="normalizedDepth">The normalized depth (0-1) that this object is to be spawned at in the dungeon</param>
  158. /// <param name="tables">A list of chance tables to pick from</param>
  159. /// <returns>A random value</returns>
  160. public static GameObject GetCombinedRandom(RandomStream random, bool isOnMainPath, float normalizedDepth, params GameObjectChanceTable[] tables)
  161. {
  162. float totalWeight = tables.SelectMany(x => x.Weights.Select(y => y.GetWeight(isOnMainPath, normalizedDepth))).Sum();
  163. float randomNumber = (float)(random.NextDouble() * totalWeight);
  164. foreach(var w in tables.SelectMany(x => x.Weights))
  165. {
  166. float weight = w.GetWeight(isOnMainPath, normalizedDepth);
  167. if (randomNumber < weight)
  168. return w.Value;
  169. randomNumber -= weight;
  170. }
  171. return null;
  172. }
  173. public static GameObjectChanceTable Combine(params GameObjectChanceTable[] tables)
  174. {
  175. GameObjectChanceTable combined = new GameObjectChanceTable();
  176. foreach(var t in tables)
  177. foreach(var w in t.Weights)
  178. combined.Weights.Add(new GameObjectChance(w.Value, w.MainPathWeight, w.BranchPathWeight, w.TileSet) { DepthWeightScale = w.DepthWeightScale });
  179. return combined;
  180. }
  181. }
  182. }