HELP NEEDED; Found a small bug, but can fix what seems to be simple

Hi all,

First off, I’m a graphic designer, and not a coding expert. I’ve tried my best to figure out what I’m sure is a “simple” fix for any of you out there that know coding vs me, who can do things visually, but back-end stuff is not my skill.

The issue I’m having is this:
When a character dies in my game, if they die by being hit, everything functions properly, but if they die by any other means (burning, choking, anything non-contact) they still die, but the “death” animation doesn’t activate and they essentially stand idle. So then what happens is, once the character is back in free-roam, if they are hit by something (even if their health is refilled to the max), the player essentially freezes there until they are hit again.

I believe what’s happening is the coding is telling the player they’re dead, but because the player doesn’t follow through with the death animation and it recognizes that although there was no contact, the player has in fact died, the game is thinking the first hit after re-spawn is killing the player, when in fact they’ve already died.

I’ve attached the two scripts that interact with each other and if you take a look at the “deathtracker” script I believe whatever needs to be added for the characters non-contact death should be on line 104. The “player controller” script is where the dead/killable bool options for dying/hitting are.

If anyone can help, I would greatly appreciate it!!

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using System;

public class DeathTracker : MonoBehaviour
{
    public static DeathTracker _instance;

    public static DeathTracker instance {
        get {
            if (_instance == null) {
                _instance = FindObjectOfType<DeathTracker> ();
            }
            return _instance;
        }
        set {
            _instance = value;
        }
    }

    void Awake ()
    {
        if (_instance == null) {
            _instance = this;
        }
    }


    public Transform NewGameSpawn, ContinueSpawn;

    public void MovePlayer ()
    {
      
    }

    private bool dead = false;

    public void PlayerDeath ()
    {
        StartCoroutine (PlayerDeath2 ());
    }

    public void MissionDeath ()
    {
        StartCoroutine (MissionDeath2 ());
    }

    public IEnumerator MissionDeath2 ()
    {
        if (!dead) {
            dead = true;
            var player = (GMRewrite.currentCharacter as PlayerController);
            player.canMove = false;

            if (!CameraRewrite.instance.GetCamera.orthographic) {
                CameraRewrite.instance.distanceUp = 7.5f;
                CameraRewrite.instance.cameraMaxDistance = 25f;
            }

            yield return new WaitForSeconds (2);
            Fader.instance.Fade (Color.black, 1, false, () => {
            });
            yield return new WaitForEndOfFrame ();
            while (Fader.status != Fader.FaderStatus.ENDFADE) {  
                yield return null;
            }

            CameraRewrite.instance.distanceUp = 1f;
            CameraRewrite.instance.cameraMaxDistance = 10f;
            GMRewrite.currentMission.ExitToFreeroam (false, false);
            Fader.instance.Fade (Color.black, 1, true, () => {
            });

            yield return new WaitForEndOfFrame ();
            while (Fader.status != Fader.FaderStatus.ENDFADE) {  
                yield return null;
            }

            while (CenterScreenText.showing) {
                yield return null;
            }

            if (!GMRewrite.tutorialHealthLost) {
                GMRewrite.tutorialHealthLost = true;
                DialogueComic.instance.DisplayDialogue (DEBUG_GlobalUpdate.instance.healthLostDialogue, 0, () => {
                    player.canMove = true;
                });
            } else {
                player.canMove = true;
            }

            dead = false;
        }
    }

    public IEnumerator PlayerDeath2 ()
    {
        if (!dead) {
            dead = true;
            var player = GMRewrite.currentCharacter as PlayerController;
            player.canMove = false;
            //SOMETHING SHOULD GO HERE THAT ACTIVATES DEAD ANIMATION AND/OR RECOGNITION OF 0 HEALTH FROM PLAYER CONTROLLER


            CameraRewrite.instance.distanceUp = 7.5f;
            CameraRewrite.instance.cameraMaxDistance = 25f;
            yield return new WaitForSeconds (2f);
            yield return Fader.instance.Fade (Color.black, 1, false, () => {

            });

            player.CancelEverything (false);
            player.transform.position = ContinueSpawn.position;
            player.transform.rotation = ContinueSpawn.rotation;
            CameraRewrite.instance.distanceUp = 1f;
            CameraRewrite.instance.cameraMaxDistance = 10f;

            yield return Fader.instance.Fade (Color.black, 1, true, () => {
              
            });

            if (!GMRewrite.tutorialHealthLost) {
                GMRewrite.tutorialHealthLost = true;
                DialogueComic.instance.DisplayDialogue (DEBUG_GlobalUpdate.instance.healthLostDialogue, 0, () => {
                    player.canMove = true;
                });
            } else {
                player.canMove = true;
            }

            dead = false;
        }
    }
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using System;

public class PlayerController : Entity
{
    public float animJumpHeight = 3f;
    public bool hit = false, landing = false;
    //    public int heightIndex = 20;
    public float sniffMoveRate;
    public ParticleSystem digParticles;
    public GameObject sniff;
    public Transform sniffTarget, sniffZone;
  
