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

< 5 FPS After Adding This Script, Please Help?

Discussion in 'Scripting' started by Lamprey, Jan 5, 2016.

  1. Lamprey

    Lamprey

    Joined:
    May 26, 2015
    Posts:
    36
    Alright, hello. I am having a serious lag issue. Now this may be my computer, but I want to rule out any possibility of bad code or memory leak inside this new code. These classes control "Fighters" in my top down space battle sim. Before this update, I could run the game at a decent FPS, somewhere around 40 with 40 cruisers on the screen at once. Now with the fighters I get less than 5. I have attached the project for download to see if other people lag, and I have the two classes below for anyone who can help me. Thank you in advance.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class Fighter : MonoBehaviour
    6. {
    7.     //AI
    8.     public Team FighterTeam;
    9.     public float PercievedThreat;
    10.  
    11.     //Control
    12.     public float GyroSpeed;
    13.     public float ThrusterSpeed;
    14.  
    15.     //Vitals
    16.     public float HullStrength;
    17.     public float CoreStrength;
    18.  
    19.     public float EnergyLevel;
    20.  
    21.     //Systems
    22.     public GameObject EngineOne;
    23.     public bool EOneOperational;
    24.  
    25.     //Fireing Systems
    26.     public GameObject[] MainBatteryOne;
    27.     public bool MBOneCD;
    28.     public GameObject[] MainBatteryTwo;
    29.     public bool MBTwoCD;
    30.  
    31.     //Cooldown
    32.     public bool EnergyCD;
    33.  
    34.     public GameObject Destroyed;
    35.  
    36.     Rigidbody2D rbody;
    37.     SpriteRenderer rend;
    38.  
    39.     // Use this for initialization
    40.     void Start()
    41.     {
    42.         rbody = GetComponent<Rigidbody2D>();
    43.         rend = GetComponent<SpriteRenderer>();
    44.     }
    45.  
    46.     // Update is called once per frame
    47.     void Update()
    48.     {
    49.         bool EnginesOn = false;
    50.         bool GunsFiring = false;
    51.  
    52.         if (CoreStrength < 1)
    53.         {
    54.             CoreStrength = 0;
    55.             Instantiate(Destroyed, transform.position, transform.rotation);
    56.             Destroy(gameObject);
    57.         }
    58.  
    59.         if (HullStrength < 1)
    60.         {
    61.             HullStrength = 0;
    62.             EOneOperational = false;
    63.         }
    64.  
    65.         if (!EnergyCD)
    66.         {
    67.             if (EnergyLevel < 6000)
    68.             {
    69.                 if (!EnginesOn && !GunsFiring)
    70.                 {
    71.                     EnergyLevel += 25f;
    72.                 }
    73.             }
    74.         }
    75.  
    76.         if (EnergyLevel < 5)
    77.         {
    78.             EnergyLevel = 0;
    79.             EnergyCD = true;
    80.             StartCoroutine(EnergyCooldown());
    81.         }
    82.  
    83.         PercievedThreat = CoreStrength + HullStrength + EnergyLevel;
    84.     }
    85.  
    86.     IEnumerator EnergyCooldown()
    87.     {
    88.         yield return new WaitForSeconds(5);
    89.         EnergyCD = false;
    90.     }
    91.  
    92.     void MBOneCooldown()
    93.     {
    94.         MBOneCD = false;
    95.     }
    96.  
    97.     void MBTwoCooldown()
    98.     {
    99.         MBTwoCD = false;
    100.     }
    101.  
    102.     public void FireRightBatteries(Vector3 target)
    103.     {
    104.         foreach (GameObject g in MainBatteryOne)
    105.         {
    106.             g.GetComponent<MainBattery>().Fire((target - g.transform.position).normalized);
    107.         }
    108.         MBOneCD = true;
    109.     }
    110.  
    111.     public void FireLeftBatteries(Vector3 target)
    112.     {
    113.         foreach (GameObject g in MainBatteryTwo)
    114.         {
    115.             g.GetComponent<MainBattery>().Fire((target - g.transform.position).normalized);
    116.         }
    117.         MBTwoCD = true;
    118.     }
    119.  
    120.     public void ActivateGyros(Vector3 rot)
    121.     {
    122.         float h = GyroSpeed;
    123.  
    124.         if (EnergyLevel == 0)
    125.         {
    126.             h = 0;
    127.         }
    128.  
    129.         Quaternion q = Quaternion.LookRotation(transform.position - rot, Vector3.forward);
    130.         q.x = 0;
    131.         q.y = 0;
    132.  
    133.         transform.rotation = Quaternion.Slerp(transform.rotation, q, 0.1f * Time.deltaTime);
    134.     }
    135.  
    136.     public void ActivateMainThrusters()
    137.     {
    138.         bool EnginesOn;
    139.  
    140.         float v = ThrusterSpeed;
    141.  
    142.         if (EnergyLevel == 0)
    143.         {
    144.             v = 0;
    145.         }
    146.  
    147.         if (v < 0.1f && v > -0.1f)
    148.         {
    149.             EngineOne.GetComponent<SpriteRenderer>().enabled = false;
    150.         }
    151.         else
    152.         {
    153.             EnginesOn = true;
    154.             EngineOne.GetComponent<SpriteRenderer>().enabled = true;
    155.         }
    156.  
    157.         if (!EnergyCD)
    158.         {
    159.             rbody.AddForce(transform.up * v * 0.1f * Time.deltaTime, ForceMode2D.Impulse);
    160.         }
    161.     }
    162.  
    163.     public RaycastHit2D[] RadarScan(float radius)
    164.     {
    165.         RaycastHit2D[] hit = Physics2D.CircleCastAll(transform.position, radius, transform.up);
    166.         return hit;
    167.     }
    168.  
    169.     public void Cooldown()
    170.     {
    171.         Invoke("MBOneCooldown", 0.25f);
    172.         Invoke("MBTwoCooldown", 0.25f);
    173.     }
    174. }
    175.  
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class FighterAI : MonoBehaviour {
    5.  
    6.     public AI ai;
    7.     public Fighter parent;
    8.     public Target target;
    9.  
    10.     // Use this for initialization
    11.     void Start()
    12.     {
    13.         parent = GetComponentInParent<Fighter>();
    14.         ai.orders = DATA.AICONTROL;
    15.     }
    16.  
    17.     // Update is called once per frame
    18.     void Update()
    19.     {
    20.         if (ai.orders == DATA.AICONTROL)
    21.         {
    22.             RaycastHit2D[] targets = parent.RadarScan(1000);
    23.  
    24.             Fighter c = null;
    25.             Cruiser cc = null;
    26.  
    27.             foreach (RaycastHit2D t in targets)
    28.             {
    29.                 if (t.collider.gameObject.GetComponent<Fighter>() != null)
    30.                 {
    31.                     if (t.collider.gameObject.GetComponent<Fighter>().FighterTeam != parent.FighterTeam)
    32.                     {
    33.                         if (c == null)
    34.                         {
    35.                             c = t.collider.gameObject.GetComponent<Fighter>();
    36.                         }
    37.                         else
    38.                         {
    39.                             if (Vector3.Distance(c.transform.position, transform.position) > Vector3.Distance(t.transform.position, transform.position))
    40.                             {
    41.                                 c = t.collider.gameObject.GetComponent<Fighter>();
    42.                             }
    43.                         }
    44.                     }
    45.                 }
    46.                 else if (t.collider.gameObject.GetComponent<Cruiser>() != null)
    47.                 {
    48.                     if (t.collider.gameObject.GetComponent<Cruiser>().CruiserTeam != parent.FighterTeam)
    49.                     {
    50.                         if (c == null)
    51.                         {
    52.                             cc = t.collider.gameObject.GetComponent<Cruiser>();
    53.                         }
    54.                         else
    55.                         {
    56.                             if (c.PercievedThreat > t.collider.gameObject.GetComponent<Cruiser>().PercievedThreat)
    57.                             {
    58.                                 cc = t.collider.gameObject.GetComponent<Cruiser>();
    59.                             }
    60.                         }
    61.                     }
    62.                 }
    63.  
    64.                 if (c != null)
    65.                 {
    66.                     bool vuln = false;
    67.  
    68.                     if (c.HullStrength < 1)
    69.                     {
    70.                         vuln = true;
    71.                     }
    72.                     else
    73.                     {
    74.                         vuln = false;
    75.                     }
    76.  
    77.                     target = new Target(c, Vector3.Distance(parent.transform.position, c.gameObject.transform.position), Quaternion.Angle(parent.transform.rotation, c.gameObject.transform.rotation) - 180, vuln);
    78.  
    79.                     if (target.angle > 0 || target.angle < 0)
    80.                     {
    81.                         parent.ActivateGyros(target.fEnemy.gameObject.transform.position);
    82.                     }
    83.  
    84.                     if (target.distance > 80)
    85.                     {
    86.                         parent.ActivateMainThrusters();
    87.                     }
    88.                     else if (target.distance <= 80)
    89.                     {
    90.                         if (!parent.MBOneCD && !parent.MBTwoCD)
    91.                         {
    92.                             parent.FireRightBatteries(c.gameObject.transform.position);
    93.                             parent.FireLeftBatteries(c.gameObject.transform.position);
    94.                             parent.MBOneCD = true; parent.MBTwoCD = true;
    95.                         }
    96.                     }
    97.                 }
    98.                 else if (cc != null)
    99.                 {
    100.                     bool vuln = false;
    101.  
    102.                     if (cc.ShieldStrength < 1)
    103.                     {
    104.                         vuln = true;
    105.                     }
    106.                     else
    107.                     {
    108.                         vuln = false;
    109.                     }
    110.  
    111.                     target = new Target(cc, Vector3.Distance(parent.transform.position, cc.gameObject.transform.position), Quaternion.Angle(parent.transform.rotation, cc.gameObject.transform.rotation) - 180, vuln);
    112.  
    113.                     if (target.angle > 0 || target.angle < 0)
    114.                     {
    115.                         parent.ActivateGyros(target.enemy.gameObject.transform.position);
    116.                     }
    117.  
    118.                     if (target.distance > 40)
    119.                     {
    120.                         parent.ActivateMainThrusters();
    121.                     }
    122.                     else if (target.distance <= 40)
    123.                     {
    124.                         if (!parent.MBOneCD && !parent.MBTwoCD)
    125.                         {
    126.                             parent.FireRightBatteries(cc.gameObject.transform.position);
    127.                             parent.FireLeftBatteries(cc.gameObject.transform.position);
    128.                             parent.MBOneCD = true; parent.MBTwoCD = true;
    129.                         }
    130.                     }
    131.                 }
    132.             }
    133.         }
    134.         else if (ai.orders == DATA.PLAYERCONTROL)
    135.         {
    136.  
    137.         }
    138.     }
    139. }
    140.  
     
  2. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
  3. MaxRoetzler

    MaxRoetzler

    Joined:
    Jan 3, 2010
    Posts:
    136
    You should just take a look at the Profiler to find any of these issues.

    With only the two scripts to look at, I'd say it might be the Fighter-AI Update method. Seems kinda heavy, but hard to say without knowing what else is going on, how many fighters are in the scene etc.

    Generally I'd say:
    • Cache some of the objects in the Update method! (GetComponent<Fighter> ..)
    • Make an abstract ship class, or use an interface to handle targeting, then you don't have to check for every specific ship type separately. (fighter, cruiser, ...)
    • Split the Fighter AI into further sub-routines, and exit as early as possible (AiControl currently handles finding enemies, moving towards them, firing at them... could be split into AiSeek, AiChase, AiAttack)
    • To reduce the number of raycasts, you could put all fighter Teams into their own physics layer, and only circle / spherecast against that layer (not sure if it makes sense, haven't done anything with teams/factions yet :))
     
    Last edited: Jan 5, 2016
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,199
    How many targets are you typically getting?

    Say there's 20 ships, and all of them are within range of each other, that will cause the foreach-loop in your Update to run 20*20=400 times.

    It seems to me like your ships are figuring out their targets every frame. You'll save a lot of performance by having them figure out new targets only once, and then sticking to that until something happens (target destroyed, out of range, self attacked, order given, etc).

    It might also be that radarscan is very expensive, can't tell without seeing it. As other people have said, the profiler is your friend.
     
  5. Lamprey

    Lamprey

    Joined:
    May 26, 2015
    Posts:
    36
    Thanks for the replies, I will test everyone's solutions and see what happens. I'm planning on moving towards the next phase of my game, from an AI controlled sandbox to a serious RTS. With that I'll try and fix everything. I also had a question about the difference between Physics2D.<InsertCastType>All and Physics2D.<InsertCastType>. If Physics2D.CircleCastAll is super expensive, I'll try using just circle cast with a layer mask. I'll also move Radar to a non update system and only update Targets if a target is lost (Target Distance > maxRange, or Target Destroyed). I may also turn polygon colliders into box colliders/circle colliders to reduce complexity on collision if they are also expensive. Any suggestions or elaborations on my ideas would be nice. I will also look into this Profiler, even though I have no idea what it is.
     
  6. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    regarding the "all" casts... if you look at the descriptions on the physics2d api page:
    http://docs.unity3d.com/ScriptReference/Physics2D.html
    no point continuing to search for everything if you just care "is something there, don't really care what?"
     
  7. Lamprey

    Lamprey

    Joined:
    May 26, 2015
    Posts:
    36
    I guess the reason I used CircleCastAll is because I wanted the AI to look for all enemy ships within a circle around it. Then to cycle through each one until it gets the one with the least distance to it and perceived threat (perceived threat = a target's Energy Level, Shield Strength, Hull Strength, and Core Strength). It also checked to see if the target was vulnerable (Shield Strength and Hull Strength = 0). The issue with lag is probably derived from the fact this occurs every update. Would moving it to fixed update help, or should I just make it so when the target is lost or destroyed it has to search for another target.
     
  8. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    ah, if there is more to is that just "something there" then it sounds like you need the All.

    Update runs every frame (however many frames per second), Fixed Update runs every "fixed step" (50 steps per second as the default comes to mind, but that might be wrong)... how often does the AI need to check something is changing? It might be worth doing a "check state" a couple of times a second and when something it is interacting with changes (i.e. target destroyed etc.). For something like that you can have it run in a coroutine and periodically run rather than depending on the limited choice or tick rates provided by the update functions.

    (Sebastian does a periodic coroutine check for the pathfinding update in this example:
    )
     
  9. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    926
    You could also spread these calculations over a fixed amount of frames by processing only a fraction of all the ships every cycle. The next step would be some kind of grid /spatial hashing to reduce the amount of calculations and maybe in the process not rely on Circlecast but manual rect or radii -queries.
     
    LeftyRighty likes this.