Search Unity

Physics performance is suboptimal in my project and I'm not sure why

Discussion in 'Physics' started by Deleted User, May 11, 2017.

  1. Deleted User

    Deleted User

    Guest

    I've implemented a Command design pattern with C# Delegates, and it it not performing well enough. Or perhaps it is, and something else is causing this. The problem is when I try to strafe my game object in any direction the movement gets very sketchy. It jumps to its next position in a jagged manner.


    Video (problem demo begins at 0:15):



    Are delegates fairly expensive? Also does callstack size per frame impact performance as much as I think it does???

    Relevant code:


    Code (CSharp):
    1. ///GameInput.cs
    2.  
    3. using UnityEngine;
    4.  
    5. public class GameInput : MonoBehaviour
    6. {
    7.     const float toggleResetTime = 0.20f;
    8.     Vector2 screenCenter;
    9.     float limit;
    10.     Timer reset, boostReset, toggleReset;
    11.     public Camera mainCam;
    12.     public float resetLimit, ratio, boostResetLimit; /// <summary
    13.                                                      /// Ratio must be between 0.001 and 1!
    14.                                                      /// </summary>
    15.     bool flightEnabled = false;
    16.  
    17.  
    18.     public struct _input
    19.     {
    20.         public Vector2 keyboard, mousePositionRelative, mousePositionActual;
    21.         public float roll, radianAngle;
    22.         public Vector3 booster;
    23.         public bool enableFlight;
    24.         public WEAPON_TOGGLE weaponToggle;
    25.     }
    26.  
    27.     _input inpt;
    28.  
    29.  
    30.     FireCommand fire;
    31.     FlyCommand fly;
    32.     MoveCommand move;
    33.     ToggleCommand weaponToggle;
    34.     AimCommand aim;
    35.     RollCommand roll;
    36.     BoostCommand boost;
    37.     TargetCommand targetSelect;
    38.  
    39.     delegate void Commands(_input arg);
    40.  
    41.     public enum WEAPON_TOGGLE { WEAPON1, WEAPON2, WEAPON3 };
    42.     Commands updateCmds, physicsUpdateCmds;
    43.  
    44.     void Start()
    45.     {
    46.         screenCenter = new Vector2(Screen.width, Screen.height) / 2f;
    47.         limit = (screenCenter.x >= screenCenter.y ? screenCenter.y : screenCenter.x) * ratio;
    48.         reset = gameObject.AddComponent<Timer>();
    49.         boostReset = gameObject.AddComponent<Timer>(); ;
    50.         reset.TimeLimit = resetLimit;
    51.         boostReset.TimeLimit = boostResetLimit;
    52.         boostReset.Begin();
    53.  
    54.         toggleReset = gameObject.AddComponent<Timer>();
    55.         toggleReset.TimeLimit = toggleResetTime;
    56.         toggleReset.Begin();
    57.         SpaceShip slave = GetComponent<SpaceShip>();
    58.  
    59.         fire = new FireCommand(slave);
    60.  
    61.         move = new MoveCommand(slave);
    62.  
    63.         fly = new FlyCommand(slave);
    64.  
    65.         aim = new AimCommand(slave);
    66.  
    67.         roll = new RollCommand(slave);
    68.  
    69.         weaponToggle = new ToggleCommand(slave);
    70.  
    71.         boost = new BoostCommand(slave);
    72.  
    73.         targetSelect = new TargetCommand(slave);
    74.     }
    75.  
    76.     // Update is called once per frame
    77.     void Update()
    78.     {
    79.         getInput();
    80.         updateCmds(inpt);
    81.     }
    82.  
    83.     private void FixedUpdate()
    84.     {
    85.         getPhysicsInput();
    86.         physicsUpdateCmds(inpt);
    87.     }
    88.  
    89.     void getInput()
    90.     {
    91.         updateCmds = aim.Execute; //Should always be able to aim
    92.  
    93.         inpt = calculateInputs();
    94.         if (Input.GetAxisRaw("Fire1") > 0f)
    95.         {
    96.             updateCmds += fire.Execute;
    97.         }
    98.  
    99.         if (reset.TimeUp)
    100.         {
    101.             if (Input.GetAxisRaw("Spacebar") > 0f)
    102.             {
    103.                 flightEnabled = !flightEnabled;
    104.                 reset.Begin();
    105.             }
    106.         }
    107.  
    108.         if (flightEnabled)
    109.         {
    110.             updateCmds += fly.Execute;
    111.         }
    112.  
    113.         if ((inpt.roll = Input.GetAxisRaw("Roll")) != 0f)
    114.         {
    115.             updateCmds += roll.Execute;
    116.         }
    117.         if (toggleReset.TimeUp)
    118.         {
    119.             if (Input.GetAxisRaw("Hotkey1") > 0f)
    120.             {
    121.                 updateCmds += weaponToggle.Execute;
    122.                 inpt.weaponToggle = WEAPON_TOGGLE.WEAPON1;
    123.             }
    124.             else if (Input.GetAxisRaw("Hotkey2") > 0f)
    125.             {
    126.                 updateCmds += weaponToggle.Execute;
    127.                 inpt.weaponToggle = WEAPON_TOGGLE.WEAPON2;
    128.             }
    129.             else if (Input.GetAxisRaw("Hotkey3") > 0f)
    130.             {
    131.                 updateCmds += weaponToggle.Execute;
    132.                 inpt.weaponToggle = WEAPON_TOGGLE.WEAPON3;
    133.             }
    134.             toggleReset.Begin();
    135.  
    136.         }
    137.  
    138.  
    139.         if (Input.GetAxisRaw("TargetSelect") > 0f)
    140.         {
    141.             updateCmds += targetSelect.Execute;
    142.         }
    143.  
    144.     }
    145.  
    146.     void getPhysicsInput()
    147.     {
    148.       physicsUpdateCmds = move.Execute; //Always execute move
    149.  
    150.         if (Input.GetAxisRaw("Booster") > 0f)
    151.         {
    152.                 physicsUpdateCmds += boost.Execute;
    153.                 if (inpt.keyboard.x != 0f)
    154.                 {
    155.                     inpt.booster = Vector3.right * inpt.keyboard.x;
    156.                 }
    157.                 else if (inpt.keyboard.y != 0f)
    158.                 {
    159.                 inpt.booster = Vector3.forward * inpt.keyboard.y;
    160.             }
    161.         }
    162.     }
    163.  
    164.     _input calculateInputs()
    165.     {
    166.         _input local = new _input();
    167.  
    168.         local.mousePositionActual = Vector2.right * Input.mousePosition.x + Vector2.up * Input.mousePosition.y;
    169.         local.mousePositionRelative = screenCenter - local.mousePositionActual;
    170.         local.radianAngle = Vector2.Angle(local.mousePositionRelative, Vector3.right);
    171.  
    172.         float ratio = 1f;
    173.         if (local.mousePositionRelative.y < 0f)
    174.         {
    175.             local.radianAngle *= -1;
    176.         }
    177.         if (local.mousePositionRelative.magnitude < limit) //If within radius
    178.         {
    179.             ratio = local.mousePositionRelative.magnitude / limit; //Find the ratio of the mouse position relative to the limit
    180.         }
    181.         local.mousePositionRelative.Normalize();
    182.         local.mousePositionRelative *= ratio; //Multiply by the ratio
    183.  
    184.         local.keyboard = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
    185.  
    186.         return local;
    187.     }
    188. }
    189.  
    Code (CSharp):
    1. ///Booster.cs
    2.  
    3. using UnityEngine;
    4.  
    5. public class Booster : EnergyUseGadget {
    6.     public Rigidbody shipRB;
    7.     public float force;
    8.  
    9.     public void Boost(Vector3 dir)
    10.     {
    11.         if (UseEnergy())
    12.         {
    13.             shipRB.AddRelativeForce(dir * force, ForceMode.Acceleration);
    14.         }
    15.     }
    16.  
    17.     // Use this for initialization
    18.     public override void Start () {
    19.         base.Start();
    20.     }
    21.    
    22.     // Update is called once per frame
    23.     public override void Update () {
    24.         base.Update();
    25.     }
    26. }
    27.  
    Code (CSharp):
    1. //SpaceShip.cs
    2.  
    3. using UnityEngine;
    4.  
    5. public class SpaceShip : Mortal
    6. {
    7.     /*Base class for all ships (AI, player controlled, friendly, enemy, etc.
    8.    
    9.      Designed to be used by a control class.
    10.      */
    11.     public Rigidbody shipRB;
    12.     public float force, torque, shipHP, lerpDelta, boosterForce;
    13.     float acceleration, maxVelocity, massDivDrag;
    14.     //Gadgets section
    15.     public Booster booster;
    16.     public GunComputer computer;
    17.     //end gadgets
    18.  
    19.     public float Acceleration
    20.     {
    21.         get
    22.         {
    23.             return acceleration;
    24.         }
    25.     }
    26.  
    27.     public override void Start()
    28.     {
    29.         base.Start();
    30.         massDivDrag = shipRB.mass / shipRB.drag;
    31.         maxVelocity = force / massDivDrag;
    32.     }
    33.  
    34.     public override void Update()
    35.     {
    36.         base.Update();
    37.     }
    38.     public void FixedUpdate()
    39.     {
    40.         acceleration = maxVelocity - shipRB.velocity.magnitude;
    41.     }
    42.     public void AimWeapons(Vector2 mousePosition)
    43.     {
    44.         computer.Aim(mousePosition);
    45.     }
    46.  
    47.     public void FireWeapons()
    48.     {
    49.         computer.Fire();
    50.     }
    51.  
    52.     public void SelectTarget()
    53.     {
    54. ////Temporary code
    55.         GameObject[] possibleTargets = GameObject.FindGameObjectsWithTag("Ship");
    56.         int random = Random.Range(0, possibleTargets.Length);
    57.         computer.Target = possibleTargets[random].transform;
    58.     }
    59.  
    60.     public void Toggle(GameInput.WEAPON_TOGGLE toggle)
    61.     {
    62.         computer.ToggleWeapon(toggle);
    63.     }
    64.  
    65.     public void Rotate(Vector2 input) ///Call this every update() when wanting to control the path of flight (CALL ONLY FROM UPDATE())
    66.     {
    67.         shipRB.transform.Rotate(new Vector3(input.y, -input.x, 0f) * torque * Time.deltaTime);
    68.     }
    69.     public void Roll(float roll) ///Call this every update() when wanting to control the path of flight (CALL ONLY FROM UPDATE())
    70.     {
    71.         shipRB.transform.Rotate(new Vector3(0f, 0f, roll) * torque * Time.deltaTime);
    72.     }
    73.  
    74.     public void Move(Vector2 input) ///ONLY CALL FROM FIXEDUPDATE() LOOP. Controls movement!
    75.     {
    76.         shipRB.AddRelativeForce((Vector3.forward * input.y + Vector3.right * input.x).normalized * force);
    77.     }
    78.  
    79.     public void Boost(Vector3 dir)
    80.     {
    81.         booster.Boost(dir);
    82.     }
    83.  
    84. }
    85.  
     
  2. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Make sure your rigidbody has interpolation enabled, this might be just the camera following the rigidbody outside of the physics tick (such as in LateUpdate), so it is showing the rigidbody as jittery while everything else is fine. I think interpolation will fix that.

    In regards to delegates, there is something important to do in regards to garbage collection. If you were to profile your game right now youd probably see massive garbage collection happening.
    Take a look at your code here
    Code (CSharp):
    1.  
    2.     void getInput()
    3.     {
    4.         updateCmds = aim.Execute; //Should always be able to aim
    5.         //......
    6.     }
    You have code similar to that riddled all over the place.
    This caught me off guard when I ran into this issue, but in that line of code the problem is that behind the scenes there is something being done to that aim.Execute method to be put into the delegate. I am not sure what, but I think its just creating a new delegate (might have to do with object.MemberwiseClone).
    So to avoid the garbage, you must store the aim.Execute in a delegate variable so that it only needs to be created once, and then use that variable for putting into other delegates.
    So for example, you would do this...
    Code (CSharp):
    1.  
    2.     Commands aimExecute;
    3.  
    4.     void Start()
    5.     {
    6.         aimExecute = aim.Execute;
    7.     }
    8.  
    9.     void getInput()
    10.     {
    11.         updateCmds = aimExecute;
    12.         //......
    13.     }
    However...
    There is another issue, one that I did not know of till doing some tests right now, and that is the delegates invocationList. Behind the scenes, a delegate holds a list of all the delegates you assigned to it. The way lists work is, they have an array in the backend, and in order to resize an array you must make a new array, which causes garbage. The problem here is that it seems delegates really like to completely recreate arrays whenever you add or remove delegates, except for if the delegate only had 1 delegate assigned to it, based on my tests.
    So doing
    Code (CSharp):
    1.  
    2. void Update()
    3. {
    4.     del += someDel;
    5.     del -= someDel;
    6. }
    7.  
    would cause no garbage, but if you had
    Code (CSharp):
    1.  
    2. void Start()
    3. {
    4.     del += someOtherDel;
    5. }
    6.  
    7. void Update()
    8. {
    9.     del += someDel;
    10.     del -= someDel;
    11. }
    12.  
    then there will be garbage.

    So if you are adding and removing from a delegate, as you are every frame, there will be lots of garbage.
    The alternative I think will be to have your own List<Commands> and call list.Clear() at the start of the frame.
    I am glad I found this out, since I also have delegates being assigned to and unassigned from, though its not every frame, but every weapon switch, so maybe its not so bad, but it still bothers me T.T

    You can have a look at the delegate sourcecode here (its MulticastDelegate, but I think delegates are all multicast delegates).
    http://www.dotnetframework.org/defa...icastDelegate@cs/1407647/MulticastDelegate@cs
    The relevant methods are CombineImpl, RemoveImpl, and DeleteFromInvocationList
     
    Last edited: May 12, 2017
    Gametyme and hippocoder like this.
  3. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,508
    Most probably this issue has to do with interpolation and modifying the transform of a rigidbody. You shouldn't modify directly the Transform associated to a rigidbody, as both you and the physics will be trying to set different values.
     
  4. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Any kind of assumption about perf should be profiled. Tut tut.
     
  5. Deleted User

    Deleted User

    Guest

    @Edy I know that but this seems to work for now.

    @HiddenMonk very good advice! I've since changed things to reflect what you've said. Additionally your mention of garbage collection piqued my curiosity. I had a few uses of the "new" keyword and decided to cache things instead. Most specifically things like
    Code (CSharp):
    1. inpt.mouseRelativePosition = new Vector3(mouse.x, mouse.y, roll);
    got changed to
    Code (CSharp):
    1. inpt.mouseRelativePosition= Vector3.up* mouse.x + Vector3.right * mouse.y + Vector3.forward * roll;
    making good use of operators and reducing GC work, afactl.


    @hippocoder just lol I haven't heard anyone use that language (tut tut.) since I was little. I don't know if Unity personal has the profiler... maybe I'll check that out.
     
    Last edited by a moderator: May 13, 2017
    hippocoder likes this.
  6. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Vector3 is a struct, which causes no garbage as far as I know. You can do new Vector3 as much as you like =)
     
  7. Deleted User

    Deleted User

    Guest

    Wha-wha-what? I thought it was a class hahahhahaha I never knew about that!
     
    HiddenMonk likes this.
  8. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    Except for the fact that it obviously doesnt since youre here asking for help...

    You can do things the right way, or the easy way. One will have good results, other will not.
     
  9. Deleted User

    Deleted User

    Guest

    @HiddenMonk I was just glancing through the MS C# and .Net documentation when I found this page. It seems we were correct about creating a new delegate every Update() or so.

    Delegates are immutable, once they have a method given to them they do not change.

    As per:

    https://docs.microsoft.com/en-us/do...how-to-declare-instantiate-and-use-a-delegate
     
    HiddenMonk likes this.