Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
12 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by Cirrocumulus · Feb 06, 2018 at 02:25 PM · scriptingprobleminterfaces

Communication between a generalized Spawner and spawned objects

I have a simple problem with instance counting that I can't resolve. I'm trying to write a generalized "Spawner" class which can instantiate any number of prefabs dragged into it in the Inspector, based on some conditions (also set in the Inspector). I need the instantiated objects to tell the Spawner when they are destroyed, without caring who the Spawner was (there can be multiple Spawners in the scene). I've defined an interface for the spawners which looks like this:

 using UnityEngine;
 public interface ICanSpawnEntities {
     void NotifyDestroyed(GameObject obj);
 }

I also have an interface to be used on the script component of the prefabs to be spawned, which looks like this:

 public interface ISpawnable
 {
     Spawner Spawner { get; set; }
 }

The script component can then do this:

 private void OnDestroy()
 {
     Spawner.NotifyDestroyed(this.gameObject);
 }    
 

In my Spawner class, I'm using the simplest possible implementation of weighted probabilities for the different prefabs. In order to keep count of the different instantiated prefabs, I'm using a Dictionary. But for the life of me I can't figure out how to write the NotifyDestroyed() method. I don't know how to compare the returned GameObject (or even a ISpawnable object) to my SpawnableObject struct. On a side note, I think the SpawnableObject properties should be static because they only define a "type" of a spawnable object (something which has a shared prefab and some stats), but when I've tried that it got me into a huge reference mess. Please help! There must be an easier way to achieve this (I'd rather not use a central EventManager for this or FindWithTag methods, I'd just like to keep the classes loosely coupled). Thanks a lot.

using System.Collections; using System.Collections.Generic; using UnityEngine;

 public class Spawner : MonoBehaviour, ICanSpawnEntities
 {
     // ---------- Inspector fields ----------

     [Range(1, 60)]
     public int secondsBetweenSpawns;
     [Range(0, 1)]
     public float overallSpawnProbability = 1;

     public SpawnableObject[] spawnableObjects;

     // ---------- Private fields ----------

     private int totalProbabilityWeights;
     private List<SpawnableObject> spawnableObjectsWeighted;
     private Dictionary<SpawnableObject, int> spawnedCount;
     private Coroutine spawnCoroutine;

     private void Awake()
     {
         // Initialize the list and dictionary
         spawnableObjectsWeighted = new List<SpawnableObject>();
         spawnedCount = new Dictionary<SpawnableObject, int>();

         // For each spawnable object, add it to the list as many times
         // as its weight value, and set its spawned count to zero
         foreach (SpawnableObject obj in spawnableObjects)
         {
             for (int i = 0; i < obj.probabilityWeight; i++)
             {
                 spawnableObjectsWeighted.Add(obj);
                 spawnedCount[obj] = 0;
             }
         }
     }

     public void NotifyDestroyed(GameObject obj)
     {
         // ?? 
     }

     private void Start()
     {
         StartSpawning();
     }

     // Other objects might need to ask this to start spawning,
     // so it's separate from Start()
     public void StartSpawning()
     {
         Debug.Log("[Spawner/StartSpawning]");
         spawnCoroutine = StartCoroutine(SpawnCoroutine());
     }

     // Stop the spawning coroutine
     public void StopSpawning()
     {
         StopCoroutine(spawnCoroutine);
     }

     // Main spawning routine
     IEnumerator SpawnCoroutine()
     {
         while (true)
         {
             // Wait 3 seconds between spawns
             yield return new WaitForSeconds(3.0f);

             // If the random value is higher than the probability,
             // wait some more (secondsBetweenSpawns)
             while (Random.value > overallSpawnProbability)
             {
                 yield return new WaitForSeconds(secondsBetweenSpawns);
             }

             // Get the object to spawn from the weighted list of objects
             SpawnableObject obj = spawnableObjectsWeighted[Random.Range(0, spawnableObjectsWeighted.Count)];

             // Instantiate the object if the maximum number hasn't been reached
             if (spawnedCount[obj] < obj.maxSimultaneousInstances)
             {
                 Instantiate(obj.prefab);
                 spawnedCount[obj]++;
                 Debug.Log("[Spawner/SpawnCoroutine] " + obj.prefab.name + " spawned");
             }
             else
             {
                 Debug.Log("[Spawner/SpawnCoroutine] "
                           + obj.prefab.name + " already at maximum ("
                           + obj.maxSimultaneousInstances + ")");
             }
         }
     }
 }



 // A struct holding an object and a weight for its spawning probability.
 // Make it serializable so its fields are visible in the Inspector.
 [System.Serializable]
 public struct SpawnableObject
 {
     public GameObject prefab;

     [Range(0, 10)]
     public int probabilityWeight;

     [Range(1, 3)]
     public int maxSimultaneousInstances;
 }
Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

