I tried to use a bool to check for trigger before using InvokeRepeating but nothing happened. I can't put anything in the Update() method because then there'll be dozens of projectiles firing constantly.
Currently, the projectile fires every 3 seconds which is fine. However I only want those projectiles to fire if I'm standing in a trigger area (which I already have), but I can't seem to get it to work.
public class FireProjectile : MonoBehaviour
{
public GameObject Player;
public Transform launchPoint;
public GameObject projectile;
public float launchVelocity = 10f;
public bool isFiring;
void Start()
{
InvokeRepeating("fireProjectile", 0f, 3f);
}
void Update()
{
//
}
void fireProjectile()
{
var _projectile = Instantiate(projectile, launchPoint.position, launchPoint.rotation);
_projectile.GetComponent<Rigidbody>().velocity = launchPoint.forward * launchVelocity;
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject == Player)
{
isFiring = true;
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject == Player)
{
isFiring = false;
}
}
}
To mimic your code, we can set up a timer that keeps counting up to your required delay, then resets. When it resets, you can fire the projectile if you were in the trigger.
Note this is just one of many ways to accomplish this. It’s also a very simple way to stop a player spamming projectiles by moving in and out of the trigger area - the player only fires every delay seconds, if they’re currently within the trigger area.
public class FireProjectile : MonoBehaviour
{
public GameObject Player;
public Transform launchPoint;
public GameObject projectile;
public float launchVelocity = 10f;
public bool isFiring;
public float delay = 3;
private float _timer;
void Update()
{
_timer += Time.deltaTime;
if ( _timer >= delay )
{
// reset timer and fire if with trigger area.
_timer -= delay;
if ( isFiring )
fireProjectile();
}
}
void fireProjectile()
{
var _projectile = Instantiate(projectile, launchPoint.position, launchPoint.rotation);
_projectile.GetComponent<Rigidbody>().velocity = launchPoint.forward * launchVelocity;
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject == Player)
isFiring = true;
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject == Player)
isFiring = false;
}
}
Related
This question already has answers here:
How to make the script wait/sleep in a simple way in unity
(7 answers)
Closed 1 year ago.
Whenever lives get removed by an enemy in my game in unity, the game temporarily freezes. I think it has to do with the line "Thread.Sleep(3000);" but I am not sure of an alternative wait that will not freeze the entire game temporarily.
I would greatly appreciate if you could help me!
Thanks.
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public Transform attackPoint;
public float attackrange = 0.5f;
public LayerMask playerlayers;
public float speed = 3f;
private Transform target;
bool hitID = false;
private void Update()
{
if (target != null)
{
float step = speed * Time.deltaTime;
transform.position = Vector2.MoveTowards(transform.position, target.position, step);
Collider2D[] hitplayers = Physics2D.OverlapCircleAll(attackPoint.position, attackrange, playerlayers);
foreach (Collider2D player in hitplayers)
{
if (hitID == false)
{
hitID = true;
player.GetComponent<HeartSystem>().TakeDamage(1);
Thread.Sleep(3000);
hitID = false;
}
}
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player")
{
target = other.transform;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.gameObject.tag == "Player")
{
target = null;
}
}
void OnDrawGizmosSelected()
{
if (attackPoint == null)
return;
Gizmos.DrawWireSphere(attackPoint.position, attackrange);
}
}
Enemy.cs
using System.Collections;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public Transform attackPoint;
public float attackrange = 0.5f;
public LayerMask playerlayers;
public float speed = 3f;
private Transform _target;
private bool _hitID;
private void Update()
{
if (_target != null)
{
var step = speed * Time.deltaTime;
transform.position = Vector2.MoveTowards(transform.position, _target.position, step);
var hitPlayers = new Collider2D[10];
Physics2D.OverlapCircleNonAlloc(attackPoint.position, attackrange, hitPlayers, playerlayers);
foreach (var player in hitPlayers)
{
if (_hitID == false)
{
StartCoroutine(HitCoroutine(player));
}
}
}
}
private IEnumerator HitCoroutine(Collider2D player)
{
_hitID = true;
player.GetComponent<HeartSystem>().TakeDamage(1);
yield return new WaitForSeconds(3);
_hitID = false;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Player"))
{
_target = other.transform;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.gameObject.CompareTag("Player"))
{
_target = null;
}
}
private void OnDrawGizmosSelected()
{
if (attackPoint == null)
return;
Gizmos.DrawWireSphere(attackPoint.position, attackrange);
}
}
Try this.
In a case like this you want to use a coroutine.
You can read about coroutines here:
https://docs.unity3d.com/Manual/Coroutines.html
Unity is single threaded, so you are actually suspending the entire thread the game is running on. Since the frames are updated on this thread, you're even freezing the visual frame updates.
You have a few ways to manage "waiting" in game.
Coroutines:
A process that can be suspended until a later condition is met (including time)
void Update()
{
//Your other code goes here
if (hitID == false)
{
hitID = true;
player.GetComponent<HeartSystem>().TakeDamage(1);
StartCoroutine(ResetHitIDAfterSeconds(3))
}
}
IEnumerator ResetHitIDAfterSeconds(float seconds)
{
yield return new WaitForSeconds(seconds);
hitID = false;
}
The above code allows you to have the same logic, and will kick off a coroutine that waits for 3 seconds before setting hitID back to false.
Time.deltaTime:
You can also track the elapsed time in your Update method and manage your variables actively based on the elapsed time. Time.deltaTime returns the amount of time that elapsed since the previous frame.
public class ConstantRotation : MonoBehaviour
{
public float timeElapsed = 0f;
void Update()
{
//Your other code goes here
if (hitID == false)
{
hitID = true;
player.GetComponent<HeartSystem>().TakeDamage(1);
timeElapsed = 0f;
}
else
{
timeElapsed += Time.deltaTime
if(timeElapsed >= 3.0f)
{
hitId = false;
}
}
}
}
You would likely want to modify the above code a bit to avoid having the logic run on every frame, and only run it on hit.
I Have created a 2d stealth game where the enemy fires on the player, only problem is that although the bullets are created and deleted fine on another script, the script itself spams the program with bullets every frame, creating the unwanted result
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HurtPlayer : MonoBehaviour
{
public float timeToShoot;
private float timeToShootCounter;
private bool shot;
private Vector3 moveDirection;
public float timeBetweenShot;
public float timeBetweenShotCounter;
public Transform firePoint;
public GameObject Bullet;
// Use this for initialization
void Start()
{
shot = false;
timeToShootCounter = timeToShoot;
}
// Update is called once per frame
void Update()
{
while (shot == true)
{
StartCoroutine(Delay());
Destroy(GameObject.Find("Bullet"));
timeBetweenShot -= Time.deltaTime;
timeToShoot -= Time.deltaTime;
}
}
IEnumerator Delay()
{
yield return new WaitForSeconds(0.5f);
}
void OnTriggerStay2D(Collider2D other)
{
if (other.gameObject.tag == "player")
{
if (shot == false)
{
if (timeToShoot >= 0f)
{
shot = true;
if (shot == true)
{
shot = false;
Instantiate(Bullet, firePoint.position, firePoint.rotation);
Delay();
if (timeBetweenShot <= 0f)
{
shot = false;
timeToShoot = timeToShootCounter;
timeBetweenShot = timeBetweenShotCounter;
}
}
}
}
}
}
}
What I want is the time betweenshot to work and for the enemy to only shoot once every one or half a second, thanks.
Is this what you are looking for?
IEnumerator ContinuousShoot()
{
// Continuously spawn bullets until this coroutine is stopped
// when the player exits the trigger.
while (true)
{
yield return new WaitForSeconds(1f); // Pause for 1 second.
Instantiate(Bullet, firePoint.position, firePoint.rotation);
}
}
void OnTriggerEnter2D(Collider2D other)
{
// Player enters trigger
if (other.gameObject.CompareTag("player"))
{
StartCoroutine(ContinuousShoot());
}
}
void OnTriggerExit2D(Collider2D other)
{
// Player exits trigger
if (other.gameObject.CompareTag("player"))
{
StopCoroutine(ContinuousShoot());
}
}
I made two scripts. One that'll keep track of the player's health, health bar and cause the screen to flash when the player is damaged. The other script is meant to be placed on any object I wish to do damage to the player, on contact. My problem is, Nothing seems to be doing any damage to the player.
PlayerHealth.cs:
using UnityEngine;
using UnityEngine.UI;
public class PlayerHealth : MonoBehaviour
{
public int currentHealth;
public float flashSpeed = 5;
public Slider healthSlider;
public Color flashColour = new Color(1, 0, 0, 0.1f);
bool isDead;
bool damaged;
private void Awake()
{
currentHealth = 100;
}
private void Update()
{
damaged = false;
}
public void TakeDamage(int amount)
{
damaged = true;
currentHealth -= amount;
healthSlider.value = currentHealth;
}
}
AttackPlayer.cs:
using UnityEngine;
public class AttackPlayer : MonoBehaviour
{
public float timeBetweenAttacks = 0.5f;
public int attackDamage = 10;
GameObject player;
PlayerHealth playerHealth;
float timer;
private void Awake()
{
player = GameObject.FindGameObjectWithTag("Player");
playerHealth = player.GetComponent<PlayerHealth>();
}
private void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject == player)
{
Attack();
}
}
private void Update()
{
timer += Time.deltaTime;
if(playerHealth.currentHealth <=0)
{
// TODO: add death script here.
}
}
void Attack()
{
timer = 0f;
if(playerHealth.currentHealth > 0)
{
playerHealth.TakeDamage(attackDamage);
}
}
}
The player has a rigidbody2D. The player and damaging objects have Box Collider 2D's on them.
Make sure that the players Collider has isTrigger enabled.
attackDamage is public -> set in the inspector. Make sure it is not 0.
You could use
[Range(1,100)] public int attackDamage = 10;
to automatically clamp the value in the inspector.
A guess but I'ld say your Collider might not be on the GameObject player but probably on one of its children => the condition col.gameObject == player is not true.
Instead of GameObject references rather compare the PlayerHealth (since there is only one) reference like
private void OnTriggerEnter2D(Collider2D col)
{
// gets PlayerHealth component on this or any parent object
var health = col.GetComponentInParent<PlayerHealth>();
if (health == playerHealth)
{
Attack();
}
}
You have
private void Update()
{
damaged = false;
}
public void TakeDamage(int amount)
{
damaged = true;
currentHealth -= amount;
healthSlider.value = currentHealth;
}
I don't know what else should happen on TakeDamage but the value of damaged is resetted in Update so right after it was set by the Trigger because Physics events like OnTriggerEnter are executed before Update (see execution Order).
Hint: Instead of
player = GameObject.FindGameObjectWithTag("Player");
playerHealth = player.GetComponent<PlayerHealth>();
you could also use
playerHealth = FindObjectOfType<PlayerHealth>();
if that component exists only once in your scene.
Or to be more flexible (having multiple Players) all you have to do is change your OnTriggerEnter2D and Attack method to
private void OnTriggerEnter2D(Collider2D col)
{
// gets PlayerHealth component on this or any parent object
var health = col.GetComponentInParent<PlayerHealth>();
if (health != null)
{
Attack(health);
}
}
void Attack(PlayerHealth health)
{
timer = 0f;
if(health.currentHealth > 0)
{
health.TakeDamage(attackDamage);
}
}
So you wouldn't need to get the reference before.
The issue I am having is with a dropped item i pick up adding ammo to a gun.
Built a Gun class with all the methods and variables.
Built a Rifle class derived from the Gun class
The Rifle works perfect No Issues
I now am adding a "PickUp" system where x amount of enemies drop a pickup.
This is the script on the item to pick up
public class AddARAmmo : MonoBehaviour
{
private Rifle rifle;
private void Awake()
{
rifle = FindObjectOfType<Rifle>();
}
private void OnTriggerEnter(Collider other)
{
if (other.tag == string.Format("Player"))
{
rifle.AddAmmo(30);
Destroy(gameObject);
}
}
}
The rifle and gun scripts are kind of long but here is the relevant stuff from the Gun base class is public abstract class ......
public int bulletsInStock;
public void AddAmmo(int ammoToAdd)
{
bulletsInStock += ammoToAdd;
UpdateAmmo();// updates on screen Ammo
}
......
Then in the Rifle Class
public override void Modifiers() // This is where the guns starts are stored
{
bulletSpeed = 2777f;
bulletsInStock = 200;
bulletsInMag = 30;
bulletPoolSize = 40;
desiredRPS = 15;
muzzleFlashPoolSize = 10;
}
I am getting an Object Reference Not Set To An Instance
The Rifle script is on the rifle in the game hierarchy so it should find it.
Does anyone see anything wrong?
Here is the full Gun script
public abstract class Gun : MonoBehaviour
{
[SerializeField] protected GameObject muzzleFlash;// spawns on barrelEnd
[SerializeField] protected Transform muzzleFlashFolder;
[SerializeField] protected Transform bulletFolder;// is the parent of bullets
[SerializeField] protected Transform barrelEnd;// Gameobject at the end of barrel
[SerializeField] protected Rigidbody bullet; // The bullet Prefab
[SerializeField] protected Text ammo; // OSD
[SerializeField] protected Text weaponType; // OSD
[HideInInspector] protected float bulletSpeed;
[HideInInspector] public int bulletsInStock;
[HideInInspector] protected int bulletsInMag;
[HideInInspector] protected float desiredRPS;// Rounds Per Second
[HideInInspector] protected List<Rigidbody> poolOfBullets; // Make pool for bullets
[HideInInspector] protected int bulletPoolSize; // The size off the buletpool 10 works really well
[HideInInspector] protected List<GameObject> muzzleFlashPool;// pool for muzzleflash
[HideInInspector] protected int muzzleFlashPoolSize; // size of the muzzle pool
[HideInInspector] protected int bulletsLeft; // In mag
[HideInInspector] protected bool isReloading = false;
[HideInInspector] protected float timeLeft;// for fire speed
[HideInInspector] protected float fireSpeedTimer;
[HideInInspector] protected Weapons weaponsScript;
[HideInInspector] protected PlayerController playerController;
protected void FixedUpdateStuff()
{
if (playerController.canMove && Input.GetAxisRaw(string.Format("Fire1")) > 0)
{
FireSpeedControl();// call the fire timer controller
}
if (playerController.canMove && Input.GetAxisRaw(string.Format("Fire1")) == 0)
{
timeLeft = 0f;
}
if (playerController.canMove && Input.GetKeyDown(KeyCode.R) && !isReloading)
{
Reload();
}
UpdateAmmoOnInput();
}
protected void UpdateStuff()
{
if (gameObject.activeInHierarchy)// when a gun become active it updates OSD
{
UpdateWeaponType();// With its Name
}
}
protected void RPSFinder()// finds the Rounds Per Second the gun will fire
{
fireSpeedTimer = (100 / desiredRPS) / 100;
timeLeft = fireSpeedTimer;
}
protected void Fire()// Instatiates a clone of the desired bullet and fires it at bulletSpeed
{
if (!Empty())
{
Rigidbody bulletClones = GetPooledBullet();
if (bulletClones != null)
{
bulletClones.transform.SetPositionAndRotation(barrelEnd.position, barrelEnd.rotation);
bulletClones.gameObject.SetActive(true);
}
GameObject muzzleFlashClone = GetMuzzleFlash();
if (muzzleFlashClone != null)
{
muzzleFlashClone.transform.position = barrelEnd.position;
muzzleFlashClone.gameObject.SetActive(true);
}
bulletClones.AddForce(-bulletClones.transform.up * bulletSpeed * .304f); //add the force in FPS * .304 = MPS
bulletsLeft--;// the holder to know how many bullets are left in the magazine
isReloading = false;// Gun cannot reload unless it has been fired
UpdateAmmo();// Updates the on screen ammo count and the stock usage
return;
}
}
protected void Reload()
{// this removes full magazine from the stock and the stock can still go negitive FIX FIX FIX FIX FIX FIX FIX
if (bulletsInStock > 0)
{
isReloading = true;
bulletsInStock -= bulletsInMag;
bulletsLeft = bulletsInMag;
UpdateAmmo();
}
}
protected bool Empty()// Checks the magazine to see if there are bullets in it
{
if (bulletsLeft == 0)
return true;
else
return false;
}
protected void FireSpeedControl()// controls the RPS fired by the gun Controled by Update() Input
{
if (timeLeft > 0f)
{
timeLeft -= Time.deltaTime;
}
else if (timeLeft <= 0f)
{
Fire();
timeLeft = fireSpeedTimer;
}
}
protected Rigidbody GetPooledBullet()// retrieve a preInstatiated bullet from the pool to use when shooting
{
for (int i = 0; i < poolOfBullets.Count; i++)
{
if (!poolOfBullets[i].gameObject.activeInHierarchy)
{
return poolOfBullets[i];
}
}
return null;
}
protected GameObject GetMuzzleFlash()
{
for (int i = 0; i < muzzleFlashPool.Count; i++)
{
if (!muzzleFlashPool[i].gameObject.activeInHierarchy)
{
return muzzleFlashPool[i];
}
}
return null;
}
protected void UpdateAmmo()// Update the on screen ammo information
{
ammo.text = bulletsLeft + string.Format( " : ") + bulletsInStock;
}
protected abstract void UpdateWeaponType();
protected void UpdateAmmoOnInput()
{
if (weaponsScript.updateAmmo)
{
UpdateAmmo();
weaponsScript.updateAmmo = false;
}
}
public abstract void Modifiers();
protected void StartStuff()
{
Modifiers();// Call first to store indvidual gun stats
playerController = FindObjectOfType<PlayerController>();
weaponsScript = FindObjectOfType<Weapons>();
poolOfBullets = new List<Rigidbody>();
for (int i = 0; i < bulletPoolSize; i++)
{
Rigidbody bulletClone = (Rigidbody)Instantiate(bullet);
bulletClone.gameObject.SetActive(false);// Builds the Inspector list
poolOfBullets.Add(bulletClone); //and populates the elements with clones
bulletClone.transform.parent = bulletFolder.transform;
}
muzzleFlashPool = new List<GameObject>();
for (int i = 0; i < muzzleFlashPoolSize; i++)
{
GameObject muzzleFlashClone = (GameObject)Instantiate(muzzleFlash);
muzzleFlashClone.gameObject.SetActive(false);
muzzleFlashPool.Add(muzzleFlashClone);
muzzleFlashClone.transform.parent = muzzleFlashFolder.transform;
}
bulletsLeft = bulletsInMag;
ammo.text = string.Format( " 0 : 0 ");
RPSFinder();// Run last to set the RPS of the gun
}
public void AddAmmo(int ammoToAdd)
{
bulletsInStock += ammoToAdd;
UpdateAmmo();
}
}
}
and here is the full Rifle script
public class Rifle : Gun
{
//All variables are stored in the "Gun" Script
//Copy this onto guns
void Start()
{
StartStuff();
}
private void FixedUpdate()
{
FixedUpdateStuff();
}
void Update()
{
UpdateStuff();
}
public override void Modifiers() // This is where the guns starts are stored
{
bulletSpeed = 2777f;
bulletsInStock = 200;
bulletsInMag = 30;
bulletPoolSize = 40;
desiredRPS = 15;
muzzleFlashPoolSize = 10;
}
protected override void UpdateWeaponType()
{
weaponType.text = string.Format("Assault Rifle");
}
}
There are three reasons why FindObjectOfType may return null:
Let's say the script name to find is Rifle:
And FindObjectOfType<Rifle>() is returning null.
1.The GameObject the Rifle script is attached to is in-active. You must make sure that the GameObject is active.
2.The Rifle is not attached to any GameObject at-all. Make sure that the Rifle script is attached to a GameObject..
It doesn't matter if the script is enabled or disabled, it should find it as long as the GameObject it is attached to is active. See #1.
3.When loading new scene:
When you trigger scene loading with functions such as SceneManager.LoadScene
and Application.LoadLevel, FindObjectOfType will not be able to find anything until the scene loading it done.
See here for how to check when scene has finished loading.
If you still have problems which you shouldn't, simply find the GameObject then get the Rifle component from it. Assuming that the name of the GameObject is "Rifle" too.
GameObject.Find("Rifle").GetComponent<Rifle>();
I figured out how to fix this for anyone having a similar issue.
The issue was when the enemy dropped the item, the Awake() method ran. When it ran the gun was inactive in the scene so FindObjectOfType did not find the reference script because as mentioned above, It has to be active in the scene to be found.
So what I did was create a Holder script i called EnemyDrops and this script calls the findobjectoftypes for the guns. That way the call is done on the initial game start .
I then changed the pickup to find the EnemyDrops script(which is on en empty game object) and send the call to it.
EnemyDrops Script
private Handgun handgun;
private void Awake()
{
handgun = FindObjectOfType<Handgun>();
}
public void AddHandgunAmmo(int x)
{
handgun.AddAmmo(x);
}
and the new pickup script
public class AddHandgunAmmo : MonoBehaviour
{
private EnemyDrops enemyDrops;
private void Awake()
{
enemyDrops = FindObjectOfType<EnemyDrops>();
}
private void OnTriggerEnter(Collider other)
{
if (other.tag == string.Format("Player"))
{
enemyDrops.AddHandgunAmmo(50);
Destroy(gameObject);
}
}
}
As you can see it all still works with passing values through the methods just had to have a "middle man" to relay the information. But this works just fine
Thanks for everyones feedback and I hope this helps someone
I am new at Unity. I have successfully made a player can shoot a bullet to the enemy depends on the current wave, and I make where the enemy have it is own life depends on the current wave multiply with certain value. If the enemy life is 0, the enemy dies. But, the problem is: for example there are 2 enemies on the scene and the enemy life is 5 and the player start shooting the first enemy until the enemy life decreases to 2, when the player start shooting the second enemy, the enemy life is not 5 anymore, but it is 2 until one of the 2 cubes is dead and the enemy life reset to 5.
How can I solve this problem?
Here is the bullet script:
public class BulletManager : MonoBehaviour
{
private ScoreManager scoreManager;
private PlayerController playerController;
private EnemyManager enemyManager;
public int bulletPower;
private void Start()
{
scoreManager = GameObject.Find("Game Manager").GetComponent<ScoreManager>();
playerController = GameObject.Find("Character").GetComponent<PlayerController>();
enemyManager = GameObject.Find("Game Manager").GetComponent<EnemyManager>();
bulletPower = playerController.currentWave;
}
private void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "Enemy")
{
enemyManager.enemyLife -= bulletPower;
if (enemyManager.enemyLife <= 0)
{
scoreManager.Points += (2 * playerController.currentWave);
enemyManager.enemyLife = playerController.currentWave * 5;
Destroy(col.gameObject);
}
}
}
}
And here is the enemy script:
public class EnemyManager : MonoBehaviour
{
private ScoreManager scoreManager;
private SoundManager soundManager;
private PlayerController playerController;
public int enemyLife;
private void Start()
{
scoreManager = GameObject.Find("Game Manager").GetComponent<ScoreManager>();
soundManager = GameObject.Find("Game Manager").GetComponent<SoundManager>();
playerController = GameObject.Find("Character").GetComponent<PlayerController>();
enemyLife = playerController.currentWave * 5;
}
private void Update()
{
if (playerController.life <= 0)
{
Invoke("Restart", 1);
}
}
private void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "Bullet")
{
soundManager.PlaySound("Enemy Dead");
Destroy(col.gameObject);
}
else if (col.gameObject.tag == "Destroyer")
{
playerController.life--;
}
}
private void Restart()
{
scoreManager.SendToHighScore();
Application.LoadLevel(0);
}
}
The problem is that all of your enemies use the EnemyManager class to manage their health. What your code does is count the amount of hits on all enemies and then destroys the last one that was hit. My suggestion is to add an EnemyStats class to manage stats specific to each enemy (name of the enemy, the amount of damage they can do, weapon graphics, sound effects, the skys the limit). I hate to give you the answer right off the bat (or at least a basic one) since this is a good learning opportunity but here is a basic script that should do what you need and the changes you need to make to your BulletManager script.
EnemyStats.cs (Attach to the enemies game object)
public class EnemyStats : MonoBehaviour
{
public int currentHealth;
public int maxHealth;
public float increasePerWave;
public int bulletPower;
// use a negative value for healing
public void ReduceHealth(int damage)
{
currentHealth -= damage;
}
public bool IsDead()
{
return currentHealth <= 0;
}
public int GetDamage()
{
return bulletPower;
}
/// Restores enemy to their max health for the current wave. Call this after creating an spawning.
public void RestoreHealth(int wave)
{
currentHealth = (int)(maxHealth * increasePerWave * (wave + 1));
}
}
BulletManager update
private void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "Enemy")
{
EnemyStats stats = col.GetComponent<EnemyStats>();
if (stats)
{
stats.ReduceHealth(bulletPower);
if (stats.IsDead())
{
scoreManager.Points += (2 * playerController.currentWave);
Destroy(col.gameObject);
}
}
}
}