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. Dismiss Notice

How can I store functions (with return value and different params) into a queue for execution?

Discussion in 'Scripting' started by Bazz_boyy, May 10, 2016.

Thread Status:
Not open for further replies.
  1. Bazz_boyy

    Bazz_boyy

    Joined:
    May 22, 2013
    Posts:
    192
    Hi, as the title implies, I'm looking for a way to store a series of functions into a queue. These functions all have the same return value (a list), but take different parameters... Is this achievable? I tried to use Actions and store them in the queue but I got lots of errors (I read actions can only take 1 param?). What's your suggestion?

    For what it's worth, here's some code:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System;
    5.  
    6. public class LevelManager : MonoBehaviour {
    7.  
    8.     SequenceManager sQM;
    9.     int enemiesInField;
    10.     List<Enemy> activeEnemies = new List<Enemy>();
    11.     Queue<Action> sequenceQueue;
    12.  
    13.     void Start () {
    14.         sQM = GetComponent<SequenceManager>();
    15.  
    16.         PlayLevel(0);
    17.     }
    18.  
    19.     void Update () {
    20.  
    21.     }
    22.  
    23.     public void PlayLevel(int level)
    24.     {
    25.         StartCoroutine("Level" + 0);  
    26.     }
    27.  
    28.     public void RemoveEnemyFromList(Enemy enemy)
    29.     {
    30.         activeEnemies.Remove(enemy);
    31.     }
    32.  
    33.     IEnumerator Level0()
    34.     {
    35.         bool generatingSequences = true;
    36.         bool levelComplete = false;
    37.  
    38.  
    39.         while (!levelComplete)
    40.         {
    41.             while (generatingSequences)
    42.             {
    43.                 //activeEnemies.AddRange(sQM.playSequence("BullySquadSequence", 12, 20));
    44.                 Action newAction = null;
    45.                 sequenceQueue += activeEnemies.AddRange
    46.                 newAction += activeEnemies.AddRange;
    47.                 sequenceQueue.Enqueue(newAction);
    48.                 //sequenceQueue.Dequeue();
    49.                 yield return new WaitForSeconds(5);
    50.  
    51.                 /*
    52.                 activeEnemies.AddRange(sQM.FallingTrios(3, 2));
    53.                 yield return new WaitForSeconds(1);
    54.                 activeEnemies.AddRange(sQM.FallingTrios(5, 2));
    55.                 yield return new WaitForSeconds(1);
    56.                 activeEnemies.AddRange(sQM.FallingTrios(7, 2));
    57.                 yield return new WaitForSeconds(1);
    58.  
    59.                 activeEnemies.AddRange(sQM.BullySquadSequence(12, 1));
    60.                 //activeEnemies.AddRange(sQM.playSequence("BullySquadSequence", 12, 20));
    61.                 yield return new WaitForSeconds(5);
    62.  
    63.                 activeEnemies.AddRange(sQM.BullySquadSequence(12, 1));
    64.                 //activeEnemies.AddRange(sQM.playSequence("BullySquadSequence", 12, 20));
    65.  
    66.                 generatingSequences = false;
    67.              
    68.  
    69.                 yield return null;
    70.             }
    71.  
    72.             if (activeEnemies.Count == 0)
    73.             {
    74.                 levelComplete = true;
    75.                 Debug.Log("You F***ing WON M8");          
    76.             }
    77.             yield return null;
    78.         }
    79.     }
    80.  
    81. }
    82.  
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Reflection;
    4. using System.Collections.Generic;
    5. using System;
    6.  
    7. public class SequenceManager : MonoBehaviour {
    8.  
    9.     public GameObject EnemyPrefab;
    10.     Transform player;
    11.  
    12.  
    13.  
    14.     void Awake () {
    15.  
    16.         PoolManager.Instance.CreatePool(EnemyPrefab, 30);
    17.      
    18.         player = GameObject.FindWithTag("Player").transform;
    19.         if (player == null)
    20.         {
    21.             Debug.Log("Couldn't find the player!");
    22.         }
    23.     }
    24.  
    25.     void Update()
    26.     {
    27.    
    28.     }
    29.  
    30.     /*
    31.     public List<Enemy> playSequence(string _sequenceName, int _numOfEnemies, float _speed)
    32.     {
    33.         if(_sequenceName == "BullySquadSequence")
    34.         {
    35.  
    36.         }
    37.         MethodInfo mI = GetType().GetMethod(_sequenceName, BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(int), typeof(float) }, null);
    38.         if (mI != null)
    39.         {
    40.             return (List<Enemy>)mI.Invoke(this, new object[] { _numOfEnemies, _speed });
    41.  
    42.         }
    43.         else
    44.         {
    45.             Debug.Log("MethodInfo is NULL");
    46.             return null;
    47.         }
    48.     }
    49.     */
    50.  
    51.     public List<Enemy> BullySquadSequence(int _numOfEnemies, float _speed)
    52.     {
    53.         float radianBetweenEnemies;
    54.         float currentSpawnRadian = 0;
    55.         float spawnDis = 5;
    56.         //bool sequenceComplete = false;
    57.  
    58.         radianBetweenEnemies = 360 / _numOfEnemies;
    59.         Vector2 spawnPos = Vector2.zero;
    60.  
    61.         List<Enemy> activeEnemies = new List<Enemy>();
    62.  
    63.         for (int i = 0; i < _numOfEnemies; i++)
    64.         {
    65.             spawnPos = (Vector2)player.position + DegreeToVector2(currentSpawnRadian);
    66.             GameObject enemy = PoolManager.Instance.ReuseObject(EnemyPrefab, spawnPos * spawnDis, Quaternion.identity);
    67.             Enemy enemyScript = enemy.GetComponent<Enemy>();
    68.             activeEnemies.Add(enemyScript);
    69.             enemy.GetComponent<Enemy>().StartMovementSequenceCoroutine("BullySquad", 0);
    70.             currentSpawnRadian += radianBetweenEnemies;
    71.         }
    72.         return activeEnemies;
    73.     }
    74.  
    75.     public List<Enemy> FallingTrios(int _numOfEnemies, float _speed)
    76.     {
    77.         Vector2 leftMostEnemySpawn;
    78.         float disFromPlayer = 10;
    79.         float disBetweenEnemySpawn = 3;
    80.         float totalDis = disBetweenEnemySpawn * (_numOfEnemies - 1);
    81.  
    82.         List<Enemy> activeEnemies = new List<Enemy>();
    83.  
    84.         leftMostEnemySpawn = new Vector2(totalDis / 2 - totalDis, disFromPlayer);
    85.      
    86.        
    87.         for (int i = 0; i < _numOfEnemies; i++)
    88.         {
    89.             GameObject enemy = PoolManager.Instance.ReuseObject(EnemyPrefab, leftMostEnemySpawn + Vector2.right * disBetweenEnemySpawn * i, Quaternion.identity);
    90.             Enemy enemyScript = enemy.GetComponent<Enemy>();
    91.             activeEnemies.Add(enemyScript);
    92.             enemy.GetComponent<Enemy>().StartMovementSequenceCoroutine("FallingTrios", _speed);
    93.         }
    94.         return activeEnemies;
    95.     }
    96.  
    97.     public static Vector2 RadianToVector2(float radian)
    98.     {
    99.         return new Vector2(Mathf.Cos(radian), Mathf.Sin(radian));
    100.     }
    101.  
    102.     public static Vector2 DegreeToVector2(float degree)
    103.     {
    104.         return RadianToVector2(degree * Mathf.Deg2Rad);
    105.     }
    106. }
    107.  
     
  2. Qbit86

    Qbit86

    Joined:
    Sep 2, 2013
    Posts:
    487
    `Action<T1>` is a “function”, which takes one param of type `T1` and returns `void`. You probably need `Func<TResult>` which takes no params (already bound) and returns object of type `TResult`. So your queue has to have signature of `Queue<Func<List<Enemy>>>`. You add items like `queue.Add(() => BullySquadSequence(3, 7.5f))`, so actual params are bound at the time of adding action, and action is invoked later.
     
  3. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    You could introduce some abstraction by wrapping your functions into classes that derived from the same base class, and have a queue of objects of that base class. This way, you have a queue of objects of the same type yet these objects could be parameterized through their concrete class.

    What do you want to achieve, btw? Is this some sort of command queue?
     
  4. Qbit86

    Qbit86

    Joined:
    Sep 2, 2013
    Posts:
    487
    ...And invokation looks like `var enemies = queueItem();`

    If you don't want to bind params early and need to specify them at the moment of invokation, you could use signature `Func<int, float, List<Enemy>>` (the latter generic param is type of result): `Queue<Func<int, float, List<Enemy>>>`. Then adding the action would look like `queue.Add(BullySquadSequence)`, and invokation: `var enemies = queueItem(12, 20f)`;
     
  5. Bazz_boyy

    Bazz_boyy

    Joined:
    May 22, 2013
    Posts:
    192
    Right, that sounds simple enough. Can't say I understand the 'var enemies = queueItem()' bit, but I understand the rest so Ill probably figure it out as I implement it tomorrow... Thanks for your answer.

    That's probably a much better system than what I'm trying to achieve. I have a bunch of enemy sequences, which are basically like a collection of instructions that spawns enemies and tell them how to moves... then I have the levelManager which churns out the sequences to make up the level. Parameters allow me to change the number of enemies in the sequence or the speed in which they move.

    For some reason, treating the sequence as an object seemed awkward to me, as it feels more like a big function.. So I decided to house all the sequence functions into one class (SequenceManager).... Now that I have encountered this problem, it probably would have been more beneficial/ streamlined to make the Sequences classes of their own that inherit from say a base class 'Sequence'. Then like u said I could add them to the queue very simply (Initializing what would be the parameters during construction of the object?).

    I guess a command queue could be what I'm trying to achieve... just some method of getting more control/ organization with the execution of the enemy sequences/waves.
     
  6. Qbit86

    Qbit86

    Joined:
    Sep 2, 2013
    Posts:
    487
    It's just sample; more verbose one (I didn't try to compile, just from head):
    Code (csharp):
    1. Queue<Func<List<Enemy>>> queue = new Queue<Func<List<Enemy>>>();
    2. queue.Enqueue(() => BullySquadSequence(12, 20f));
    3. Func<List<Enemy>> queueItem = queue.Dequeue();
    4. List<Enemy> enemies = queueItem(); // You can also queueItem.Invoke();
    or in case of late binding:
    Code (csharp):
    1. Queue<Func<int, float, List<Enemy>>> queue = new Queue<Func<int, float, List<Enemy>>>();
    2. queue.Enqueue(BullySquadSequence);
    3. Func<int, float, List<Enemy>> queueItem = queue.Dequeue();
    4. List<Enemy> enemies = queueItem(12, 20f); // You can also queueItem.Invoke(12, 20f);
     
    joker_yash96 likes this.
  7. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @Bazz_boyy It seems you need more a data oriented solution, which would work more as level editing than programming.

    I suggest to have a look at Panda BT, it's a scripting tool based on Behaviour Tree, which might come as handy to describe your enemy waves.

    Let's say you have the following MonoBehaviour to spawn your enemies:
    Code (CSharp):
    1. using UnityEngine;
    2. using Panda; // We are using Panda BT
    3.  
    4. public class Spawner : MonoBehaviour
    5. {
    6.     public Transform[] spawnLocations; // List of locations where to spawn
    7.     public GameObject[] unitPrefabs; // List of units to spawn
    8.  
    9.     [Task] // <-- This indicates that the following method can be called from a BT script.
    10.     void Spawn( int unitIndex, int locationIndex)
    11.     {
    12.         var newUnit = GameObject.Instantiate(unitPrefabs[unitIndex]);
    13.         newUnit.transform.position = spawnLocations[locationIndex].position;
    14.         Task.current.Succeed();
    15.     }
    16. }
    17.  
    18.  
    Then you can use the Spawn task from a BT Script to define your enemy waves. Let's say you want to spawn unit 0 from locations 1 and 2, wait for 2 seconds, then spawn unit 1 from locations 3 and 4. Your BT Script would be:
    Code (CSharp):
    1. tree "Root"
    2.     sequence
    3.         Spawn( 0, 1)
    4.         Spawn( 0, 2)
    5.         Wait( 2.0 )
    6.         Spawn( 1, 3)
    7.         Spawn( 1, 4)
    8.  
    Eventually, you could describe more advanced waves using conditions, loops, ... etc.
    You can have more information about Panda BT here (I'm the author):
    http://www.pandabehaviour.com/

    If you have questions for wrapping up your system using this tool, I'd be glad to help.
     
    Last edited: May 10, 2016
  8. Bazz_boyy

    Bazz_boyy

    Joined:
    May 22, 2013
    Posts:
    192
    oooh right I see what you mean now. Yeah that's exactly what I was trying to achieve. Thanks for breaking that down for me!
     
  9. Bazz_boyy

    Bazz_boyy

    Joined:
    May 22, 2013
    Posts:
    192
    I've actually heard of Panda BT before. Someone suggested it to me as a means of managing game state if I remember correctly. I want to try it, I just haven't had the time (I'm still learning/ practicing other programming concepts). I haven't even touched on BT's yet! But from your sample code, it actually looks like a very useful and easy to read/ Implement (I especially like the BT Script example). I'll probably try refactoring my code with Panda later, but for now I'm still learning basic data structures lawl...

    I'll have to read into 'Data Oriented Design' too...

    Anyway, thanks for your insight, I'll make sure to check out Panda BT in the future.
     
  10. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,195
    @ericbegue wrote Panda BT, and believes that it's a pancea for every problem that exists - no matter what it is. In fact, the further removed your problem is from the domain of behaviour trees, the more likely he's to show up. If you post on these forums, there's about a 25% chance that he'll answer with "have you heard of my framework?"

    I'm expecting him to start trying to offer it as a solution to other kinds of problems soon - like back pain or world hunger. Last I heard, he's trying to have it run for office. He sent a copy to help with the wildfires around Fort McMurray.
     
    LeftyRighty, lordofduct and Qbit86 like this.
  11. Qbit86

    Qbit86

    Joined:
    Sep 2, 2013
    Posts:
    487
    So it's like jQuery in front-end world, right? Like: Q: How do I add two numbers in JavaScript? A: You should definitely use jQuery framework for that.
     
    lordofduct likes this.
  12. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @Bazz_boyy You're welcome. Indeed Panda BT it's really minimalist and easy to use. Once you know the basic concepts behind Behaviour Tree, writing tasks and BT scripts is very straight forward.

    Joke aside, that's perfectly right. I do believe Behaviour Tree can be applied not only to A.I. but to other broader problems as well. In fact it is applicable to any problems that requires to define logics than span multiple frames or logics that unveil during a long period of time.

    I don't know about world hunger, but it could be a good solution to back pain: since it could save you some time over the keyboard trying to debug some more traditional codes.
     
    Last edited: May 10, 2016
    LeftyRighty and Qbit86 like this.
  13. joker_yash96

    joker_yash96

    Joined:
    May 9, 2019
    Posts:
    7
    This Post Still Helping People
    Thanks
     
  14. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,521
    Please use the like button to show your appreciation rather than necroing threads.

    Thanks.
     
    joker_yash96 likes this.
Thread Status:
Not open for further replies.