Skip to main content
Adding fix for built version.
Source Link
DMGregory
  • 136.3k
  • 22
  • 247
  • 373
public class Spawner : MonoBehaviour
{
   // Here's where we define our weighted object structure,
   // and flag it Serializable so it can be edited in the Inspector.
   [System.Serializable]
   public struct Spawnable
   {
       public GameObject gameObject;
       public float weight;
   }

   // Now expose an array of these to be populated in the Inspector.
   public Spawnable[] spawnList;

   // Track the total weight used in the whole array.
   float _totalSpawnWeight;

   // Update the total weight when the user modifies Inspector properties,
   // and on initialization at runtime.
   void OnValidate()
   {
      _totalSpawnWeight = 0f;
      foreach(var spawnable in spawnList)
          _totalSpawnWeight += spawnable.weight;
   }

   // As Problematic points out below, OnValidate isn't called
   // in a built executable. But in that case we don't need to react
   // to a user fiddling with the Inspector mid-game, so it suffices
   // to run this code once during Awake:
   void Awake()
   {
       OnValidate();
   }

   // Spawn an item randomly, according to the relative weights.
   public void Spawn()
   {
      // Generate a random position in the list.
      float pick = Random.value * _totalSpawnWeight;
      int chosenIndex = 0;
      float cumulativeWeight = spawnList[0].weight;

      // Step through the list until we've accumulated more weight than this.
      // The length check is for safety in case rounding errors accumulate.
      while(pick > cumulativeWeight && chosenIndex < spawnList.length - 1)
      {
         chosenIndex++;
         cumulativeWeight += spawnList[chosenIndex].weight;
      }

      // Spawn the chosen item.
      Instantiate(spawnList[chosenIndex].gameObject, transform.position, transform.rotation);
   }
}
public class Spawner : MonoBehaviour
{
   // Here's where we define our weighted object structure,
   // and flag it Serializable so it can be edited in the Inspector.
   [System.Serializable]
   public struct Spawnable
   {
       public GameObject gameObject;
       public float weight;
   }

   // Now expose an array of these to be populated in the Inspector.
   public Spawnable[] spawnList;

   // Track the total weight used in the whole array.
   float _totalSpawnWeight;

   // Update the total weight when the user modifies Inspector properties,
   // and on initialization at runtime.
   void OnValidate()
   {
      _totalSpawnWeight = 0f;
      foreach(var spawnable in spawnList)
          _totalSpawnWeight += spawnable.weight;
   }

   // Spawn an item randomly, according to the relative weights.
   public void Spawn()
   {
      // Generate a random position in the list.
      float pick = Random.value * _totalSpawnWeight;
      int chosenIndex = 0;
      float cumulativeWeight = spawnList[0].weight;

      // Step through the list until we've accumulated more weight than this.
      // The length check is for safety in case rounding errors accumulate.
      while(pick > cumulativeWeight && chosenIndex < spawnList.length - 1)
      {
         chosenIndex++;
         cumulativeWeight += spawnList[chosenIndex].weight;
      }

      // Spawn the chosen item.
      Instantiate(spawnList[chosenIndex].gameObject, transform.position, transform.rotation);
   }
}
public class Spawner : MonoBehaviour
{
   // Here's where we define our weighted object structure,
   // and flag it Serializable so it can be edited in the Inspector.
   [System.Serializable]
   public struct Spawnable
   {
       public GameObject gameObject;
       public float weight;
   }

   // Now expose an array of these to be populated in the Inspector.
   public Spawnable[] spawnList;

   // Track the total weight used in the whole array.
   float _totalSpawnWeight;

   // Update the total weight when the user modifies Inspector properties,
   // and on initialization at runtime.
   void OnValidate()
   {
      _totalSpawnWeight = 0f;
      foreach(var spawnable in spawnList)
          _totalSpawnWeight += spawnable.weight;
   }

   // As Problematic points out below, OnValidate isn't called
   // in a built executable. But in that case we don't need to react
   // to a user fiddling with the Inspector mid-game, so it suffices
   // to run this code once during Awake:
   void Awake()
   {
       OnValidate();
   }

   // Spawn an item randomly, according to the relative weights.
   public void Spawn()
   {
      // Generate a random position in the list.
      float pick = Random.value * _totalSpawnWeight;
      int chosenIndex = 0;
      float cumulativeWeight = spawnList[0].weight;

      // Step through the list until we've accumulated more weight than this.
      // The length check is for safety in case rounding errors accumulate.
      while(pick > cumulativeWeight && chosenIndex < spawnList.length - 1)
      {
         chosenIndex++;
         cumulativeWeight += spawnList[chosenIndex].weight;
      }

      // Spawn the chosen item.
      Instantiate(spawnList[chosenIndex].gameObject, transform.position, transform.rotation);
   }
}
Source Link
DMGregory
  • 136.3k
  • 22
  • 247
  • 373

Here's one way to do it, using a custom struct to store object-weight pairs:

public class Spawner : MonoBehaviour
{
   // Here's where we define our weighted object structure,
   // and flag it Serializable so it can be edited in the Inspector.
   [System.Serializable]
   public struct Spawnable
   {
       public GameObject gameObject;
       public float weight;
   }

   // Now expose an array of these to be populated in the Inspector.
   public Spawnable[] spawnList;

   // Track the total weight used in the whole array.
   float _totalSpawnWeight;

   // Update the total weight when the user modifies Inspector properties,
   // and on initialization at runtime.
   void OnValidate()
   {
      _totalSpawnWeight = 0f;
      foreach(var spawnable in spawnList)
          _totalSpawnWeight += spawnable.weight;
   }

   // Spawn an item randomly, according to the relative weights.
   public void Spawn()
   {
      // Generate a random position in the list.
      float pick = Random.value * _totalSpawnWeight;
      int chosenIndex = 0;
      float cumulativeWeight = spawnList[0].weight;

      // Step through the list until we've accumulated more weight than this.
      // The length check is for safety in case rounding errors accumulate.
      while(pick > cumulativeWeight && chosenIndex < spawnList.length - 1)
      {
         chosenIndex++;
         cumulativeWeight += spawnList[chosenIndex].weight;
      }

      // Spawn the chosen item.
      Instantiate(spawnList[chosenIndex].gameObject, transform.position, transform.rotation);
   }
}

This shows up in the Inspector like so: Inspector example showing structure fields

You can make this look neater by creating a custom PropertyDrawer or Editor, but out of the box this gets the core functionality.

Note that your weights don't have to sum to 1 or 100 or any other particular value. They're taken as portions relative to the total. As long as they're non-negative they should "just work."