    private Vector3 defaultCenter;

    public AudioClip barkClip;

    void Start ()
    {
        movement = MovementBase.CreateJoystick ();
        defaultCenter = ccont.center;
//        aud = gameObject.GetComponent<AudioSource> ();
    }

    public bool killable;
  
    // Update is called once per frame
    public override void UpdateEntity ()
    {
//        Debug.Log ("Scene count: " + SceneManager.sceneCount);
        if (killable) {
            if (Health.currentValue <= 0) {
                stayDead = true;
                if (GMRewrite.currentMission) {
                    DeathTracker.instance.MissionDeath ();
                } else {
                    DeathTracker.instance.PlayerDeath ();
                }
                killable = false;
            }
        }


        if (!killable && Health.currentValue > 0) {
            killable = true;
        }

        if (!GMRewrite.tutorialStaminaLost) {
            if (Stamina.currentValue <= 0) {
                if (GMRewrite.currentMission == null) {
                    bool comic = true;
                    if (ComicCutsceneManager.instance) {
                        comic = !ComicCutsceneManager.instance.comicMan.canvas.gameObject.activeSelf;
                    }
                    bool dialogue = !DialogueComic.instance.headLeft.gameObject.activeSelf;
                    bool status = Fader.status != Fader.FaderStatus.FADING;
//                    Debug.Log (comic + " " + dialogue + " " + status);
                    if (comic && status && dialogue) {
                        DialogueComic.instance.DisplayDialogue (DEBUG_GlobalUpdate.instance.staminaLostDialogue, 0, () => {
                        });
                        GMRewrite.tutorialStaminaLost = true;
                    }
                }
            }
        }

        if (!inAir) {
            if (landing)
//                AudMgr.PlaySound (AudMgr.instance.landing);
            landing = false;
        } else {
            landing = true;
        }

        if (hit) {
          
        }
        if (anim.GetBool ("Sniff")) {
            if (sniffTarget) {
                sniff.SetActive (true);
                sniff.transform.SetParent (sniffTarget);
//                print ("sniffDistance " + Vector3.Distance (sniff.transform.position, sniffTarget.position));
                if (Vector3.Distance (sniff.transform.position, sniffTarget.position) > 1) {
//                    print ("close");
                    Vector3 moveDirection = (sniffTarget.position - sniff.transform.position);
                    moveDirection.Normalize ();
                    print ("move " + moveDirection);
                    sniff.GetComponent<CharacterController> ().SimpleMove (moveDirection * sniffMoveRate);
                } else {
                    sniff.transform.position = transform.position;
                }
            } else if (sniffZone) {
                InputController.instance.FPLook ();
            }
        } else {
            sniff.SetActive (false);
            sniff.transform.SetParent (transform);
            sniff.transform.position = transform.position;
        }

        try {
            movement.Move (this);
        } catch (NullReferenceException e) {
            Debug.LogWarning ("NPC IMovement field is Null\n" + e.Message);
        }
      
        UpdateCContPosition ();
//        RefillTracker.instance.RefillUpdate ();
    }

    internal void FallOver()
    {
        throw new NotImplementedException();
    }

    private void UpdateCContPosition ()
    {
        float addy = anim.GetFloat ("AdditionalY");
        ccont.center = defaultCenter + Vector3.up * addy * animJumpHeight;
    }

    void LateUpdate ()
    {
        Animate ();
    }

