I'm using a lightning GameObject prefab to have a visual effect when i'm firing my weapon. When I fire, I enable that lightning object and I have a generic component that deactivates it after a certain delay.
The problem is that the "should wait" log is never reached, and instead of waiting the set delay, it waits much longer and doesn't actually deactivate the GameObject.
Here's the code for the DeactivateAfter component
public class DestroyAfter : MonoBehaviour {
[SerializeField]
private float delay;
private bool firstRun = true;
void OnEnable () {
if (firstRun == false) {
StartCoroutine(DestroyMethod());
}
firstRun = false;
}
public IEnumerator DestroyMethod() {
Debug.LogFormat("Should wait; Time: {0}", Time.time);
yield return new WaitForSeconds(delay);
Debug.LogFormat("Should deactivate; Time: {0}", Time.time);
gameObject.SetActive(false);
}
}
The condition never be true, you need to set firstRun condition to true first.
private bool firstRun = **true**;
void OnEnable () {
if (firstRun == **true**) {
StartCoroutine(DestroyMethod());
}
firstRun = false;
}
And i ever like to set flag first and later do what you want:
private bool firstRun = **true**;
void OnEnable () {
if (firstRun == **true**) {
firstRun = false;
StartCoroutine(DestroyMethod());
}
}
I think you should use particle system for your weapon fire. Anyway I think your code is not working because you are deactivating the game object instead of deactivating the component. Activate and deactivate your component using something like this :
gameObject.GetComponent<YourComponent>().enabled = false;
First Solution - Instantiate and Destroy with delay
If I understood you correctly the lightning GameObject is already a prefab you instantiate when firing and you want to destroy it after a delay.
So instead of a Coroutine solution and enabling or disabling the Object, this could be way simpler done using instantiate and destroy after as your script is actually called:
GameObject obj = GameObject.Instantiate(yourPrefab, position, rotation);
GameObject.Destroy(obj, delay);
This function can be called from anywhere as long as you have yourGameObject provided to the calling class. So you don't even need an own class for this.
The second float parameter is a delay in seconds (see API)
For example you could have a shooting class like:
public class Shooting : MonoBehavior
{
[SerializeField]
private GameObject lightningPrefab;
[SerializeField]
private float delay;
public void OnShooting(Vector3 position, Quaternion rotation){
GameObject obj = GameObject.Instantiate(lightningPrefab, position, rotation);
/* Directly after instantiating the Object
* you can already "mark" it to be destroyed after x seconds
* so you don't have to store it as variable anywhere */
GameObject.Destroy(obj, delay);
}
}
You then also could add some check to not constantly spawn the prefab:
[...]
private GameObject lightningObject;
public void OnShooting(Vector3 position, Quaternion rotation){
if(lightningObject == null ){
lightningObject = GameObject.Instantiate(lightningPrefab, position, rotation);
GameObject.Destroy(lightningObject, delay);
}
}
On this way there is always only one of your lightning objects a time.
Second Solution - Timeout
Another way without having to instantiate and destroy the object all the time would be a simple timeout instead of the Coroutine:
public class Shooting : MonoBehavior
{
[Tooltip("The lightning Object")]
[SerializeField]
private GameObject lightningObject;
[Tooltip("The time in sec the lightningObject stays visible")]
[SerializeField]
private float visibleDelay;
/* track the time that passed since the last OnShooting call */
private float timePassed;
/* Additional bool to not perform Update when not needed */
private bool isVisible;
/* Update is called once each frame */
private void Update(){
/* If the object is not visible do nothing */
if (isVisible != true) return;
/* update the timePassed */
timePassed += Time.deltaTime;
/* if delay passed since last OnShooting call
* deactiavte the object */
if (timePassed >= visibleDelay){
lightningObject.SetActive(false);
}
}
public void OnShooting(){
/* Activate the Object */
lightningObject.SetActive(true);
/* set isVisible */
isVisible = true;
/* reset the timePassed */
timePassed = 0;
}
}
Hmm. Your code works fine in my project.
What i'm doing, some object have your script and I deactivate and then activate the gameObject when scene is playing.
Your Destroy Method is public.
Check your project, maybe somewhere calls StopAllCoroutines() or StopCoroutine(DestroyMethod());
Related
So I am using two push buttons (connected to an Arduino Uno) as an input to my game. The player has to push down both buttons at the same time for the character to move in the game. I want the player to hold down the buttons for a different amount of time in each level. I have a working Arduino and a working Unity timer and player script, but am not able to get the code to do what I want. What I basically want is that only when the player presses the buttons down, does the timer start counting down. Right now, the timer starts as soon as the scene begins. I know that I somehow have to reference the timer script to the button object, I have tried this but it still doesn't work. Note that the timer UI does have a Timer tag on it. I have also referenced the Player Controller script in the Timer script. Right now, Its giving me a range of errors. I have attached an image depicting these errors.error image
The Timer script:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
//int startTime = 0;
public bool buttonPressed = false;
public int timeLeft;
public Text countdownText;
GameObject Character;
void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
//StartCoroutine("LoseTime");
BeginTimer();
}
void Update()
{
countdownText.text = ("Time Left = " + timeLeft);
if (timeLeft <= 0)
{
//StopCoroutine("LoseTime");
//countdownText.text = "Times Up!";
Invoke("ChangeLevel", 0.1f);
}
}
public void BeginTimer()
{
Character.GetComponent<PlayerController>().Update();
//gameObject.GetComponent<MyScript2>().MyFunction();
if (buttonPressed == true )
{
StartCoroutine("LoseTime");
}
else if (buttonPressed == false)
{
StopCoroutine("LoseTime");
}
}
IEnumerator LoseTime()
{
while (true)
{
yield return new WaitForSeconds(1);
timeLeft--;
}
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
The Player Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
public class PlayerController : MonoBehaviour
{
SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
//player == GameObject.FindWithTag("Player").GetComponent<>();
public float Speed;
public Vector2 height;
public float xMin, xMax, yMin, yMax;
public bool buttonPressed = false;
GameObject Character;
public void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
if (!sp.IsOpen)
{ // If the erial port is not open
sp.Open(); // Open
}
sp.ReadTimeout = 1; // Timeout for reading
}
public void Update()
{
if (sp.IsOpen)
{ // Check to see if the serial port is open
try
{
string value = sp.ReadLine();//To("Button"); //Read the information
int button = int.Parse(value);
//float amount = float.Parse(value);
//transform.Translate(Speed * Time.deltaTime, 0f, 0f); //walk
if (button == 0) //*Input.GetKeyDown(KeyCode.Space*/) //jump
{
buttonPressed = true;
Character.GetComponent<Rigidbody2D>().AddForce(height, ForceMode2D.Impulse);
Character.GetComponent<Rigidbody2D>().position = new Vector3
(
Mathf.Clamp(GetComponent<Rigidbody2D>().position.x, xMin, xMax),
Mathf.Clamp(GetComponent<Rigidbody2D>().position.y, yMin, yMax)
);
Timer tmr = GameObject.Find("Timer").GetComponent<Timer>();
tmr.BeginTimer();
}
}
catch (System.Exception)
{
}
}
void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
}
I think the problem may be with how I am referencing the scripts in each other.
In your timer you have a quite strange mixup of Update and Coroutine. Also note that BeginTimer is called exactly once! You also shouldn't "manually" call Update of another component.
I wouldn't use Update at all here. Simply start and stop a Coroutine.
The Timer script should only do the countdown. It doesn't have to know more:
public class Timer : MonoBehaviour
{
public int timeLeft;
public Text countdownText;
private bool timerStarted;
public void BeginTimer(int seconds)
{
// Here you have to decide whether you want to restart a timer
timeLeft = seconds;
// or if you rather want to continue counting down
//if(!timerStarted) timeLeft = seconds;
StartCoroutine(LoseTime());
}
public void StopTimer()
{
StopAllCoroutines();
}
private IEnumerator LoseTime()
{
timerStarted = true;
while (timeLeft > 0)
{
yield return new WaitForSeconds(1);
timeLeft --;
countdownText.text = $"Time Left = {timeLeft}";
}
// Only reached after the timer finished and wasn't interrupted meanwhile
// Using Invoke here is a very good idea since we don't want to interrupt anymore
// if the user lets go of the button(s) now
Invoke(nameof(ChangeLevel), 0.1f);
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
In general avoid to use Find at all. If anyhow possible already reference things in the Inspector! If needed you can use Find but only once! What you never want to do is use any of the Find and GetComponent variants repeatedly - rather store the reference the first time and re-use it - and especially not in Update no a per frame basis. They are very expensive calls!
public class PlayerController : MonoBehaviour
{
public float Speed;
public Vector2 height;
// I prefer to use Vector2 for such things
public Vector2 Min;
public Vector2 Max;
public bool buttonPressed = false;
// Already reference these via the Inspector if possible!
public Rigidbody2D Character;
public Timer timer;
public Rigidbody2D _rigidbody;
private SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
private void Awake()
{
FetchReferences();
}
// This is my "secret" tip for you! Go to the component in the Inspector
// open the ContextMenu and hit FetchReferences
// This already stores the references in the according fields ;)
[ContextMenu("FetchReferences")]
private void FetchReferences()
{
if(!Character)Character = GameObject.FindWithTag("Player"). GetComponent<Rigidbody2D>();
if(!timer) timer = GameObject.Find("Timer").GetComponent<Timer>();
}
private void Start()
{
if (!sp.IsOpen)
{
sp.Open(); // Open
}
sp.ReadTimeout = 1;
}
private void Update()
{
// I wouldn't do the serialport open check here
// your if block simply silently hides the fact that your application
// doesn't work correctly! Rather throw an error!
try
{
string value = sp.ReadLine(); //Read the information
int button = int.Parse(value);
//TODO: Since it isn't clear in your question how you get TWO buttons
//TODO: You will have to change this condition in order to only fire if both
//TODO: buttons are currently pressed!
buttonPressed = button == 0;
if (buttonPressed)
{
Character.AddForce(height, ForceMode2D.Impulse);
// The clamping of a rigidbody should always be done ine FixedUpdate!
// Pass in how many seconds as parameter or make the method
// parameterless and configure a fixed duration via the Inspector of the Timer
timer.BeginTimer(3.0f);
}
else
{
// Maybe stop the timer if condition is not fulfilled ?
timer.StopTimer();
}
}
catch (System.Exception)
{
// You should do something here! At least a Log ...
}
}
private void FixedUpdate()
{
// Here I wasn't sure: Are there actually two different
// Rigidbody2D involved? I would assume you rather wanted to use the Character rigidbody again!
Character.position = new Vector3(Mathf.Clamp(Character.position.x, Min.x, Max.x), Mathf.Clamp(Character.position.y, Min.y, Max.y));
}
// Did you mean OnApplicationQuit here?
private void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
Typed on smartphone but I hope the idea gets clear
I have came across similar problem which few people already posted on the forum. I checked the exisiting posts but still not couldn't rectify the issue. I have cube in my program which hits the floor when program starts. However, I found that onCollisionEnter is not being called when cube hits the floor. Rigidbody is attached to the cube. Could anyone guide me where am I making mistake?
Code of cube is:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class cube : MonoBehaviour
{
public float speed = 3.5f;
public float jumpingforce = 10f;
private bool canjump = false;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if(Input.GetKey("right"))
{
transform.position += Vector3.right * speed * Time.deltaTime;
}
if(Input.GetKey("left"))
{
transform.position += Vector3.left * speed * Time.deltaTime;
}
if(Input.GetKeyDown("space"))
{
GetComponent<Rigidbody>().AddForce(0,jumpingforce,0);
}
}
void onCollisionEnter(Collision collision)
{
Debug.Log("checking collision....");
}
}
First of all OnCollisionEnter needs to start (as btw any method and class name should in c#) with a capital O .. otherwise Unity will not recognize it and never call it. Unity calls these methods (like also Awake, Start, Update) as "Messages". If they do not exactly match the name they are never found and never called.
Some more things:
Do not call GetComponent repeatedly. It is quite expensive! Rather store the reference once and re-use it.
Then whenever a Rigidbody is involved you should not update it's transformations using the Transform component! This breaks the Physics! Rather use Rigidbody.MovePosition within FixedUpdate
Small Sidenote: Remove any empty Unity message method like Start, Awake, Update. Even if they are empty they are called by Unity as message only causing unnecessary overhead.
So you should change your code to something like
public class cube : MonoBehaviour
{
public float speed = 3.5f;
public float jumpingforce = 10f;
private bool canjump = false;
// you can also already reference this via the Inspector
// then it skips the GetComponnet call
[SerializeField] privte Rigidbody rigidbody;
private void Awake()
{
if(!rigidbody) rigidbody = GetComponent<Rigidbody>()
}
private void FixedUpdate()
{
// Getting this input in FixedUpdate is ok since it is a continues call
if(Input.GetKey("right"))
{
rigidbdy.MovePosition(rigidbody.position + Vector3.right * speed * Time.deltaTime);
}
if(Input.GetKey("left"))
{
rigidbdy.MovePosition(rigidbody.position + Vector3.left * speed * Time.deltaTime);
}
}
private void Update()
{
// this one-time input event has to be checked in Update / frame-based
// since it is true only for one frame and FixedUpdate might not be called during that frame
if(Input.GetKeyDown("space"))
{
// also it's ok calling this in Update since it is a one-time event
// and unlikely to happen twice until FixedUpdate is called again
rigidbody.AddForce(0,jumpingforce,0);
}
}
private void OnCollisionEnter(Collision collision)
{
Debug.Log("checking collision....");
}
}
I have built this 2D project in unity where you tap on blocks and they destroy using the onMouseDown() function. My problem is after tapping a block and it destroys, how can I make the player wait for a certain amount of time before he can tap on another block in the game. I have tried using PlayerPrefs and subtracting Time.deltaTime from a certain float variable but it did not work.
Note: all the blocks share the same destroy script!!!
float waitTime = 1.5f;
static float lastClickTime = float.NegativeInfinity;
void OnMouseDown ()
{
float time = Time.time;
if( time > ( lastClickTime + waitTime ) )
{
lastClickTime = time;
DestroyThisBlock();
}
}
If they all share the same script, you can start with defining a static bool variable and a static event, say:
public static bool isLockedDown = false;
public static event Action onBlockDestroyed;
Then, on destruction function, first keep a check about this locked down. If this is false, then destroy, turn the lock to true, and invoke the static listener that'll be read by another script, which in turn will start a coroutine that'll turn this static lock to false after given set of seconds.
public class Block : MonoBehaviour {
void DestroyBlock()
{
if(isLockedDown)
return;
isLockedDown = true;
onBlockDestroyed.Invoke();
////destroy block///
}
}
public classBlockManager : MonoBehaviour {
void Awake()
{
Block.onBlockDestroyed += BeginUnlocking
}
void BeginUnlocking()
{
StartCoroutine(UnlockTimer);
}
IEnumerator UnlockTimer()
{
yield return new WaitForSeconds(1f);
BLock.isLockedDown = false;
}
}
(This is a 2D project)
I have a jumping issue where if my character WALKS into an X platform, she won't jump, but when she JUMPS ONTO the X platform, she can perform a jump.
For the platforms I am currently using 2 Box Collider 2Ds (one with "is trigger" checked)
For the character I am currently using 2 Box Collider 2Ds (one with "is trigger" checked) and Rigidbody 2D.
Below is the code for jumping and grounded I am currently trying to use.
{
public float Speed;
public float Jump;
bool grounded = false;
void Start()
{
}
void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
if (grounded)
{
GetComponent<Rigidbody2D>().velocity = new Vector2(GetComponent<Rigidbody2D>().velocity.x, Jump);
}
}
}
void OnTriggerEnter2D()
{
grounded = true;
}
void OnTriggerExit2D()
{
grounded = false;
}
}
Issue arises on the same part of every platform. (Each square represents a single platform sprite and they have all the same exact characteristics, since I copy pasted each one of them). Please check the photo on this link: https://imgur.com/a/vTmHw
It happens because your squares have seperate colliders. Imagine this:
There are two blocks : A and B. You are standing on block A. Now you try to walk on block B. As soon as your Rigidbody2D collider touches block B, your character gets an event OnTriggerEnter2D(...). Now you claim, that you are grounded.
However, at this moment you are still colliding with block A. As soon as your Rigidbody2D no longer collides with block A, your character receives OnTriggerExit2D(...). Now you claim, that you are no longer grounded. But in fact, you are still colliding with block B.
Solution
Instead of having bool variable for checking if grounded, you could have byte type variable, called collisionsCounter:
Once you enter a trigger - increase the counter.
Once you exit a trigger - decrease the counter.
Do some checking to make sure you are actually above the collider!
Now, once you need to check if your character is grounded, you can just use
if (collisionsCounter > 0)
{
// I am grounded, allow me to jump
}
EDIT
Actually, after investingating question further, I've realized that you have totally unnecessary colliders (I'm talking about the trigger ones). Remove those. Now you have only one collider per object. But to get the calls for collision, you need to change:
OnTriggerEnter2D(...) to OnCollisionEnter2D(Collision2D)
OnTriggerExit2D(...) to OnCollisionExit2D(Collision2D)
Final code
[RequireComponent(typeof(Rigidbody2D))]
public sealed class Character : MonoBehaviour
{
// A constant with tag name to prevent typos in code
private const string TagName_Platform = "Platform";
public float Speed;
public float Jump;
private Rigidbody2D myRigidbody;
private byte platformCollisions;
// Check if the player can jump
private bool CanJump
{
get { return platformCollisions > 0; }
}
// Called once the script is started
private void Start()
{
myRigidbody = GetComponent<Rigidbody2D>();
platformCollisions = 0;
}
// Called every frame
private void Update()
{
// // // // // // // // // // // // // //
// Need to check for horizontal movement
// // // // // // // // // // // // // //
// Trying to jump
if (Input.GetKeyDown(KeyDode.UpArrow) && CanJump == true)
Jump();
}
// Called once Rigidbody2D starts colliding with something
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.collider.tag == TagName_Platform)
platformCollisions++;
}
// Called once Rigidbody2D finishes colliding with something
private void OnCollisionExit2D(Collision2D collision)
{
if(collision.collider.tag == TagName_Platform)
platformCollisions--;
}
// Makes Character jump
private void Jump()
{
Vector2 velocity = myRigidbody.velocity;
velocity.y = Jump;
myRigidbody.velocity = velocity;
}
}
Here can be minor typos as all the code was typed inside Notepad...
I think there are a couple of issues here.
Firstly, using Triggers to check this type of collision is probably not the best way forward. I would suggested not using triggers, and instead using OnCollisionEnter2D(). Triggers just detect if the collision space of two objects has overlapped each other, whereas normal collisions collide against each otehr as if they were two solid objects. Seen as though you are detecting to see if you have landed on the floor, you don't want to fall through the floor like Triggers behave.
Second, I would suggest using AddForce instead of GetComponent<Rigidbody2D>().velocity.
Your final script could look like something like this:
public class PlayerController : MonoBehaviour
{
public float jumpForce = 10.0f;
public bool isGrounded;
Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void OnCollisionEnter2D(Collision2D other)
{
// If we have collided with the platform
if (other.gameObject.tag == "YourPlatformTag")
{
// Then we must be on the ground
isGrounded = true;
}
}
void Update()
{
// If we press space and we are on the ground
if(Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
// Add some force to our Rigidbody
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
// We have jumped, we are no longer on the ground
isGrounded = false;
}
}
}
Iam trying to load my "PlayerHealth" Script to my "GameOverManager" to check my currentHealth from the PlayerHealth-Script.
If the current health is "0" - I want to trigger an animation.
The problem is, that Unity gives me an error with following message:
"NullReferenceException: Object reference not set to an instance of an object
GameOverManager.Update () (at Assets/GameOverManager.cs:32)"
Here is the piece of Code of my GameOverManager:
public class GameOverManager : MonoBehaviour {
public PlayerHealth playerHealthScript;
public float restartDelay = 5f;
Animator anim;
float restartTimer;
private void Awake()
{
anim = GetComponent<Animator>();
}
private void Update()
{
playerHealthScript = GetComponent<PlayerHealth>();
if (playerHealthScript.currentHealth <= 0) {
anim.SetTrigger("GamerOver");
restartTimer += Time.deltaTime;
if (restartTimer >= restartDelay) {
SceneManager.LoadScene(2);
}
}
}
}
The error is triggered on the following line:
if (playerHealthScript.currentHealth <= 0)
Here is the hierarchy - FPSController holds "PlayerHealth" - HUDCanvas holds "GameOverManager:
Here are the inspectors:
Here is the code of "PlayerHealth":
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;
public class PlayerHealth : MonoBehaviour
{
public int startingHealth = 100; // The amount of health the player starts the game with.
public int currentHealth; // The current health the player has.
public Slider healthSlider; // Reference to the UI's health bar.
public Image damageImage; // Reference to an image to flash on the screen on being hurt.
public AudioClip deathClip; // The audio clip to play when the player dies.
public float flashSpeed = 5f; // The speed the damageImage will fade at.
public Color flashColour = new Color(1f, 0f, 0f, 0.1f); // The colour the damageImage is set to, to flash.
public float restartDelay = 5f;
//Animator anim; // Reference to the Animator component.
public AudioSource playerAudio; // Reference to the AudioSource component.
// PlayerMovement playerMovement; // Reference to the player's movement.
// PlayerShooting playerShooting; // Reference to the PlayerShooting script.
bool isDead; // Whether the player is dead.
bool damaged; // True when the player gets damaged.
void Awake()
{
// Setting up the references.
// anim = GetComponent<Animator>();
// playerAudio = GetComponent<AudioSource>();
// playerMovement = GetComponent<PlayerMovement>();
// playerShooting = GetComponentInChildren<PlayerShooting>();
// Set the initial health of the player.
currentHealth = startingHealth;
}
void Update()
{
// If the player has just been damaged...
if (damaged)
{
// ... set the colour of the damageImage to the flash colour.
damageImage.color = flashColour;
}
// Otherwise...
else
{
// ... transition the colour back to clear.
damageImage.color = Color.Lerp(damageImage.color, Color.clear, flashSpeed * Time.deltaTime);
}
// Reset the damaged flag.
damaged = false;
}
public void TakeDamage(int amount)
{
// Set the damaged flag so the screen will flash.
damaged = true;
// Reduce the current health by the damage amount.
currentHealth -= amount;
playerAudio.Play();
Debug.Log("PLayer Health: " + currentHealth);
// Set the health bar's value to the current health.
healthSlider.value = currentHealth;
// Play the hurt sound
playerAudio.Play();
// If the player has lost all it's health and the death flag hasn't been set yet...
if (currentHealth <= 0 && !isDead)
{
// ... it should die.
Death();
}
}
void Death()
{
// Set the death flag so this function won't be called again.
isDead = true;
Debug.Log("In der Death Funktion");
First of all, you don't need, or better, you SHOULD NOT, use GetComponent inside Update, it's a very slow method and it impacts a lot the performance.
So, change your code to this:
public class GameOverManager : MonoBehaviour {
public PlayerHealth playerHealthScript;
public float restartDelay = 5f;
private Animator anim;
private float restartTimer;
private void Awake() {
anim = GetComponent<Animator>();
//No need for GetComponent<PlayerHealth>() if you assign it in the Inspector
//playerHealthScript = GetComponent<PlayerHealth>();
}
private void Update() {
if (playerHealthScript.currentHealth <= 0) {
anim.SetTrigger("GamerOver");
restartTimer += Time.deltaTime;
if (restartTimer >= restartDelay) {
SceneManager.LoadScene(2);
}
}
}
}
Moreover, your bug happens most probably because in the Inspector you assigned to the playerHealthScript variable the game object containing the PlayerHealth script. But, since you try in Update to get the script component again but this time from the game object that has the GameOverManager script (and I assume it doesn't have the PlayerHealth script), you get the NullReference since that script can't be found on this game object.
So, as you can see from the two lines commented out in my code, you actually don't need to get that component from script, just assign it via Inspector and you're good to go.