Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Random enemy spawn chance, with a list of enemies that expands on the run

Discussion in 'Scripting' started by Stonewood1612, Jul 13, 2015.

  1. Stonewood1612

    Stonewood1612

    Joined:
    Sep 15, 2014
    Posts:
    32
    Hello fellow community members,

    This might be a bit tricky to phrase...

    So I'm working on a spawning system that continuously spawns enemies, 1 every second. While that's easy, different types of enemies have different chances to spawn, and they don't all start to spawn from the beginning. I'm a bit stuck on how I should do the random picker, based on percentage, while all chances combined don't form 100%. Though it still always should spawn an enemy.

    So let's give an example

    At first it only is able to spawn one type of enemy. I'll call this the defaultEnemy. So this prefab starts with 100% chance to spawn basically, although this is not a set chance.

    Once enough time has passed, more enemies are allowed to spawn. Let's say Enemy1 has a 10% chance, Enemy2 has 8%. (The numbers are defined by a variable in their script/scriptableObject.) That means when those two are allowed to spawn, it has 10% chance to pick Enemy1, 8% chance to pick Enemy 2. If it picks neither, it should spawn the defaultEnemy. So that one now has 82% chance to spawn.

    And so more enemies become available to spawn over time. So the chance it picks nothing (the defaultEnemy) varies, as it decreases every time another type becomes available to spawn.

    So at the end it could look like this:
    Enemy1: 10% (time > 10)
    Enemy2: 8% (time >12)
    Enemy3: 12% (time > 60)
    Enemy4: 6% (time > 70)
    Enemy5: 4% (time > 120)
    Chance to pick nothing (= defaultEnemy) after time has passed 120 : 60%

    One goal I want with the script is that I can easily add more enemies, without making changes to the script, just by adding more in the array/list that it can spawn in the Unity inspector. That may be the hardest part even. So it would have to do a loop for each enemy in the array... but still resolve the pick chance of all of them at once...

    Your advise would greatly appreciated.

    Edit: using C# :p
     
    Last edited: Jul 13, 2015
  2. Stonewood1612

    Stonewood1612

    Joined:
    Sep 15, 2014
    Posts:
    32
    Found this:

    Code (CSharp):
    1. List<KeyValuePair<string, double>> elements = ...some data
    2. Random r = new Random();
    3. double diceRoll = r.NextDouble();
    4.  
    5. double cumulative = 0.0;
    6. for (int i = 0; i < elements.Count; i++)
    7. {
    8.     cumulative += elements[i].Value;
    9.     if (diceRoll < cumulative)
    10.     {
    11.         string selectedElement = elements[i].Key;
    12.         break;
    13.     }
    14. }
    from: http://www.vcskicks.com/random-element.php

    While that's useful, it doesn't work if your elements don't add up to 100. But this gives me an idea...
     
  3. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    493
    Create an EnemySpawn class that holds the Enemy to spawn, the percent chance of spawning, and its spawn time.
    Then create a list of these EnemySpawns, and access the relevant EnemySpawn data.
     
  4. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    493
    Here, I did a little of the work for you.
    Now, lets see you do the rest! :p

    To use this, create a single C# script named "Spawner" and replace all the code in it, with the code below, then attach it to a gameobject.

    Code (CSharp):
    1.  
    2.  
    3. using UnityEngine;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. [System.Serializable]
    8. public class EnemySpawn {
    9.     [SerializeField]
    10.     private GameObject _enemyPFB;
    11.  
    12.     [SerializeField]
    13.     private float _spawnChance;
    14.  
    15.     [SerializeField]
    16.     private int _spawnTime;
    17.  
    18.     public GameObject EnemyPrefab
    19.     {
    20.         get {return _enemyPFB;}
    21.     }
    22.  
    23.     public float SpawnChance
    24.     {
    25.         get {return _spawnChance;}
    26.     }
    27.  
    28.     public int SpawnTime
    29.     {
    30.         get {return _spawnTime;}
    31.     }
    32.  
    33.  
    34. }
    35.  
    36.  
    37. public class Spawner : MonoBehaviour {
    38.     [SerializeField]
    39.     public List<EnemySpawn> enemies;
    40.  
    41.  
    42. }
     
  5. Stonewood1612

    Stonewood1612

    Joined:
    Sep 15, 2014
    Posts:
    32
    Sorry I actually wrote this code yesterday, before I saw your posts :p
    It works as I want it to. At least for now. It actually wasn't hard at all, but without that website, I wouldn't have figured it out that easily.

    Code (CSharp):
    1. public class SpawnWaveGenerator : MonoBehaviour {
    2.    
    3.     [System.Serializable]
    4.     public class enemy {
    5.        
    6.         public string name;
    7.         public GameObject prefab;
    8.         public float chance;
    9.         public float delayTime; //a delay before they can start spawning
    10.        
    11.     }
    12.  
    13.     public Transform[] points;
    14.     public enemy[] enemies;
    15.     public float spawnDelay = 1.0f; //affected by difficulty
    16.  
    17.     enemy chosenEnemy;
    18.    
    19.     float random;
    20.    
    21.     float cumulative;
    22.     public string selectedEnemy; //debug
    23.  
    24.     void Start () {
    25.    
    26.         InvokeRepeating("SpawnRandom", 1f, 1f); //might be temp
    27.     }
    28.    
    29.     void Update () {
    30.         //some stuff will go here later
    31.    
    32.     }
    33.  
    34.     void SpawnRandom (){
    35.        
    36.         random = Random.value;
    37.         cumulative = 0f;
    38.        
    39.         for (int i = 0; i < enemies.Length; i++)
    40.         {
    41.             cumulative += enemies[i].chance;
    42.             if (random < cumulative && Time.time >= enemies[i].delayTime)
    43.             {
    44.                 selectedEnemy = enemies[i].name;
    45.                 chosenEnemy = enemies[i];
    46.                 Debug.Log("picked " + selectedEnemy);
    47.                 break;
    48.             }
    49.         }
    50.    
    51.         Instantiate(chosenEnemy.prefab, points[Random.Range(0, points.Length)].position,points[Random.Range(0, points.Length)].rotation);
    52.    
    53.     }
    54. }
    The enemies and their chances can be assigned in the inspector :)

    What you must do is have the lowest chance enemy first, and then build up.
    The default enemy, comes always last in the array, with a chance of 1. (100%) So that one gets picked if all others fail.

    Thanks for you help, @TonanBora , even though I didn't get to use it, I greatly appreciate every post from those willing to help. Thanks again. :)
     
    Bradley_FDA likes this.
  6. TonanBora

    TonanBora

    Joined:
    Feb 4, 2013
    Posts:
    493

    Glad you figured it out without seeing my script! :)
     
  7. Bradley_FDA

    Bradley_FDA

    Joined:
    Nov 24, 2015
    Posts:
    1
    I've had a look at your code @Stonewood1612 It's pretty good but I can't seem to change the spawn time. If I change the delay, in the inspector mind you, it doesn't seem to change it in game. I think I may have missed something so I'm going to keep going through it for now (see if my friends have any ideas). Any help is appreciated though. Thanks in advance
     
  8. 3zzerland

    3zzerland

    Joined:
    Oct 31, 2014
    Posts:
    42
    @Bradley_FDA
    The variable spawnDelay isn't going to impact anything because it's not being used anywhere in the code he posted. Take a look at the InvokeRepeating method he is using and see what you might be able to change there.