Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Massive Performance Hit When Enemies Spawn

Discussion in 'Scripting' started by Robject, Nov 14, 2012.

  1. Robject

    Robject

    Joined:
    Oct 31, 2012
    Posts:
    10
    Hi,

    I’m trying to create a survival game where the player is stuck in the centre of a massive Hive and insects are spawning from the edges to converge on the player.

    About a month ago I decided to let a few insects spawn at one time (rather than constantly shooting them down). For this test I’d selected the insect with the most complex movement pattern to spawn alone and it took only 3 of these insects to spawn before a very noticeable performance hit occurred. In further testing – by just switching on the Stats toggle in the Game window – I noticed that, even when just one insect spawns, the FPS drops by about 1/3, which is totally unacceptable.

    I have tried and tried to correct this by simplifying the script and even redoing the model a couple of times but I just cannot seem to improve the FPS. I’m on the verge of giving up on the game entirely, so any suggestions would be greatly appreciated.

    I have attached part of the movement “js” file, which includes the Start(), Update() and “Attack When Close” functions. If you need any of the other scripts that are referenced therein, would like me to post the full movement script, or would just like more of an explanation on the way the game works, please just let me know.

    Sorry in advance if my script is messy. This is the first time I’ve done any real programming.
     

    Attached Files:

  2. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    there is very little in teh posted that would be causing such significant drops in performance. But you havnt posted it all the code, so I presume the problem is elsewhere... can you post the code for
    ShiftCheck()
    Dodge()
     
  3. Deleted User

    Deleted User

    Guest

    next time please post your code here instead of a download link, many people including me, don't like do download random stuff from the web...

    so I will do that for you ;)
    Code (csharp):
    1.  
    2. function Start ()
    3. {
    4. //ACTIVATE BUG AND RADAR BLIP STARTING VARIABLES
    5. //Note: only speed frequency is NOT inverted for others <1 is greater
    6. Speed = (Camera.main.GetComponent(Difficulty).SpeedConstant)*0.8;
    7. Shiftfreq = Camera.main.GetComponent(Difficulty).ShiftingFreq*0.8;     
    8. Dodgefreq = Camera.main.GetComponent(Difficulty).DodgingFreq*1.2;
    9. Interval = Time.time + Shiftfreq;
    10. dodgeInt = Time.time + Dodgefreq;
    11. dodgeRand = Random.value;
    12. if (Random.value <= 0.5)
    13.     {
    14.     ShiftDodge = "Dodge";
    15.     }
    16. else
    17.     {
    18.     ShiftDodge = "Shift";
    19.     }
    20. //CREATE INSTANCE (the instance variable is a number generated when the bug and radar blip are spawned)
    21. ID = GetInstanceID(); //may not need this
    22. CurrentBlip = GameObject.Find("Blip"+Instance);
    23. CurrentBug = GameObject.Find("Horn"+Instance);
    24. CurrentBug.name = ("Horn"+ID); //may not need this
    25. CurrentBlip.name = ("Blip"+ID); //may not need this
    26. CurrentBlip.GetComponent(Blip).CurBug = CurrentBug;
    27. //GET TARGET AND INITIALISE COL SPHERE
    28. if (!target)
    29.     {
    30.     target = GameObject.FindWithTag("Player").transform;
    31.     transform.LookAt(target);
    32.     transform.Rotate(0,0,(Random.value*360));
    33.     }
    34. if (!Col)
    35.     {
    36.     Col = GameObject.FindWithTag("Trigger").collider;
    37.     }
    38. //INITIALSE THE RADAR BLIP
    39. MotionCheck = false;
    40. CurrentBlip.GetComponent(Blip).BugType = 5;
    41. CurrentBlip.GetComponent(Blip).Speed = Speed;
    42. //CurrentBlip.transform.rotation = CurrentBug.transform.rotation;
    43. //START NORMAL FLIGHT ANIMATION
    44. Movement = Vector3.forward * Speed;
    45. Anim = "Flight";
    46. animation.Play(Anim);
    47. }
    48.  
    49. function Update ()
    50. {
    51. if (MotionCheck == true)
    52.     {
    53.     if (animation.IsPlaying("Flight"))
    54.         {
    55.         Shift = false;
    56.         light.range = 0;
    57.         collider.direction = 2;
    58.         transform.LookAt(target);
    59.         CurrentBlip.GetComponent(Blip).Motion = true;
    60.         Movement = Vector3.forward * Speed;
    61.         MotionCheck = false;
    62.         }
    63.     else if (animation.IsPlaying("Attack"))
    64.         {
    65.         Shift = false;
    66.         light.range = 0;
    67.         MotionCheck = false;
    68.         }
    69.     }      
    70. transform.Translate(Movement, Space.Self);
    71. animation.PlayQueued(Anim, QueueMode.CompleteOthers);
    72. if (ShiftDodge == "Shift")
    73.     {
    74.     if (Time.time > Interval)
    75.         {
    76.         dodgeInt = dodgeInt +(Speed/10);
    77.         ShiftCheck();
    78.         }
    79.     }
    80. else
    81.     {
    82.     if (Time.time > dodgeInt)
    83.         {
    84.         Interval = Interval+(Speed/10);
    85.         Dodge();
    86.         }
    87.     }
    88. }
    89.  
    90. //ATTACK WHEN CLOSE
    91. function OnTriggerEnter(Col)
    92. {
    93. Movement = Vector3.zero;
    94. Anim = "Attack";
    95. collider.direction = 1;
    96. animation.PlayQueued(Anim, QueueMode.PlayNow);
    97. }
    98.  
    I took a quick look...
    you should cache your transform, animation and collider components
    string comparisons are slow use enums instead
     
    Last edited by a moderator: Nov 14, 2012
  4. Robject

    Robject

    Joined:
    Oct 31, 2012
    Posts:
    10
    Thanks for the advice Element. I'll make sure to paste the code into the thread next time. As I mentioned, I'm very new to programming. Could you possibly give me an example of how to cache one of the variables you suggested or point me to some useful text on the subject?

    I'll paste in the Dodge(), ShiftCheck(), and LightFlash() functions here as well. The only reason I left them out is that when they are running, there's a brief increase in the frame rate so I didn't think they held problem... but just in case :)

    Code (csharp):
    1. //SHIFT CHECK
    2. function ShiftCheck()
    3. {
    4. var NewAnim : String;
    5.  
    6. if (Random.value >= 0.4) //On top of the shift freq this should be edited for different bugs (the lower the no. the greater the chance)
    7.     {
    8.     if (Anim == "Flight")
    9.         {
    10.         Anim = "RotShift";
    11.         Movement = Vector3.zero;
    12.         NewAnim = "Flight";
    13.         collider.direction = 1;
    14.         }
    15.     else
    16.         {
    17.         Anim = "RotShiftAt";
    18.         Movement = Vector3.zero;
    19.         NewAnim = "Flight";
    20.         }
    21.     Shift = true;
    22.     animation.PlayQueued(Anim,QueueMode.PlayNow);
    23.     GetComponentInChildren(HornMATShift).Shifting();
    24.     CurrentBlip.GetComponent(Blip).Motion = false;
    25.     color = GetComponentInChildren(HornMATShift).LightCol;
    26.     LightFlash();
    27.     Interval = Time.time + Shiftfreq;
    28.     MotionCheck = true;
    29.     Anim = NewAnim;
    30.     }  
    31. else
    32.     {
    33.     Interval = Time.time + Shiftfreq;
    34.     }
    35.    
    36. if (Random.value <= 0.7)
    37.     {
    38.     ShiftDodge = "Dodge";
    39.     }
    40. else
    41.     {
    42.     ShiftDodge = "Shift";
    43.     }
    44. }
    45.  
    46. //DODGE
    47. function Dodge()
    48. {
    49. if (Random.value >= 0.3)
    50.     {
    51.     if (Anim == "Attack")
    52.         {
    53.         Anim = "DBack";
    54.         Movement = Vector3.back * 4*Speed;
    55.         collider.direction = 1;
    56.         CurrentBlip.GetComponent(Blip).Motion = false;
    57.         CurrentBlip.GetComponent(Blip).dodgeJump = 4;
    58.         CurrentBlip.GetComponent(Blip).DodgeJump();
    59.         MotionCheck = true;
    60.         ShiftDodge = "Dodge";
    61.         dodgeInt = Time.time + animation["DBack"].length;
    62.         dodgeRand = Random.value;
    63.         }
    64.     else
    65.         {      
    66.         if (dodgeRand <= 0.3)       //UP
    67.             {
    68.             Anim = "DUp";
    69.             Movement = Vector3.up * 3*Speed;
    70.             CurrentBlip.GetComponent(Blip).Motion = false;
    71.             CurrentBlip.GetComponent(Blip).dodgeJump = 1;                           //ANY CHANGES WILL ALSO NEED TO BE APPLIED TO BLIP
    72.             CurrentBlip.GetComponent(Blip).DodgeJump();
    73.             MotionCheck = true;
    74.             }
    75.         else if (dodgeRand <= 0.6)  //LEFT
    76.             {
    77.             Anim = "DLeft";
    78.             Movement = Vector3.left * 3*Speed;
    79.             CurrentBlip.GetComponent(Blip).Motion = false;
    80.             CurrentBlip.GetComponent(Blip).dodgeJump = 2;
    81.             CurrentBlip.GetComponent(Blip).DodgeJump();
    82.             MotionCheck = true;
    83.             }
    84.         else                        //RIGHT
    85.             {                  
    86.             Anim = "DRight";
    87.             Movement = Vector3.right * 3*Speed;
    88.             CurrentBlip.GetComponent(Blip).Motion = false;
    89.             CurrentBlip.GetComponent(Blip).dodgeJump = 3;
    90.             CurrentBlip.GetComponent(Blip).DodgeJump();
    91.             MotionCheck = true;
    92.             }
    93.         dodgeInt = Time.time + Dodgefreq;
    94.         dodgeRand = Random.value;
    95.         ShiftDodge = "Shift";
    96.         }
    97.     animation.PlayQueued(Anim,QueueMode.PlayNow);
    98.     Anim = "Flight";
    99.     }
    100. else
    101.     {
    102.     if (Random.value <= 0.7)
    103.         {
    104.         ShiftDodge = "Dodge";
    105.         }
    106.     else
    107.         {
    108.         ShiftDodge = "Shift";
    109.         }
    110.     dodgeInt = Time.time + Dodgefreq;
    111.     dodgeRand = Random.value;
    112.     }
    113. }
    114.  
    115. //LIGHT FLASH      
    116. function LightFlash()
    117. {
    118. switch (color)
    119.         {
    120.         case 1:
    121.             light.color = Color.cyan;
    122.             light.range = 40;
    123.             break;
    124.         case 2:
    125.             light.color = Color.blue;
    126.             light.range = 40;
    127.             break;
    128.         case 3:
    129.             light.color = Color.green;
    130.             light.range = 40;
    131.             break;
    132.         case 4:
    133.             light.color = Color.red;
    134.             light.range = 40;
    135.             break;
    136.         }
    137. CurrentBlip.GetComponent(Blip).SyncMat();
    138. CurrentBlip.GetComponent(Blip).Motion = true;
    139. }
     
  5. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    The problem is most likely going to be all the GetComponents. I figured this was going to be the problem which is why I asked for to see those other functions

    Try and use GetComponent as little as possible, and never in Update() (calling a function from Update is effectively the same as doing it in Update).
     
  6. Deleted User

    Deleted User

    Guest

    Code (csharp):
    1.  
    2.  
    3. myTransform : Transform = null;
    4. myCollider : Collider = null;
    5. myAnimation : Animation = null;
    6.  
    7. function Awake(){
    8.     myTransform = transform;
    9.     myCollider = gameObject.GetComponent(Collider);
    10.     myAnimation = gameObject.GetComponent(Animation);
    11. }
    12.  
    13. function Update(){
    14.     // use cached commponent
    15.     myTransform.position.x += 1;
    16. }
    17.  
    18.  
     
    Last edited by a moderator: Nov 16, 2012
  7. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,616
    Why is that unacceptable? I guess it might be, but without any context it doesn't actually mean anything. Are you dropping from 1000fps to 650fps (a relatively small increase in processing time), or from 60fps to 40fps (a relatively large increase)?

    For the sake of discussions like this, it's much more useful to consider and present your frame times as milliseconds instead of frames per second. 1000fps = 1ms/frame, and dropping to 650fps (1.54ms) only adds 0.54ms per frame. On the other hand, going from 60fps (16.7ms) to 40fps (25ms) indicates a jump in processing time of over 8ms. In both cases the frame rate drops by "about 1/3", but the performance implications are very different.
     
  8. Robject

    Robject

    Joined:
    Oct 31, 2012
    Posts:
    10
    Thanks for the further posts all. I'm going to try the caching and thinking up a way to cut down on the variables being called/assigned from other scripts.

    In response to Angrypenguin. The frame rate drops in a similar way to this:

    Start: 1000fps (1ms)
    1st Spawn: 600fps (1.54ms)
    2nd Spawn: 300fps (3.3ms) - Player rotation is now noticeably slower
    3rd Spawn: 200 (5ms) - Rotation is starting to crawl
    etc

    And considering the game scenario is Wave Survival and the rotation is severely inhibited with just 3 enemies spawned, the drop is unacceptable because it is cumulative.
     
  9. Deleted User

    Deleted User

    Guest

    Multiply the rotation with Time.deltaTime!!!
    This should keep the rotation consistant, regardless of the fps
     
  10. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Do you instantiate your objects during game play (at the time they are needed) or do you use an object pool and re-cycle objects?

    Instantiating objects during game play can cause massive frame drops. As these new objects come into existence, a whole lot of stuff happens... the geometry gets loaded, animations, coliders, etc... the scripts begin to execute, all your getcomponents happen... that is a lot of stuff....

    Instead instantiate all your objects (as many as are going to be on screen at one point) and keep them disabled or some of their components disabled until needed. That way, you can cache all the components at the start and get everything ready. Once an object is needed, simply enable whatever component that might be needed. Moving an object and turning on a renderer + collider is so much faster than instantiating an object that contains those components even if it contains no scripts at all.

    I am pressed for time and wish I could elaborate more but that are many posts about how to use object pools and re-cycling objects.
     
  11. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    While pooling does help with performance, its highly unlikely that the cause of this drop is instantiation, especially if he is only getting to 3 enemies.

    I would simply cache blip

    then I would go into any other script you are using and remove any getcomponent from update.

    As a guideline, only the following functionality should be called from update.
    1. player input (input.getkey, etc)
    2. object movement/rotation
    3. animation calls

    Everything else should be done elsewhere, including things like targetting, AI, etc

    Create your own less intensive Update function using InvokeRepeating

    Code (csharp):
    1.  
    2.  
    3. void Start()
    4. {
    5.   InvokeRepeating("DoStuff", 0.1f, 0.1f);
    6. }
    7.  
    8. void DoStuff()
    9. {
    10.  
    11. }
    12.  
    13.  
     
  12. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    When you instantiate objects that contain one or more scripts which in turn contain a bunch of GetComponents in Awake or Start, plus GameObject.Find or GetComponentInChildren, it adds up and you get hit with all of that as these objects are instantiated. Caching all these Components helps the next time you try to access them but you can't avoid that initial hit from instantiate. Using an object pool helps shift (better plan) where you will take that hit.

    As you pointed out as well, GetComponents in update or similar things will also cost you and should be avoided.

    Using InvokeRepeating or Coroutines is great when you don't need stuff to happen every single frame.

    Also anytime you use .Find or Find with Tag, you invite the Garbage Collection monster because of those strings. I use layers and stay away from Tags or = "Strings".
     
  13. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    You're preaching to the choir my friend.

    There is a difference between a constant FPS drop and a momentary FPS drop on instantiation. You are always going to have to instantiate at some point, so this is generally an un-avoidable drop.

    This would be an issue if he was constantly instantiating, but in this situation we are talking about spawning 3 enemies, which would not cause a significant hit. The constant GetComponents/Finds in update however...

    Which is why I suggested it be used for things like AI/targetting. You dont want it happening everyframe
     
    Last edited: Nov 22, 2012
  14. Robject

    Robject

    Joined:
    Oct 31, 2012
    Posts:
    10
    Thanks for the further advice! I'm still having the problem, but hadn't checked on here in a while. I've now got almost all the "GetComponents" for the blip overridden as I've:

    1) Cached the "GetComponent" components in the Awake() function

    2) Stuck the radar sphere directly on top of the Hive Sphere as only the RadarCam renders it anyway so that...
    2.1) I was able to attached the blip to the insect prefab I'm working on so all it's movement is covered by the insect's movement script but only the RadarCam can see it
    So the only "GetComponents" I use now are getting the mesh to change its Material and the blip light to change its color when the insect "Shifts". Sadly, the frame rate has either changed minimally or not at all.

    I agree that the problem must be related to how pretty much everything is run from (and thus effectively inside) Update(), so too much is happening every frame. I've not even come across InvokeRepeating before so I'm not sure how it works - what with me being such a noob - so I'll have a look into that. Then I can hopefully, get all the remaining "GetComponent" routines, as well as the Shifting and Dodging functions all done outside of Update().

    Thanks again for all the advice everyone :)
     
  15. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    To give you an idea of how InvokeRepeating works, you could compare it to the Update function.

    Update is running (as fast as it can I believe)... lets just pretend behind the scenes, Unity uses invoke repeating to kick off the Update function, so it might look like this...

    InvokeRepeating("Update", 0.01f, 0.01f);

    That means every 0.01 seconds (100x/second) Update is going to be called. Im not sure how its affected by performance, but thats some technical detail thats un-important here..

    If you wanted a function to run 10x/second...

    InvokeRepeating("MyFunction", 0.1f, 0.1f);

    if you wanted it to run 1x/second

    InvokeRepeating("MyFucntion", 1, 1);

    and so on...
     
  16. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    Update() is called every frame.

    InvokeRepeating and Coroutine allow you to basically control the frequency at which they are called.

    Personally, I prefer using Coroutines but look those two up.
     
  17. Robject

    Robject

    Joined:
    Oct 31, 2012
    Posts:
    10
    YEEEEEEEEEEEEES!!! Finally sussed it. :D Firstly let me start by saying thank you all very much for your help. I'm now using "InvokeRepeat" for all the Shifting and Dodging checks to avoid using:
    " if (Time.time <= Interval)" in my Update(), which helped performance and shortened my clumsy script.

    But it turns out what was really kicking my frame rate was my queuing of the animations! I'd been using "animation.PlayQueued(Anim, QueueMode.CompleteOthers);" in my Update() - So it must have been making a massive list of the same animations being Queued until they were overridden by a "Play Now" in one of my other functions.

    I've got around this by just adding another if statement:
    Code (csharp):
    1. if (!animation.isPlaying)
    2.     {
    3.     animation.PlayQueued(Anim, QueueMode.PlayNow);
    4.     }
    The time to render a frame now only goes up to 2ms. Annoying there's still a slight drop in player rotation but it's no way near as bad as it was and isn't very noticeable in a build run.

    Thanks again for everyone's input! :)