    private void Animate ()
    {
        anim.SetBool ("inAir", inAir);
        float normalWalk = (Speed.currentValue / Speed.maxValue);
        float normalSprint = (Mathf.Max (0, Speed.currentValue - Speed.maxValue) / Sprint.maxValue);
        float speed = Mathf.Clamp (normalWalk, -1, 1) + normalSprint;

        anim.SetBool ("Sprint", sprinting);
        anim.SetFloat ("Speed", speed);
    }

    public bool barking{ get; set; }

    public bool barkTick = false;

    public override void Bark ()
    {
        StartCoroutine (bark ());
    }

    IEnumerator bark ()
    {
        barking = true;
        anim.SetTrigger ("Barking");
        yield return new WaitForSeconds (barkAnimOffsets.x);
        AudMgr.PlaySFX (barkClip);
        if (StaticM8Reference.isMission8) {
            yield return new WaitForSeconds (0.25f);
            barkTick = true;
            StaticM8Reference.mission8.BlastCone.PlayBlast ();
        }
//        yield return new WaitForSeconds (barkAnimDelay);
        yield return new WaitForSeconds (barkAnimOffsets.y);
        AudMgr.PlaySFX (barkClip);
        barkTick = true;
        yield return null;
        barkTick = false;
        barking = false;
    }

    public bool digging{ get; set; }

    public bool digTick = false;
  
    public float particleSpeed;

    public static float[] burstDelay2 = new float[] {
        1f + (15f / 30f),
        (15f / 30f),
        (15f / 30f),
        (10f / 30f),
        (7f / 30f),
        (4f / 30f),
        (4f / 30f),
        (6f / 30f),
    };

    public void Dig ()
    {
//        StartCoroutine (dig ());
        StartCoroutine (perfectDig ());
    }


    IEnumerator perfectDig ()
    {
        canMove = false;
        digging = true;
        anim.SetBool ("Digging", digging);
        int count = burstDelay2.Length;
        var emission = digParticles.emission;
        for (int i = 0; i < count; i++) {
            digTick = false;
            yield return new WaitForSeconds (burstDelay2 [i]);
            if (i == count - 1) {
//                Debug.Log ("Dig Tick");
                digParticles.Emit (300);
                digTick = true;
            } else {
//                Debug.Log ("Regular Dig");
                digParticles.Emit (100);
            }
        }
        yield return new WaitForSeconds (20f / 30f);
        digTick = false;
        digging = false;
        anim.SetBool ("Digging", digging);
        if ((GMRewrite.currentCharacter as PlayerController).sniffTarget)
            (GMRewrite.currentCharacter as PlayerController).sniffTarget.GetComponent<MeshRenderer> ().enabled = true;
        canMove = true;
    }

    void OnTriggerEnter (Collider other)
    {
        if ((other.gameObject.CompareTag ("Projectile") || other.gameObject.CompareTag ("Enemy"))) {
            Vector3 v = other.transform.position - transform.position;
            int dir = Vector3.Cross (transform.forward, v).y > 0 ? 1 : -1;
            FallOver (dir);
        }

        if ((other.gameObject.layer == LayerMask.NameToLayer ("Water"))) {
//            if (!inWater) {
//                AudMgr.PlaySFX (AudMgr.instance.jumpWater);
//            }
            inWater = true;
        }
    }

    public void FallOver (int dir)
    {
        if (!hit) {
            hit = true;
            StartCoroutine (GetHit (dir, true));
        }
    }

    private bool stayDead{ get; set; }

    public static PlayerController _IEnumerator;
    public static PlayerController IEnumerator
    {
        get
        {
            if (_IEnumerator == null)
            {
                _IEnumerator = FindObjectOfType<PlayerController>();
            }
            return _IEnumerator;
        }
        set
        {
            _IEnumerator = value;
        }
    }

