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;
}
// 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);
}
}
This shows up in the Inspector like so:
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."