Search Unity

Input System calling additional times after loading new scenes

Discussion in 'Input System' started by brandonschlosser101, Mar 28, 2021.

  1. brandonschlosser101

    brandonschlosser101

    Joined:
    Apr 19, 2018
    Posts:
    15
    For some reason when I call actions in a new scene it will call an extra time, which can cause a pair of errors like this:

    MissingReferenceException while executing 'canceled' callbacks of 'Combat/Aim[/DualShock4GamepadHID/leftTrigger]'

    and

    MissingReferenceException: The object of type 'PlayerControls' has been destroyed but you are still trying to access it.

    When I then go back into the previous scene (or any new scene I imagine) and try to do the action again, I get another pair, so in my 2nd scene I get 4, 3rd I get 6, etc.

    So it's calling an additional time every time new scene is loaded and I'm not sure how to fix this.
    I'm on the latest as of now (2021.1.0f1)

    Any ideas or help is much appreciated!!
     
  2. Do you use an event scheme? You probably don't unregister your events. If you show us your code we can tell you more. If you decide to do so, please use the code formatting option in the post editor toolbar.
     
  3. brandonschlosser101

    brandonschlosser101

    Joined:
    Apr 19, 2018
    Posts:
    15
    I'm not entirely sure that it means to "unregister" them. I thought that meant disabling them, but that doesn't fix it.

    Here's my code. I marked the line it highlights when this particular error happens when I "un-aim" and go back to a normal rotation. Line 212.

    Code (CSharp):
    1. public class PlayerControls : MonoBehaviour
    2. {
    3.     //player actions
    4.     public PlayerControlInputs playerControls;
    5.     public Combat combatScript;
    6.     private PlayerStats playerStatScript;
    7.     public float deadzone;
    8.     private Rigidbody player;
    9.     public float turnSmoothTime;
    10.     private float turnSmoothVelocity;
    11.     private Vector2 moveDirection;
    12.     public bool isRunning;
    13.     public float currentSpeed;
    14.     public float runSpeed;
    15.     public float strifeSpeed;
    16.     public float walkSpeed;
    17.  
    18.  
    19.     //main camera
    20.     public Transform camera;
    21.     public Transform cameraAnchor;
    22.     public Transform focalPoint;
    23.     private Vector2 lookDirection;
    24.     public float lookSensitivity;
    25.     public float minimumX;
    26.     public float maximumX;
    27.     private float horizontal;
    28.     private float vertical;
    29.     public float rotationSpeed;
    30.  
    31.     //Occlusion
    32.     public Transform desiredCameraPosition;
    33.  
    34.  
    35.     //Aiming camera
    36.     private Vector3 standardCamLocation;
    37.     public Vector3 playerModelRotation;
    38.     public bool isAiming;
    39.     public bool hasWeaponEquipped;
    40.     public Transform aimingTransform;
    41.     public Transform playerModel;
    42.  
    43.     public float reorientProgress;
    44.     public Quaternion currentPlayerRotation;
    45.     public float reorientSpeed;
    46.     public GameObject reticle;
    47.     private Quaternion targetAimRotation;
    48.     private bool isUnaiming;
    49.  
    50.     //AdditionalControls
    51.     //UI
    52.     public GameObject startMenu;
    53.  
    54.  
    55.     //quickturn
    56.     private float quickturnProgress = 1;
    57.     private float quickturnStartingValue;
    58.  
    59.  
    60.     private void Awake()
    61.     {
    62.         playerControls = new PlayerControlInputs();
    63.         player = this.gameObject.GetComponent<Rigidbody>();
    64.         playerStatScript = gameObject.GetComponent<PlayerStats>();
    65.         combatScript = this.gameObject.GetComponent<Combat>();
    66.  
    67.         playerControls.General.Move.performed += context => moveDirection = context.ReadValue<Vector2>();
    68.         playerControls.General.Look.performed += context => lookDirection = context.ReadValue<Vector2>();
    69.         playerControls.General.Run.started += context => ToggleRun();
    70.         playerControls.General.Quickturn.performed += context => Quickturn();
    71.         playerControls.Combat.Aim.performed += context => Aim();
    72.         playerControls.Combat.Aim.canceled += context => Revert();
    73.         playerControls.Combat.Reload.performed += ContextMenu => Reload();
    74.         playerControls.UI.Pause.performed += context => Pause();
    75.  
    76.         playerModelRotation = playerModel.localEulerAngles;
    77.         standardCamLocation = camera.transform.localPosition;
    78.         camera.transform.position = desiredCameraPosition.position;
    79.  
    80.     }
    81.  
    82.     private void OnEnable()
    83.     {
    84.         playerControls.Enable();
    85.     }
    86.  
    87.     void Pause()
    88.     {
    89.         startMenu.SetActive(true);
    90.     }
    91.  
    92.     public void ToggleRun()
    93.     {
    94.         if (isRunning)
    95.         {
    96.             isRunning = false;
    97.             currentSpeed = walkSpeed;
    98.         }
    99.  
    100.         else if (isRunning == false)
    101.         {
    102.             isRunning = true;
    103.             currentSpeed = runSpeed;
    104.         }
    105.  
    106.         else
    107.         {
    108.             currentSpeed = walkSpeed;
    109.             isRunning = false;
    110.         }
    111.    
    112.     }
    113.     private void FixedUpdate()
    114.     {
    115.    
    116.         if (isAiming && playerStatScript.isBeingParalyzed != true)
    117.         {
    118.             // Debug.Log("walking");
    119.             float h = moveDirection.x;
    120.             float v = moveDirection.y;
    121.             Vector3 dir = new Vector3(h, 0, v).normalized;
    122.  
    123.             if (dir.magnitude > deadzone)
    124.             {
    125.                 currentSpeed = strifeSpeed;
    126.                 float targetAngle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg + camera.eulerAngles.y;
    127.                 Vector3 moveDirection = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
    128.                 player.velocity = moveDirection.normalized * currentSpeed *Time.deltaTime;            
    129.             }
    130.             else player.velocity = Vector3.zero;
    131.         }
    132.  
    133.         else if(isAiming != true && playerStatScript.isBeingParalyzed != true)
    134.         {
    135.             float h = moveDirection.x;
    136.             float v = moveDirection.y;
    137.             Vector3 dir = new Vector3(h, 0, v).normalized;
    138.  
    139.             if (dir.magnitude > deadzone)
    140.             {
    141.                 float targetAngle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg + camera.eulerAngles.y;
    142.                 float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, turnSmoothTime);
    143.                 transform.rotation = Quaternion.Euler(0f, angle, 0f);
    144.                 Vector3 moveDirection = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
    145.                 player.velocity = moveDirection.normalized * currentSpeed * Time.deltaTime;
    146.             }
    147.  
    148.             else
    149.             {
    150.                 player.velocity = Vector3.zero;
    151.             }
    152.  
    153.         }
    154.  
    155.         if (walkSpeed <= 75)
    156.         {
    157.             walkSpeed = 75;
    158.         }
    159.  
    160.         if(runSpeed <=75)
    161.         {
    162.             runSpeed = 75;
    163.         }
    164.  
    165.         if(strifeSpeed <= 75)
    166.         {
    167.             strifeSpeed = 75;
    168.         }
    169.  
    170.     }
    171.  
    172.     void Reload()
    173.     {
    174.         if(isAiming)
    175.         {
    176.             combatScript.Reload();
    177.         }
    178.     }
    179.  
    180.     void Quickturn()
    181.     {
    182.         quickturnStartingValue = horizontal;
    183.         quickturnProgress = 0;
    184.  
    185.     }
    186.  
    187.     void Aim()
    188.     {
    189.         if(hasWeaponEquipped)
    190.         {
    191.             //Debug.Log("setting rotation");
    192.             standardCamLocation = camera.transform.localPosition;
    193.             isAiming = true;
    194.             isRunning = false;
    195.             currentPlayerRotation = transform.rotation;
    196.             reticle.SetActive(true);
    197.         }
    198.  
    199.     }
    200.  
    201.  
    202.     void Revert()
    203.     {
    204.         playerControls.Combat.Aim.Enable();
    205.         isAiming = false;
    206.         isUnaiming = true;
    207.         currentSpeed = walkSpeed;
    208.         if(reticle != null)
    209.         {
    210.             reticle.SetActive(false); // comes up as null for soem reason in new room at first?
    211.         }
    212.         currentPlayerRotation = transform.localRotation; //<---- this is where the null reference points to. If I get rid of this, the error doesn't happen for "PlayerControls"
    213.  
    214.     }
    215.  
    216.     private void Update()
    217.     {
    218.         if (isAiming)
    219.         {
    220.             if (reorientProgress < 1)
    221.             {
    222.                 reorientProgress += Time.deltaTime * reorientSpeed;
    223.                 transform.rotation = Quaternion.Slerp(currentPlayerRotation, targetAimRotation, reorientProgress);            
    224.                 camera.transform.localPosition = Vector3.Lerp(standardCamLocation, aimingTransform.localPosition, reorientProgress);
    225.  
    226.             }
    227.  
    228.             else if(reorientProgress >= 1)
    229.             {
    230.                 transform.rotation = targetAimRotation;
    231.                 reorientProgress = 1;
    232.             }
    233.         }
    234.  
    235.         else if(isUnaiming)
    236.         {
    237.             if (reorientProgress > 0)
    238.             {
    239.  
    240.                 reorientProgress -= Time.deltaTime * reorientSpeed;
    241.                 //turns camera
    242.                 transform.rotation = Quaternion.Slerp(Quaternion.Euler(0, targetAimRotation.eulerAngles.y, targetAimRotation.eulerAngles.z), currentPlayerRotation, reorientProgress);
    243.                 //turns player
    244.                 camera.transform.localPosition = Vector3.Lerp(standardCamLocation, aimingTransform.localPosition, reorientProgress);
    245.  
    246.             }
    247.  
    248.             else if (reorientProgress <= 0)
    249.             {
    250.                 reorientProgress = 0;
    251.                 isUnaiming = false;
    252.             }
    253.         }
    254.  
    255.         if(quickturnProgress < 1)
    256.         {
    257.             quickturnProgress += Time.deltaTime * reorientSpeed;
    258.             horizontal = Mathf.Lerp(quickturnStartingValue, quickturnStartingValue + 180, quickturnProgress);
    259.             transform.rotation = Quaternion.Euler(transform.rotation.eulerAngles.x, horizontal, transform.rotation.z);
    260.         }
    261.  
    262.     }
    263.     private void LateUpdate()
    264.     {
    265.         CamControl();
    266.         CameraOcclusionandCollisionDetection();
    267.     }
    268.  
    269.     void CamControl()
    270.     {
    271.         if(playerControls.General.Look.enabled)
    272.         {
    273.             horizontal += lookDirection.x * rotationSpeed * Time.deltaTime;
    274.             vertical -= lookDirection.y * rotationSpeed * Time.deltaTime;
    275.             vertical = Mathf.Clamp(vertical, minimumX, maximumX);
    276.             camera.transform.LookAt(focalPoint);
    277.             cameraAnchor.rotation = Quaternion.Euler(vertical, horizontal, 0);
    278.             if (isAiming)
    279.             {
    280.                 targetAimRotation = Quaternion.Euler(vertical, horizontal, 0);
    281.  
    282.             }
    283.         }
    284.      
    285.     }
    286.  
    287.     void CameraOcclusionandCollisionDetection()
    288.     {
    289.         RaycastHit hit;
    290.  
    291.         if (Physics.Linecast(cameraAnchor.transform.position, desiredCameraPosition.position, out hit))
    292.         {
    293.             if (hit.collider.gameObject.tag != "Player")
    294.             {
    295.                 camera.transform.position = hit.point;
    296.             }
    297.  
    298.             if (camera.transform.position != desiredCameraPosition.position)
    299.             {
    300.                 RaycastHit occludedHit;
    301.  
    302.                 if (Physics.Linecast(desiredCameraPosition.position, cameraAnchor.position, out occludedHit))
    303.                 {
    304.                     if (occludedHit.transform.gameObject.tag == "Player")
    305.                     {
    306.                         return;
    307.                     }
    308.                 }
    309.  
    310.             }
    311.         }
    312.  
    313.         else if (isAiming != true)
    314.         {
    315.             camera.transform.position = desiredCameraPosition.position;
    316.         }
    317.  
    318.     }
    319.  
    320.     private void OnDisable()
    321.     {
    322.       playerControls.Disable();
    323.     }
    324. }
     
  4. Do you make this object "DontDestroyOnLoad"? Because what you're experiencing looks like you are double-registering.
    This thing:
    playerControls.General.Move.performed +=
    called event registration. You need to unregister when you are done. I usually advise against leaving like this even if you need it for the entire application lifespan. It is just bad habit. So what you can do is to unregister from these events
    [whatever event].performed -=
    pattern. It is important to unregister the same methods what you have registered. And I advise that way because if you change your mind later and destroy the object early, you're leaking memory.
    More info here:
    https://docs.microsoft.com/en-us/do...w-to-subscribe-to-and-unsubscribe-from-events
     
  5. brandonschlosser101

    brandonschlosser101

    Joined:
    Apr 19, 2018
    Posts:
    15
    Yeah its on the Player which is not destroyed on load.

    So to unregister, I just put the same thing but -= and right before I load the scene or do I put it also in the variable space?

    Thanks for your help! I've never used delegates before so learning the new input system has been a journey!
     
  6. I usually register onEnable and unregister on onDisable. Also make sure you don't create another player object when you load the next scene.
     
    brandonschlosser101 likes this.
  7. brandonschlosser101

    brandonschlosser101

    Joined:
    Apr 19, 2018
    Posts:
    15
    Yup that's what the issue was. I feel silly. I had the player prefab in every room and forgot to check for and destroy copies.

    Plus I de-registered.

    Thanks so much!!!
     
    Lurking-Ninja likes this.
  8. X3doll

    X3doll

    Joined:
    Apr 15, 2020
    Posts:
    34
    Sorry but, do you manage to use one player input component per scene? I struggle to understand what happen when you have to spread this component into multiple scenes.

    As i know from the documentation: 1 player input component = 1 user = 1 different control scheme

    If i had one player input component per scene, i still have this issue?
     
  9. Twelv445

    Twelv445

    Joined:
    Feb 22, 2013
    Posts:
    32
    Apologies for reviving a thread that's over a year old, but since the issue wasn't fully resolved and hasn't been addressed in this manner before, I wanted to share a solution for those who might still be encountering this problem or stumble upon this post in search of answers.

    The persistent event subscriptions, despite efforts to unsubscribe, particularly in the context of Unity's scene management and C#'s handling of lambda expressions for event subscription and unsubscription, are due to lambda expressions creating a new delegate instance each time they are used. Therefore, attempting to unsubscribe using a lambda expression does not match the delegate instance you originally subscribed with, leaving the original subscription active.

    To resolve this, it's essential to use a consistent delegate instance for both subscribing and unsubscribing from events. This consistency can be achieved by either defining methods directly or using actions as intermediary variables. Here's how you can adjust your code to ensure proper subscription management and why you should use OnEnable() and OnDisable() for this purpose:


    Avoid subscribing with lambda expressions like this:
    Code (CSharp):
    1. private void Awake()
    2. {
    3.     playerControls.Combat.Reload.performed += context => Reload();
    4. }
    5.  
    6. private void Reload()
    7. {
    8.     // Reload code here
    9. }

    Instead, subscribe by directly referencing a method in OnEnable() and unsubscribe in OnDisable():
    Code (CSharp):
    1. private void OnEnable()
    2. {
    3.     playerControls.Combat.Reload.performed += Reload;
    4. }
    5.  
    6. private void OnDisable()
    7. {
    8.     playerControls.Combat.Reload.performed -= Reload;
    9. }
    10.  
    11. private void Reload(InputAction.CallbackContext context)
    12. {
    13.     // Reload code here
    14. }
    Using OnEnable() for subscriptions and OnDisable() for unsubscriptions is crucial because Awake() is only called when the script instance is loaded, which doesn't account for objects being enabled or disabled throughout the scene's lifecycle. OnEnable() is called every time the object becomes active in the scene, making it the ideal place to set up event subscriptions. Similarly, OnDisable() is called when the object becomes inactive, allowing you to clean up subscriptions and prevent actions from being triggered when they shouldn't be, such as after an object is destroyed or a scene is unloaded.

    This approach ensures your event handlers are correctly managed, avoiding issues such as duplicate event firing and enhancing the robustness of your event handling logic, especially in complex Unity projects with multiple scene loads and object state changes.