    IEnumerator GetHit (int dir, bool reset)
    {  
        Health.currentValue = Mathf.Clamp (Health.currentValue - 1, 0, Health.maxValue);
        bool dead = Health.currentValue <= 0;
        if (!dead || !killable) {
            string side = dir > 0 ? "TakeDamageR" : "TakeDamageL";
            anim.SetTrigger (side);
            iOSHapticFeedback.Instance.Trigger(iOSHapticFeedback.iOSFeedbackType.ImpactHeavy);
        } else {
            string side = dir > 0 ? "DeadRight" : "DeadLeft";
            anim.SetBool (side, true);
            iOSHapticFeedback.Instance.Trigger(iOSHapticFeedback.iOSFeedbackType.Failure);
        }

        AudMgr.PlaySFX (AudMgr.instance.getHit);
      
        canMove = false;
        yield return new WaitForSeconds (1.8f);
        if (!stayDead) {
            CancelEverything (true);
        }
        yield return new WaitForEndOfFrame ();
        stayDead = false;
        yield return new WaitForSeconds (1.5f);
        hit = false;
    }

    private bool inWater = false;

    public override void Footstep ()
    {
        if (inWater) {
            inWater = false;
            aud.clip = AudMgr.instance.WalkWater;
            aud.volume = 0.5f;
            aud.pitch = 1 + (UnityEngine.Random.value - 0.5f) * 0.25f;
            aud.Play ();
            return;
        }

        aud.volume = 1f;
        Ray ray = new Ray (GMRewrite.currentCharacter.transform.position, Vector3.down);
        RaycastHit hit;
        if (Physics.Raycast (ray, out hit)) {
            if (hit.collider.CompareTag ("Terrain")) {
                aud.volume = 0.5f;
                aud.clip = AudMgr.instance.WalkGrass;
            } else if (hit.collider.CompareTag ("Wood")) {
                aud.clip = AudMgr.instance.WalkWood;
            } else {
                aud.clip = AudMgr.instance.WalkConcrete;
            }
        }
        aud.pitch = 1 + (UnityEngine.Random.value - 0.5f);
        aud.Play ();
    }

    public Transform LeftHeadIKTarget;
    public Transform RightHeadIKTarget;

    public void GetPet (float duration, float speed)
    {
        StartCoroutine (pet (duration, speed));
    }

    private IEnumerator pet (float duration, float speed)
    {
        Vector3 iL = LeftHeadIKTarget.position;
        Vector3 iR = RightHeadIKTarget.position;
        Vector3 head = iL + (iR - iL) / 2;

        float a = 0;
        while (a < duration) {
            a += Time.deltaTime;

            Vector3 dL = iL - head;
            Vector3 dR = iR - head;

            float sinTime = Mathf.Sin (a * speed);

            Vector3 refL = Vector3.Cross (Vector3.up, dL) * 27.5f;
            Vector3 refR = Vector3.Cross (Vector3.up, dR) * 27.5f;

            float time = (1 + sinTime) / 2f;
            dL = Quaternion.Lerp (Quaternion.Euler (refL), Quaternion.Euler (-refL), time) * dL;
            dR = Quaternion.Lerp (Quaternion.Euler (-refR), Quaternion.Euler (refR), time) * dR;

            Debug.DrawRay (head, dL);
            Debug.DrawRay (head, dR);

            LeftHeadIKTarget.position = head + dL;
            RightHeadIKTarget.position = head + dR;

            yield return null;
        }

        LeftHeadIKTarget.position = iL;
        RightHeadIKTarget.position = iR;
        yield return null;
    }

    public bool rolling = false;

    public void Roll ()
    {
        if (!canMove || !anim.GetCurrentAnimatorStateInfo (0).IsName ("idle") || (GMRewrite._controlMethod == (int)CMRewrite.LOOK)) {
            return;
        }

        anim.SetTrigger ("Trick");
        StartCoroutine (Rolling ());
    }

    IEnumerator Rolling ()
    {
        GMRewrite.currentCharacter.canMove = false;
        rolling = true;
        yield return new WaitForSeconds (2);
        rolling = false;
        GMRewrite.currentCharacter.canMove = true;
    }