1 Reply

· Add your reply
  • Sort: 
avatar image
0

Answer by Bunny83 · Feb 06, 2018 at 04:00 PM

In short: You can't.


An instance of a prefab does not have any information to know from which prefab or object it was cloned from. It's just a clone. If you want to identify a certain type of object you have to store some kind of identifier with the object. One way would be to compare the object names. As long as you ensure that the instantiated clones have the same name as the prefab and you ensure that every prefab has a unique name you can match an instance with a certain prefab type.


However you can go a different route. Just add a non serialized List to your SpawnableObject struct which would be used to track all instantiated objects of this type. You don't even need the callback from the instance since you can simply check if an instance in the list is "fake-null" / destroyed. Of course using a callback would be more efficient otherwise you would have to scan the tracking list regularily for destroyed objects.


An efficient way to implement a tracking list is to iterate the list in reverse. Removing elements at the end of a list is the cheapest operation. Removing elements in between requires moving all following elements one up. Since the order of the elements doesn't matter you can simply do:

 [System.Serializable]
 public struct SpawnableObject
 {
     public GameObject prefab;
     [Range(0, 10)]
     public int probabilityWeight;
     [Range(1, 3)]
     public int maxSimultaneousInstances;
     private List<GameObject> m_Instances;
     public int AliveCount { get {
         return RemoveDestroyed();
     } }
     public void Spawn()
     {
         if (m_Instances == null)
             m_Instances = new List<GameObject>();
         // spawn your instance here
         m_Instance.Add( newInstance );
     }
     public int RemoveDestroyed()
     {
         if (m_Instances == null)
             return 0;
         for(int i = m_Instances.Count-1; i >= 0; i--)
         {
             if (m_Instances[i] == null)
             {
                 int lastIndex = m_Instances.Count - 1;
                 if (i < lastIndex)
                     m_Instances[i] = m_Instances[lastIndex];
                 m_Instances.RemoveAt(lastIndex);
             }
         }
         return m_Instances.Count;
     }
 }

This will allow you to keep track of all instances of this prefab type. You would spawn a prefab using the Spawn method. If your spawner is "informed" about a destroyed enemy you can simply call "RemoveDestroyed()" on all of your "SpawnableObjects". In addition it will return the number of objects that are still alive.


When iterating the list to remove null elements we simple replace the null item in the list with the last item in the list and remove the last item. This way we don't move the elements around unnecessarily.


Note: If you use an object pool so the actual objects are never destroyed, you would check against the active state of the object in addition to checking against null:

 if (m_Instances[i] == null || m_Instances[i].activeSelf == false)

instead of

 if (m_Instances[i] == null)


Note that i initialize the "m_Instances" list inside the Spawn method. We can't initialized it in a field initializer since you use a struct. Structs can't have field initializers since structs and it's variables are always initialized with the default values unless you use a custom constructor. Structs can't have a custom default constructor. When using a class instead of struct this wouldn't be a problem and we could simply do

 private List<GameObject> m_Instances = new List<GameObject>();

So we wouldn't need the null check




edit
Another approach would be to turn your SpawnableObject struct into a class. That way we can reference it from the instantiated object. You can change the "ISpawnable" interface to

  public interface ISpawnable
  {
      SpawnableObject Owner { get; set; }
  }

and have the actual script instance inform the corresponding "SpawnableObject" where this instance belongs to. If you want everything more abstract you can implement a seperate interface for the SpawnableObject to define the callback.


However it highly depends on what's the actual purpose of your communication between an instance and its spawner.

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Cirrocumulus · Feb 06, 2018 at 06:09 PM 0
Share

The purpose of the communication is just for the Spawner to keep track of which and how many instances it spawned, and for the spawned instances to notify the Spawner when they've been destroyed. It seems crazy to me to go into so many interfaces just to do this simple thing. Although the string comparison method you mentioned might be the simplest one. Thanks.

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

76 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

problem with the jump code.. 1 Answer

Do asleep rigidbodies prevent from modifying its variables in runtime? 1 Answer

AssetDatabase class is missing 1 Answer

How to automatically create prefabs/gameobjects from assets? 1 Answer

Acces SceneView Settings programmatically 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges