Search Unity

Resolved How to Fix Issue with GameManager and OnCollisionEnter2D functions?

Discussion in 'Getting Started' started by ryanwiseman, Jan 15, 2024.

  1. ryanwiseman

    ryanwiseman

    Joined:
    Jan 4, 2024
    Posts:
    10
    Hello! I'm a bit new to Unity, so I've been doing my best to solve problems without going to the forums for every little issue. There is one bug that I just cannot figure out how to get past.

    While trying to create a heart management system, the majority of code already reaches proper breakpoint conditions (in the debugger) and does as it should, which means my heart management system in about 70% of the way there. However, the same OnCollisionEnter2D script that works in sections like LivesManager and PlayerMovement, does not work in the GameManager, which means my array for managing hearts does not work. Since my array is in Update and GameManager, having the OnCollisionEnter2D working there is essential. Would anyone have a good fix for this problem that matches the syntax I have going? Everything is rather simple for the code that supports this function, which I'll document briefly below in two parts:

    Part 1:
    For PlayerMovement, the function that exists for OnCollisionEnter2D is a valid breakpoint when lives are lost so when I run into a trap, the debugger is happy:

    Code (CSharp):
    1. public class PlayerMovement : MonoBehaviour
    2. {
    3.     private Rigidbody2D rb;
    4.     private BoxCollider2D coll;
    5.     private SpriteRenderer sprite;
    6.     private Animator anim;
    7.  
    8.     private GameManager _theGM;
    9.     public LivesManager _theLM;
    10.  
    11.     public int defaultLives;
    12.     public int livesCounter;
    13.  
    14.     [SerializeField] private LayerMask jumpableGround;
    15.  
    16.     private float dirX = 0f;
    17.     [SerializeField] private float moveSpeed = 7f;
    18.     [SerializeField] private float jumpForce = 14f;
    19.  
    20.     private enum MovementState { idle, running, jumping, falling }
    21.  
    22.     [SerializeField] private AudioSource jumpSoundEffect;
    23.  
    24.     private void Start()
    25.     {
    26.         _theLM = FindObjectOfType<LivesManager>();
    27.         livesCounter = defaultLives;
    28.         rb = GetComponent<Rigidbody2D>();
    29.         coll = GetComponent<BoxCollider2D>();
    30.         sprite = GetComponent<SpriteRenderer>();
    31.         anim = GetComponent<Animator>();
    32.     }
    33.  
    34.     private void Update()
    35.     {
    36.         dirX = Input.GetAxisRaw("Horizontal");
    37.         rb.velocity = new Vector2(dirX * moveSpeed, rb.velocity.y);
    38.  
    39.         if (Input.GetButtonDown("Jump") && IsGrounded())
    40.         {
    41.             jumpSoundEffect.Play();
    42.             rb.velocity = new Vector2(rb.velocity.x, jumpForce);
    43.         }
    44.  
    45.      UpdateAnimationState();
    46.  
    47.     }
    48.  
    49.  
    50.  
    51.     private void UpdateAnimationState()
    52.     {
    53.         MovementState state;
    54.  
    55.              if (dirX > 0f)
    56.         {
    57.             state = MovementState.running;
    58.             sprite.flipX = false;
    59.         }
    60.         else if (dirX < 0f)
    61.         {
    62.             state = MovementState.running;
    63.             sprite.flipX = true;
    64.         }
    65.         else
    66.         {
    67.             state = MovementState.idle;
    68.         }
    69.  
    70.  
    71.         if (rb.velocity.y > .1f)
    72.         {
    73.             state = MovementState.jumping;
    74.         }
    75.         else if (rb.velocity.y < -.1f)
    76.         {
    77.             state = MovementState.falling;
    78.         }
    79.  
    80.         anim.SetInteger("state", (int)state);
    81.     }
    82.  
    83.     private bool IsGrounded()
    84.         {
    85.             return Physics2D.BoxCast(coll.bounds.center, coll.bounds.size, 0f, Vector2.down, .1f, jumpableGround);
    86.         }
    87.  
    88.         private void OnCollisionEnter2D(Collision2D collision)
    89.         {
    90.             if(collision.gameObject.tag == "Trap")
    91.             {
    92.                 GameManager.health -=1;
    93.                 _theLM.TakeLife();
    94.             }
    95.      
    96.         }
    97. }
    Part 2:
    My LivesManager should be setup correctly (everything debugs as it should), which already takes into account whenever the player runs into "Trap" tagged objects; so Collision is functioning perfectly. You can see my syntax approach best, here:

    Code (CSharp):
    1. public class LivesManager : MonoBehaviour
    2. {
    3.     public int defaultLives;
    4.     public int livesCounter;
    5.     public GameObject heart0, heart1, heart2, heart3;
    6.  
    7.     public Text livesText;
    8.  
    9.     [SerializeField] private GameManager _theGM;
    10.     [SerializeField] LivesManager _theLM;
    11.  
    12.     [SerializeField] private Text LivesCounterText;
    13.     [SerializeField] private GameObject LivesSprite;
    14.  
    15.     void Start()
    16.     {
    17.       livesCounter = defaultLives;
    18.  
    19.       _theGM = FindObjectOfType<GameManager>();
    20.     }
    21.  
    22.     void Update()
    23.     {
    24.         livesText.text = "Hearts " + livesCounter;
    25.  
    26.         if(livesCounter <1)
    27.         {
    28.             _theGM.GameOver();
    29.         }
    30.     }
    31.  
    32.     public void TakeLife()
    33.     {
    34.         livesCounter--;
    35.     }
    36.  
    37.  
    38. }
    The GameManager Problem
    Here's where the problem is, and it is only part of the script that is the culprit. After testing all my code to make sure it is working as it should, it all leads back to an issue in GameManager. Because the OnCollisionEnter2D is not initializing, the array cannot be updated properly. This makes the hearts reset from 3, back to 4 after the game resets.

    Code (CSharp):
    1. public class GameManager : MonoBehaviour
    2. {
    3.     public List<GameObject> HeartUiObjects = new();
    4.     public int defaultLives;
    5.     public int livesCounter;
    6.     public PlayerMovement thePlayer;
    7.     private Vector2 playerStart;
    8.  
    9.     public GameObject gameOverScreen;
    10.     public GameObject victoryScreen;
    11.  
    12.     public static int health;
    13.     private GameManager _theGM;
    14.     public LivesManager _theLM;
    15.  
    16.     void Start()
    17.     {
    18.         _theLM = FindObjectOfType<LivesManager>();
    19.         livesCounter = defaultLives;
    20.         defaultLives = health;
    21.         playerStart = thePlayer.transform.position;
    22.         health = 4;
    23.         gameOverScreen.gameObject.SetActive(false);
    24.  
    25.     }
    26.  
    27.     void Update()
    28.     {
    29.  
    30.         // In this loop, we expect HeartUiObjects to have a count of 4.
    31.         // Thus, i will start at 0 and iterate { 0, 1, 2, 3 }
    32.         for(int i = 0; i < HeartUiObjects.Count; i++)
    33.         {
    34.             // if we don't add 1 to i we will see an off-by-one error
    35.             if(health < i + 1)
    36.             {
    37.          
    38.                 HeartUiObjects[i].SetActive(false);
    39.                 victoryScreen.gameObject.SetActive(false);
    40.          
    41.          
    42.             }
    43.             else
    44.             {
    45.                 HeartUiObjects[i].SetActive(true);
    46.                 victoryScreen.gameObject.SetActive(false);
    47.             }
    48.         }
    49.  
    50.         if(health <= 0)
    51.         {
    52.             gameOverScreen.gameObject.SetActive(true);
    53.             victoryScreen.gameObject.SetActive(false);
    54.             Time.timeScale = 0;
    55.         }
    56.  
    57.  
    58.     }
    59.  
    60.         private void OnCollisionEnter2D(Collision2D collision)
    61.         {
    62.             if(collision.gameObject.tag == "Trap")
    63.             {
    64.                 GameManager.health -=1;
    65.                 _theLM.TakeLife();
    66.             }
    67.  
    68.         }
    69.  
    70.     public void Victory()
    71.     {
    72.         victoryScreen.SetActive(true);
    73.         thePlayer.gameObject.SetActive(false);
    74.         gameOverScreen.SetActive(false);
    75.     }
    76.  
    77.     public void GameOver()
    78.     {
    79.         victoryScreen.SetActive(false);
    80.         gameOverScreen.SetActive(true);
    81.         thePlayer.gameObject.SetActive(false);
    82.     }
    83.  
    84.     public void Reset()
    85.     {
    86.         victoryScreen.SetActive(false);
    87.         gameOverScreen.SetActive(false);
    88.         thePlayer.gameObject.SetActive(true);
    89.         thePlayer.transform.position = playerStart;
    90.     }
    91. }
    The issue may be that Collision needs to reference "The Player" and "Trap" tag so that they can collide and it registers, but I don't know how I'd go about doing that? Everything else but this area (OnCollision2D for GameManager) runs smoothly, so I'd like to avoid rewriting a lot of this code if possible. I am rather new, so I'm still learning exactly how to setup objects to interact with each other correctly.

    Any help would be appreciated!
     
    Last edited: Jan 15, 2024
  2. DrDemencio

    DrDemencio

    Joined:
    Sep 2, 2022
    Posts:
    62
    I assume GameManager is not a component of the same object as PlayerMovement belongs to, when that object collides only PlayerMovement.OnCollisionEnter2D will be called. You could just call _theGM.OnCollisionEnter2D (although it should be public and provably have a different name) from PlayerMovement.OnCollisionEnter2D. I see you're not initializing PlayerMovement._theGM though, so you should do it like in LivesManager.Start. Or just configure it from the editor (previously tagging it with [SerializeField] like in LivesManager so it's listed in the inspector).
     
  3. ryanwiseman

    ryanwiseman

    Joined:
    Jan 4, 2024
    Posts:
    10

    How would you go about naming the OnCollisionEnter2D so that it has a different naming convention compared to the traditional 'private void OnCollision2D' naming structure? Could you provide an example of how that code would look? Naming conventions are something I'm still getting used to, but seeing it correct will help with me applying it in future situations. That right there would ensure organization and making sure I don't modify the overall OnCollision2D when it gets called more and more across objects (since the goal is to have these values public)

    I have set up the GameManager (in PlayerMovement) to be a SerializeField and assigned it to the GameManager, (and I'll be getting some sleep too because that will help with project perspective too) so that should also ensure everything works on top of the other recommendations.
     
  4. DrDemencio

    DrDemencio

    Joined:
    Sep 2, 2022
    Posts:
    62
    Naming conventions are always a sticky subject since everyone has their own definition of intuitive. Given the function body I'd move the trap tag comparison to the caller and just rename it to TakeDamage (and pass the amount of health to subtract by parameter). So for PlayerMovement:
    Code (CSharp):
    1. private void OnCollisionEnter2D(Collision2D collision)
    2. {
    3.     if(collision.CompareTag("Trap")
    4.    {
    5.         _theGM.TakeDamage(1);
    6.    }
    7. }
    And for GameManager:
    Code (CSharp):
    1. public void TakeDamage(int healthTaken)
    2. {
    3.     health -=healthTaken;
    4.     _theLM.TakeLife();
    5. }
    Although I can see your current PlayerMovement.OnCollisionEnter2D is already identical to GameManager.OnCollisionEnter2D, so even if the later is not called you should already be getting the desired functionality. Is this a temporal workaround? Is GameManager.Update getting called?
    Also, I'd refrain from exposing GameManager.health as a public static member.

    EDIT: Hmmm... on closer inspection I'm not really sure this is a good approach at all. Most likely you want PlayerManager to call _theLM.TakeLife and, inside that method notify GameManager what's the new health. Also, I'm not sure if I misunderstood, but aren't GameManager.health and LivesManager.livesCounter the same thing?
     
    Last edited: Jan 15, 2024
  5. ryanwiseman

    ryanwiseman

    Joined:
    Jan 4, 2024
    Posts:
    10

    Based on the initial tutorial I watched (which can be where a lot of bad code is written) it was done so that if values update in one, you can have it reflected in the other script. That may not be most optimal, which I'll optimize better once I nail fixing this bug. Good thing with the GameManager, it does a valid breakpoint now, which will help with what my plans are with the array.

    My approach to separating the array and theTakeDamage function is for the sake of me separating the array and OnCollision2D since the collision wouldn't debug, meaning that if I were to throw it into the invalid breakpoint function, nothing would happen. The array and OnCollision will be merged soon, and that should help with the array updating since it'll be in a statement that is called only when necessary (instead of me having to fiddle with Update and a function I only want to call once). I take this would manage the array at least so the values update?
     
  6. ryanwiseman

    ryanwiseman

    Joined:
    Jan 4, 2024
    Posts:
    10
    The problem now with my approach is that there are several terms not defined. For example, our newly created TakeDamage function lacks any definition, so when it gets referenced, it has nothing to modify the array, hence the lack of hearts decreasing but then increasing again. That means it is getting called, it just lacks a definition. In the Game Manager (and potentially for the other areas, in case it is different), could you possibly give an example on how to set that up? Once I understand how to get a definition to these objects, the issue should hopefully be resolved!
     
  7. DrDemencio

    DrDemencio

    Joined:
    Sep 2, 2022
    Posts:
    62
    I'm deeply sorry @ryanwiseman but I don't understand what you mean by not defined. I'm more used to C++ than C#, but I don't think it's possible to declare a method in C# without defining it (that is, writing the method body). If you try to call an undefined method it should simply not compile at all.

    I completely agree with you setting-up your hearts visibility in an Update is not the best idea. In fact, I think you could simply change in your GameManager
    void Update()
    to something like
    public void OnLivesChanged(int health)
    and call it from LivesManager.TakeLife. That way you can get rid of GameManager.health and remove the risk of introducing data redundancy errors.
    So in the end things would look like this:
    * PlayerMovement:
    Code (CSharp):
    1. private void OnCollisionEnter2D(Collision2D collision)
    2. {
    3.     if(collision.CompareTag("Trap")
    4.    {
    5.         _theLM.TakeLife();
    6.    }
    7. }
    * LifeManager:
    Code (CSharp):
    1. public void TakeLife()
    2. {
    3.     livesCounter--;
    4.     livesText.text = "Hearts " + livesCounter;
    5.     _theGM.OnLifeChanged(livesCounter);
    6. }
    * GameManager:
    Code (CSharp):
    1. public void OnLifeChanged(int health)
    2. {
    3.     //Basically the same code currently in Update
    4. }
    Now you can also get rid of LivesManager.Update, as GameManager.OnLivesChanged already checks the game over condition.

    On the other hand, regarding this sentence in your first post: "This makes the hearts reset from 3, back to 4 after the game resets." What do you mean by "after the game resets"? Do you mean after stopping and restarting the game? If that's the case, it's completely normal it gets reset, as there is no data that survives between sessions. If that's what you want you'll need to use some kind of persistence mechanism. I'm sorry I can't help you much in that regard as I don't yet know what functionality does Unity provide for that.
     
    Last edited: Jan 16, 2024
  8. ryanwiseman

    ryanwiseman

    Joined:
    Jan 4, 2024
    Posts:
    10
    Good news, the bug is 90% resolved, I finally was able to get things to reference as they should, it's just this small little area. I was trying to understand how to assign a definition to a function, since a few of mine had none. Idk if you use VS/VSCode, but you can right click and select Go To Definition on any function or term that is available. It's one of the greatest tools I've just started taking advantage of. For any function that lacks a definition, means that if you apply the function to another function, nothing will happen.

    So the problem was that functions like TakeDamage, when you pulled the definition, lacked one (usually you want it paired to something like GameManager). In VSCode, you can right click and have an option of "Go To Definition". I was able to get TakeDamage to finally be defined, which now, if I run into hearts, I get the hearts to go down to 3, but a Game Over screen happens. Without TakeDamage defined, nothing in the array would update.

    The GameManager.OnLivesChanged was not needed. However, if I do not have LiveManager.Update, it doesn't manage my hearts at all, so we still would want that.

    The current problem is that when I run into spikes, my health drops to 3 permanently (which is good) and gives me a Game Over screen. This is because GameOver isn't defined in the LivesManager. Any idea of what would be best to do so?

    I'll play around with this code more today, but so far, I've narrowed down the bug to this area alone. Once I get this finally resolved, I will post the very specific code needed, in case someone stumbles upon this thread with the same problem.

    Here's what my current setup looks like:
    PlayerMovement
    Code (CSharp):
    1. public class PlayerMovement : MonoBehaviour
    2. {
    3.     private Rigidbody2D rb;
    4.     private BoxCollider2D coll;
    5.     private SpriteRenderer sprite;
    6.     private Animator anim;
    7.  
    8.     [SerializeField] GameManager _theGM;
    9.     public LivesManager _theLM;
    10.  
    11.     public int defaultLives;
    12.     public int livesCounter;
    13.     public int TakeLife;
    14.     public int TakeDamage;
    15.  
    16.     [SerializeField] private LayerMask jumpableGround;
    17.  
    18.     private float dirX = 0f;
    19.     [SerializeField] private float moveSpeed = 7f;
    20.     [SerializeField] private float jumpForce = 14f;
    21.  
    22.     private enum MovementState { idle, running, jumping, falling }
    23.  
    24.     [SerializeField] private AudioSource jumpSoundEffect;
    25.  
    26.     private void Start()
    27.     {
    28.         _theLM = FindObjectOfType<LivesManager>();
    29.         _theGM = FindObjectOfType<GameManager>();
    30.         livesCounter = defaultLives;
    31.         rb = GetComponent<Rigidbody2D>();
    32.         coll = GetComponent<BoxCollider2D>();
    33.         sprite = GetComponent<SpriteRenderer>();
    34.         anim = GetComponent<Animator>();
    35.     }
    36.  
    37.     private void Update()
    38.     {
    39.         dirX = Input.GetAxisRaw("Horizontal");
    40.         rb.velocity = new Vector2(dirX * moveSpeed, rb.velocity.y);
    41.  
    42.         if (Input.GetButtonDown("Jump") && IsGrounded())
    43.         {
    44.             jumpSoundEffect.Play();
    45.             rb.velocity = new Vector2(rb.velocity.x, jumpForce);
    46.         }
    47.  
    48.      UpdateAnimationState();
    49.  
    50.  
    51.     }
    52.  
    53.  
    54.  
    55.     private void UpdateAnimationState()
    56.     {
    57.         MovementState state;
    58.  
    59.              if (dirX > 0f)
    60.         {
    61.             state = MovementState.running;
    62.             sprite.flipX = false;
    63.         }
    64.         else if (dirX < 0f)
    65.         {
    66.             state = MovementState.running;
    67.             sprite.flipX = true;
    68.         }
    69.         else
    70.         {
    71.             state = MovementState.idle;
    72.         }
    73.  
    74.  
    75.         if (rb.velocity.y > .1f)
    76.         {
    77.             state = MovementState.jumping;
    78.         }
    79.         else if (rb.velocity.y < -.1f)
    80.         {
    81.             state = MovementState.falling;
    82.         }
    83.  
    84.         anim.SetInteger("state", (int)state);
    85.     }
    86.     private bool IsGrounded()
    87.         {
    88.             return Physics2D.BoxCast(coll.bounds.center, coll.bounds.size, 0f, Vector2.down, .1f, jumpableGround);
    89.         }
    90.  
    91.         private void OnCollisionEnter2D(Collision2D collision)
    92.         {
    93.             if(collision.gameObject.tag == "Trap")
    94.             {
    95.                 _theGM.TakeDamage(1);
    96.                 _theLM.TakeLife();
    97.             }
    98.    
    99.         }
    100. }
    LivesManager
    Code (CSharp):
    1. public class LivesManager : MonoBehaviour
    2. {
    3.     public int defaultLives;
    4.     public int livesCounter;
    5.     public GameObject heart0, heart1, heart2, heart3;
    6.     public GameObject gameOverScreen;
    7.     public Text livesText;
    8.  
    9.     [SerializeField] private GameManager _theGM;
    10.     [SerializeField] LivesManager _theLM;
    11.     [SerializeField] private Text LivesCounterText;
    12.     [SerializeField] private GameObject LivesSprite;
    13.  
    14.  
    15.     void Start()
    16.     {
    17.       livesCounter = defaultLives;
    18.       _theGM = FindObjectOfType<GameManager>();
    19.     }
    20.  
    21.  
    22.     void Update()
    23.     {
    24.         livesText.text = "Hearts " + livesCounter;
    25.  
    26.         if(livesCounter <1)
    27.         {
    28.             _theGM.GameOver();
    29.         }
    30.     }
    31.  
    32.     public void TakeLife()
    33.     {
    34.         livesCounter--;
    35.  
    36.     }
    37.  
    38.  
    39. }
    GameManager
    Code (CSharp):
    1. public class GameManager : MonoBehaviour
    2. {
    3.     public List<GameObject> HeartUiObjects = new();
    4.     public int defaultLives;
    5.     public int livesCounter;
    6.     public PlayerMovement thePlayer;
    7.     private Vector2 playerStart;
    8.  
    9.     public GameObject gameOverScreen;
    10.     public GameObject victoryScreen;
    11.  
    12.     public static int health;
    13.     private GameManager _theGM;
    14.     public LivesManager _theLM;
    15.  
    16.     void Start()
    17.     {
    18.         _theLM = FindObjectOfType<LivesManager>();
    19.         livesCounter = _theLM.livesCounter;
    20.         livesCounter = defaultLives;
    21.         defaultLives = health;
    22.         playerStart = thePlayer.transform.position;
    23.         gameOverScreen.gameObject.SetActive(false);
    24.         victoryScreen.gameObject.SetActive(false);
    25.     }
    26.  
    27.  
    28.     public void TakeDamage(int healthTaken)
    29.     {
    30.         health -=healthTaken;
    31.    
    32.         for(int i = 0; i < HeartUiObjects.Count; i++)
    33.         {
    34.             {
    35.                 HeartUiObjects[i].SetActive(false);
    36.                 gameOverScreen.gameObject.SetActive(false);
    37.                 victoryScreen.gameObject.SetActive(false);
    38.             }
    39.             else
    40.             {
    41.                 HeartUiObjects[i].SetActive(false);
    42.                 gameOverScreen.gameObject.SetActive(false);
    43.                 victoryScreen.gameObject.SetActive(false);
    44.             }
    45.         }
    46.  
    47.         if(health <= 0)
    48.         {
    49.             gameOverScreen.gameObject.SetActive(true);
    50.             victoryScreen.gameObject.SetActive(false);
    51.             Time.timeScale = 0;
    52.         }
    53.     }
    54.  
    55.  
    56.  
    57.     public void Victory()
    58.     {
    59.         victoryScreen.SetActive(true);
    60.         thePlayer.gameObject.SetActive(false);
    61.         gameOverScreen.SetActive(false);
    62.     }
    63.  
    64.     public void GameOver()
    65.     {
    66.         victoryScreen.SetActive(false);
    67.         gameOverScreen.SetActive(true);
    68.         thePlayer.gameObject.SetActive(false);
    69.     }
    70.  
    71.     public void Reset()
    72.     {
    73.         victoryScreen.SetActive(false);
    74.         gameOverScreen.SetActive(false);
    75.         thePlayer.gameObject.SetActive(true);
    76.         thePlayer.transform.position = playerStart;
    77.     }
    78. }
    EDIT: There are actually a few areas that do not return to a valid breakpoint. Idk if you would be able to check the code to make sure I'm not running into anymore additional issues, but yeah, there are still some additional problems that will be bugging me (pun unintentional) until I finally resolve them
     
    Last edited: Jan 17, 2024
  9. DrDemencio

    DrDemencio

    Joined:
    Sep 2, 2022
    Posts:
    62
    The reason why it's giving you a game over screen after taking one hit it's because GameManager.health is never initialized and so it's already 0 from the beginning. Thus, when PlayerMovement.OnCollisionEnter2D calls _theGM.TakeDamage(1), GameManager.health becomes -1. Later down that method it checks if(health <= 0) and it passes, thus activating this the game over screen. This is the reason why I consider a bad idea having the lives counter repeated in LivesManager and GameManager.

    Yes, I use Visual Studio, but I'm not sure how it's possible your code compiled if TakeDamage was not defined. Unity should have given you an error message similar to this one (please disregard the PlayerController name):
    upload_2024-1-17_10-46-41.png
    And then something like this when you tried to run it:
    upload_2024-1-17_10-50-35.png
    As for method GameOver not being defined in LivesManager, I don't see the need. You're calling _theGM.GameOver(), and _theGM is a GameManager object.
     
  10. ryanwiseman

    ryanwiseman

    Joined:
    Jan 4, 2024
    Posts:
    10
    Idk why we would have different issues with definitions, maybe there's an issue with the inspector on your end? I also did some further defining by using the Inspector when I click on the script itself. That may help with the problem you are running into?

    The TakeDamage should be referencing the array, it does on my end. I'll post the three sections of code, hopefully you don't run into an issue with TakeDamage being not defined.

    After a bit of fiddling, I was able to get the default number of lives to 4 and decrease to 3 without the GameOver screen. In doing so, I managed to skip my death animation, while also forcing my player into a permanent static state. I don't know what is causing it, but the problem is almost fixed. Any idea of what I should do to get it to work?

    There will be a bit of code cleanup I do after getting this problem resolved, since there are better ways of writing it

    Player Manager
    Code (CSharp):
    1. public class PlayerMovement : MonoBehaviour
    2. {
    3.     private Rigidbody2D rb;
    4.     private BoxCollider2D coll;
    5.     private SpriteRenderer sprite;
    6.     private Animator anim;
    7.  
    8.     [SerializeField] GameManager _theGM;
    9.     public LivesManager _theLM;
    10.     [SerializeField] PlayerMovement _thePM;
    11.  
    12.     public PlayerMovement thePlayer;
    13.     private Vector2 playerStart;
    14.  
    15.     public int defaultLives;
    16.     public int livesCounter;
    17.     public int TakeLife;
    18.     public int TakeDamage;
    19.  
    20.     [SerializeField] private LayerMask jumpableGround;
    21.  
    22.     private float dirX = 0f;
    23.     [SerializeField] private float moveSpeed = 7f;
    24.     [SerializeField] private float jumpForce = 14f;
    25.  
    26.     private enum MovementState { idle, running, jumping, falling }
    27.    
    28.     [SerializeField] private AudioSource jumpSoundEffect;
    29.  
    30.     // Start is called before the first frame update
    31.     private void Start()
    32.     {
    33.         _theLM = FindObjectOfType<LivesManager>();
    34.         _theGM = FindObjectOfType<GameManager>();
    35.         _thePM = FindObjectOfType<PlayerMovement>();
    36.         livesCounter = defaultLives;
    37.         rb = GetComponent<Rigidbody2D>();
    38.         coll = GetComponent<BoxCollider2D>();
    39.         sprite = GetComponent<SpriteRenderer>();
    40.         anim = GetComponent<Animator>();
    41.         playerStart = transform.position;
    42.     }
    43.  
    44.     void Awake()
    45.     {
    46.         playerStart = transform.position;
    47.     }
    48.     // Update is called once per frame
    49.     public void Update()
    50.     {
    51.         dirX = Input.GetAxisRaw("Horizontal");
    52.         rb.velocity = new Vector2(dirX * moveSpeed, rb.velocity.y);
    53.  
    54.         if (Input.GetButtonDown("Jump") && IsGrounded())
    55.         {
    56.             jumpSoundEffect.Play();
    57.             rb.velocity = new Vector2(rb.velocity.x, jumpForce);
    58.         }
    59.    
    60.      UpdateAnimationState();
    61.    
    62.     }
    63.  
    64.     private void UpdateAnimationState()
    65.     {
    66.         MovementState state;
    67.  
    68.              if (dirX > 0f)
    69.         {
    70.             state = MovementState.running;
    71.             sprite.flipX = false;
    72.         }
    73.         else if (dirX < 0f)
    74.         {
    75.             state = MovementState.running;
    76.             sprite.flipX = true;
    77.         }
    78.         else
    79.         {
    80.             state = MovementState.idle;
    81.         }
    82.  
    83.  
    84.         if (rb.velocity.y > .1f)
    85.         {
    86.             state = MovementState.jumping;
    87.         }
    88.         else if (rb.velocity.y < -.1f)
    89.         {
    90.             state = MovementState.falling;
    91.         }
    92.  
    93.         anim.SetInteger("state", (int)state);
    94.     }
    95.     private bool IsGrounded()
    96.         {
    97.             return Physics2D.BoxCast(coll.bounds.center, coll.bounds.size, 0f, Vector2.down, .1f, jumpableGround);
    98.         }
    99.  
    100.         private void OnCollisionEnter2D(Collision2D collision)
    101.         {
    102.             if(collision.gameObject.tag == "Trap")
    103.             {
    104.                 _theGM.TakeDamage(1);
    105.                 _theLM.TakeLife();
    106.         }
    107.      }
    108.  
    109. }
    GameManager
    Code (CSharp):
    1. public class GameManager : MonoBehaviour
    2. {
    3.     public List<GameObject> HeartUiObjects = new();
    4.     //Public Int values can be helpful to the components that need defin
    5.    
    6.     public int defaultLives;
    7.     public int livesCounter;
    8.     public int victoryScreen;
    9.     public int gameOverScreen;
    10.     public PlayerMovement thePlayer;
    11.     private Vector2 playerStart;
    12.  
    13.     public static int health;
    14.     private GameManager _theGM;
    15.     public LivesManager _theLM;
    16.     public PlayerMovement _thePM;
    17.  
    18.     private Rigidbody2D rb;
    19.     private BoxCollider2D coll;
    20.     private Animator anim;
    21.  
    22.     void Start()
    23.     {
    24.         _theLM = FindObjectOfType<LivesManager>();
    25.         _theGM = FindObjectOfType<GameManager>();
    26.         _thePM = FindObjectOfType<PlayerMovement>();
    27.         livesCounter = _theLM.livesCounter;
    28.         defaultLives = health;
    29.         defaultLives = 4;
    30.         playerStart = thePlayer.transform.position;
    31.         rb = GetComponent<Rigidbody2D>();
    32.         coll = GetComponent<BoxCollider2D>();
    33.         anim = GetComponent<Animator>();
    34.        
    35.     }
    36.  
    37.     void Awake()
    38.     {
    39.         playerStart = transform.position;
    40.     }
    41.    
    42.     public void TakeDamage(int healthTaken)
    43.     {
    44.         health -=healthTaken;
    45.          // In this loop, we expect HeartUiObjects to have a count of 4.
    46.         // Thus, i will start at 0 and iterate { 0, 1, 2, 3 }
    47.        
    48.         for(int i = 0; i < HeartUiObjects.Count; i++)
    49.        
    50.         {
    51.             // if we don't add 1 to i we will see an off-by-one error
    52.             if(health < i + 1)
    53.             {
    54.                 HeartUiObjects[i].SetActive(false);
    55.             }
    56.             else
    57.             {
    58.                 HeartUiObjects[i].SetActive(false);
    59.             }
    60.             if (health <i - 1)
    61.             {
    62.                 thePlayer.gameObject.SetActive(true);
    63.                 thePlayer.transform.position = playerStart;
    64.             }
    65.         }
    66.  
    67.         if(health <= 0)
    68.         {
    69.             Time.timeScale = 0;
    70.             rb.bodyType = RigidbodyType2D.Static;
    71.         }
    72.     }
    73.  
    74.    
    75.  
    76.     public void Victory()
    77.     {
    78.         thePlayer.gameObject.SetActive(false);
    79.     }
    80.  
    81.     public void GameOver()
    82.     {
    83.         thePlayer.gameObject.SetActive(false);
    84.     }
    85.  
    86.     public void Reset()
    87.     {
    88.         thePlayer.gameObject.SetActive(true);
    89.         thePlayer.transform.position = playerStart;
    90.     }
    91.  
    92. }
    93.  
    LivesManager
    Code (CSharp):
    1. public class LivesManager : MonoBehaviour
    2. {
    3.     public int defaultLives;
    4.     public int livesCounter;
    5.     public int health;
    6.     public GameObject heart0, heart1, heart2, heart3;
    7.     public GameObject gameOverScreen;
    8.     public GameObject victoryScreen;
    9.     public PlayerMovement thePlayer;
    10.  
    11.     private Rigidbody2D rb;
    12.     private Animator anim;
    13.  
    14.     public Text livesText;
    15.  
    16.     [SerializeField] GameManager _theGM;
    17.     [SerializeField] LivesManager _theLM;
    18.     [SerializeField] PlayerMovement _thePM;
    19.  
    20.  
    21.     [SerializeField] private Text LivesCounterText;
    22.     [SerializeField] private GameObject LivesSprite;
    23.  
    24.    
    25.  
    26.     void Start()
    27.     {
    28.         rb = GetComponent<Rigidbody2D>();
    29.         anim = GetComponent<Animator>();
    30.         health = 4;
    31.         defaultLives = 4;
    32.         livesCounter = _theLM.livesCounter;
    33.         defaultLives = health;
    34.         _theGM = FindObjectOfType<GameManager>();
    35.         _theLM = FindObjectOfType<LivesManager>();
    36.         _thePM = FindObjectOfType<PlayerMovement>();
    37.         victoryScreen.gameObject.SetActive(false);
    38.         gameOverScreen.SetActive(false);
    39.  
    40.          
    41.     }
    42.  
    43.     // Update is called once per frame
    44.     void Update()
    45.     {
    46.         livesText.text = "Hearts " + health;
    47.         if (health <=1)
    48.         thePlayer.gameObject.SetActive(true);
    49.         if(health <=0)
    50.         {
    51.            gameOverScreen.SetActive(true);
    52.            thePlayer.gameObject.SetActive(false);
    53.         }
    54.     }
    55.  
    56.  
    57.  
    58.     public void TakeLife()
    59.     {
    60.         health--;
    61.         thePlayer.gameObject.SetActive(true);
    62.  
    63.     }
    64.  
    65.     private void RestartLevel()
    66.     {
    67.         SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    68.     }
    69.    
    70. }
    71.  
    The only thing not needed is my definition of PlayerMovement _thePM. I was trying to use parts of that script to fix issues with the GameManager and LivesManager, but that failed.

    You'd think this system (hearts counter) would be easy to implement, turns out, far from that.
     
  11. ryanwiseman

    ryanwiseman

    Joined:
    Jan 4, 2024
    Posts:
    10
    @DrDemencio

    Luckily, there is no issue anymore with anything related to Colliders, so I decided to make this thread resolved. There still is a bug, but the extent to this issue is small, and would be best served under a different topic.
     
    DrDemencio likes this.