    public void CancelEverything (bool canMove)
    {
        StopAllCoroutines ();
        anim.SetBool ("Digging", false);
        anim.SetBool ("DeadLeft", false);
        anim.SetBool ("DeadRight", false);
        anim.SetBool ("Barking", false);
        anim.SetBool ("Sniff", false);
        anim.ResetTrigger ("Trick");
        anim.SetTrigger ("ResetAnimations");
        digParticles.emissionRate = 0;
        digging = false;
        rolling = false;
        hit = false;
        reverse = walking = running = sprinting = false;
        Stamina.currentValue = Mathf.Floor (Stamina.currentValue / Stamina.statValue) * Stamina.statValue;
        this.canMove = canMove;
    }
}

8287725–1086231–DeathTracker.cs (3.04 KB)
8287725–1086234–PlayerController.cs (9.93 KB)

Here is how to instrument your code to find out what is happening.

You must find a way to get the information you need in order to reason about what the problem is.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as Debug.Log("Problem!",this);

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: How To - Capturing Device Logs on iOS or this answer for Android: How To - Capturing Device Logs on Android

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

Hi, thanks for all that info. So basically, I’ve done the majority of all that which is how I’ve narrowed it down to where the code needs to go, however, since I can’t code myself (being that I’m a writer + designer) I was looking for help from someone that understands coding and can look at both those scripts and help me fill in line 104 in the deathtracker script with what will activate the player death animation and/or the system recognizes players health is at 0 and is !killable from the player controller script.

The death animation doesn’t play if the character is burning, there are several possibilities.

You have one place in the code that sets the death animation that I can see. This is line 275 in the second listing that you posted. The first thing I would check is- Does line 275 ever execute if the player dies from burning? You can determine this through Debug.Log. If line 275 is never called, then that would explain why the death animation never plays, and then you’d need to determine why.

Other possibilities:

  • When you call anim.SetBool (side, true); the animation will not play if the player’s animation controller is in a state that cannot transition to the death animation state. In that case the animation will not play.
  • When the player is in the death animation state, is it possible there is a condition that is causing the animator controller to exit that state before the animation plays?
  • Is it possible the animation state is getting set to the death animation over and over every frame? That would mean that the animation will always start over and never advance past the first frame.
1 Like

Hi @kdgalla - Thank you for getting back to me! So as far as I can tell through bug testing, when the player is burning or choking, the animation never gets called/activated. My thought was, in the player controller on line 40, it’s calling for information from the deathtracker script to determine if/how the player has died. My assumption was that if within “public IEnumerator PlayerDeath2 ()” (which is line 98 in “deathtracker”) there was something that activated the death animation, it would essentially act like the death was from contact and function as it should.
https://www.youtube.com/watch?v=a2Gwn8XQKNA

So for bug testing purposes, I can press the “F” key and make the character die from non-contact and you’ll see when the green bar is depleted and has zero life, the camera functions as it should, but there is no animation of death. After respawn, when he gets knocked down again he can’t move until he gets hit again, but if they die from contact, you’ll see how everything functions as normal. Also, not sure if it helps, but I’ve added the fire damage script as well.

using UnityEngine;
using System.Collections;

public class fireDamage : MonoBehaviour
{
    float burnTimer = 1;
    bool burning = false;

    void OnTriggerStay (Collider col)
    {
        if (col.gameObject == GMRewrite.currentCharacter.gameObject) {
//            if (GMRewrite.currentCharacter.Health.currentValue <= 3) {
//                if (GMRewrite.currentMission) {
//                    GMRewrite.canChangeControl = false;
//                }
//            }
            if (GMRewrite.currentCharacter.Health.currentValue > 0) {
                if (!burning) {
                    Burn ();
                }
            }
        }
    }

    void OnDisable ()
    {
        burning = false;
        if (InputController.instance) {
            if (InputController.instance.Burning) {
                InputController.instance.Burning.SetActive (false);
            }
        }
    }

    void Burn ()
    {
        StartCoroutine (burn ());
    }

    IEnumerator burn ()
    {
        burning = true;
        InputController.instance.Burning.SetActive (true);
        GMRewrite.currentCharacter.Health.currentValue--;
        yield return new WaitForSeconds (burnTimer);
        InputController.instance.Burning.SetActive (false);
        burning = false;
    }
}

@kdgalla any ideas, based on the sample video above?

I can’t think of anything other than what I already mentioned.

Look at the animator controller in Unity’s “animator” window while the game is running. Find out if it’s entering the correct death state. If not, what state is it in? is there a transition to the death state? If so what, conditions need to be met to follow that transition. Are you setting these conditions in the animator controller? If you can see what’s going on, then you can fix it.

just bumping this thread in case anyone has any ideas

Anyone?

Someone replied, you never acknowledged their last reply. It’s likely nobody will given that. Devs can be fussy jumping in on someones unfinished conversation. :wink:

Just saying.