Player health will not go down - c#

i have tried to make it so whenever the enemy collides with Player1 health drops, i have all of the public floats and transforms setup, but the health in the debug menu shows it just doesnt go down, any ideas?
(attached to player1)
using System.Collections;
using System.Collections;
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
private float health = 0f;
public Transform player;
[SerializeField] private float maxHealth = 100f;
private void Start() {
health = maxHealth;
}
public void UpdateHealth (float mod) {
health += mod;
if (health > maxHealth){
health = maxHealth;
} else if (health <= 0f){
health = 0f;
Debug.Log("Player Respawn");
}
}
}
attached to enemy:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAttack : MonoBehaviour
{
[SerializeField] private float attackDamage = 10f;
[SerializeField] private float attackSpeed = 1f;
public Transform player;
private float canAttack;
// Start is called before the first frame update
private void OnCollisionStay2D(Collision2D other) {
if (other.gameObject.tag == "Player1"){
if (attackSpeed <= canAttack){
other.gameObject.GetComponent<PlayerHealth>().UpdateHealth(-attackDamage);
canAttack = 0f;
}else {
canAttack += Time.deltaTime;
}
}
}}

You are serializing the Max Health, not the current health, so the value shown in the inspector will never change. Add the SerializeField to the health value as well and you'll see the current health.
[SerializeField]
private float health = 0f;
Beyond that, it's difficult to see if the code isn't working. I would ensure that the tag you're checking for is actually set on the Player, and add a debug message to OnCollisionStay2D to ensure the collision handler is being called. If it isn't, ensure you have the collider components on both the enemy game object and the player gameobject.
Depending on your intention, you may want to move the canAttack modifier to Update(), since your current logic will only "refresh" the canAttack timer when the enemy is actually colliding with the player. If the enemy hits a player and the player runs away, and comes back ten minutes later and runs into the enemy, the enemy won't attack, they'll only start "healing" their canAttack timer until it's ready again. Maybe this is what you want, but I suspect that it should go into Update() so enemies that have already attacked "rest up" at all times.

Related

Recoil animation boolean instantly goes to false

So I am trying to make it so that when the user shoots the recoil animation plays, but I have an issue where the bool (which goes true the moment the Fire() is called), instantly goes back to false. I have tried to remove the line of code which is supposed to only make the bool false once the user stopped firing the gun, but the result of that was just the recoil animation playing in a loop.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeaponFire : MonoBehaviour
{
public GameObject playerCam; // The player camera
public float range = 150f;
public float damage = 25f;
public float rounds = 30f;
public float mags = 3f;
public GameObject FPS_char; // The gameobject that has the animator
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (FPS_char.GetComponent<Animator>().GetBool("WeaponFire")) // Check to see if the player has fired
{
FPS_char.GetComponent<Animator>().SetBool("WeaponFire", false); // Set to false if true
}
}
public void Fire()
{
// Shoot a ray
RaycastHit hit;
// Set the WeaponFire bool to true when Fire() is called, which is called when the player presses the left mouse button
FPS_char.GetComponent<Animator>().SetBool("WeaponFire", true);
// Create a ray
if (Physics.Raycast(playerCam.transform.position, playerCam.transform.forward, out hit, range))
{
if (rounds <= 0)
{
// Player needs to reload
return;
}
if(rounds >= 0)
{
rounds -= 1f;
gameObject.GetComponent<AudioSource>().Play(); // Play a weapon fire sound
EnemyManager enemyManager = hit.transform.GetComponent<EnemyManager>(); // A script that gives the enemies health and attack damage etc
if (enemyManager != null) // check to see if what the player hit contains that script (if it does then it is an enemy)
{
enemyManager.Hit(damage); // Call the Hit() function of the script on the enemy which removes health from the enemy depending on how much damage the weapon does
}
}
}
}
public void Reload()
{
mags -= 1f;
rounds = 30f;
// Add a reload animation
}
}
For things that need to play once use animator.SetTrigger which plays the animation when it is called once automatically. You need to change the Animator parameter from Bool to Trigger in the Animator window.
public void Fire()
{
FPS_char.GetComponent<Animator>().SetTrigger("Fire");
}

How can I make it so that the player loses health when an enemy collides with a different object?

Here an image of what my game looks like so far so that you can get a better idea but I need it so that when one of the outer capsules touches the red box the player (middle capsule) loses health.
I have tried creating a new script which checks for collisions but I couldn't get it to work and am unsure where to go from here. Below is my code for how the health bar works and pressing the space bar reduces health for demonstration purposes.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerHealth : MonoBehaviour
{
public float MaxHealth;
public Slider _slide;
private float currentHealth;
void Start()
{
currentHealth = MaxHealth;
_slide.maxValue = MaxHealth;
_slide.value = MaxHealth;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
TakeDamage(25f);
if(currentHealth <=0)
{
//move to game over
}
}
void TakeDamage(float Damage)
{
currentHealth = currentHealth - Damage;
_slide.value = currentHealth;
}
}
At one point I tried using
void Update()
{
(collision.collider.name == "Barrier");
TakeDamage(25f);
if(currentHealth <=0)
{
//move to game over
}
}
but realised this is completely wrong, as well as trying to add a box collider to the red "barrier" to aid this but it didn't fix anything.
Update 2:
Also tried changing it so that the enemies move towards the barrier and not the player and added the code:
private void OnTriggerEnter(Collider other) {
TakeDamage(25f);
if(currentHealth <=0)
{
//move to game over
}
}
which doesn't present any errors but rather just doesn't do anything. The enemy capsules just go through it and to the centre, with no health being lost.
You need to check for collisions using OnCollisionEnter3D and then use the TakeDamage function inside of it. Here is an example:
public class PlayerHealth: MonoBehaviour
{
void OnCollisionEnter3D(Collision3D col) //Check for collision
{
TakeDamage(25f);
}
}
Note that for this function to work, both objects need a 3d collider and a rigid body. If you don't want your character to fall, just set gravity to 0.

Destroyed Bullet Won't Make Clones and Shoot After around 3 seconds

I made a script that shoots a bullet and destroys it after 3 seconds, however it destroys the original bullet after it is shot which makes unity unable to make copies of it to shoot another bullet, An okay solution might be to make a copy of the bullet and shoot the copy however I do not know how to do that.
This is the script for the gun
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunShootingScript : MonoBehaviour
{
public ParticleSystem MuzzleFlash;
public float damage = 10f;
public float range = 100f;
public GameObject bulletPrefab;
public float bulletLife = 3f;
public Camera playerCamera;
private void Update()
{
if(Input.GetKeyDown(KeyCode.Mouse0))
{
Destroy(bulletPrefab, bulletLife);
Shoot();
}
}
void Shoot()
{
MuzzleFlash.Play();
Instantiate(bulletPrefab, playerCamera.transform.position, playerCamera.transform.rotation);
RaycastHit hit;
if(Physics.Raycast(playerCamera.transform.position, playerCamera.transform.forward, out hit, range))
{
TakeDamage target = hit.transform.GetComponent<TakeDamage>();
if(target != null)
{
target.GiveDamage(damage);
}
}
}
}
This is the script for the bullet.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shot : MonoBehaviour
{
// Start is called before the first frame update
public float speed = 3000f;
void Update()
{
transform.position += transform.forward * speed * Time.deltaTime;
}
}
From what I see, you destroy the bulletPrefab but you never actually assign an object for it. You can have a separate GameObject and do that when instantiating inside the Shoot method. Try this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunShootingScript : MonoBehaviour
{
public ParticleSystem MuzzleFlash;
public float damage = 10f;
public float range = 100f;
public GameObject bulletPrefab;
public float bulletLife = 3f;
private GameObject bulletShot // This is what I added
public Camera playerCamera;
private void Update()
{
if(Input.GetKeyDown(KeyCode.Mouse0))
{
Destroy(bulletShot); // Removed the timer from here
Shoot();
}
}
void Shoot()
{
MuzzleFlash.Play();
bulletShot = Instantiate(bulletPrefab, playerCamera.transform.position, playerCamera.transform.rotation); // Assigned the bullet to the separate GameObject
RaycastHit hit;
if(Physics.Raycast(playerCamera.transform.position, playerCamera.transform.forward, out hit, range))
{
TakeDamage target = hit.transform.GetComponent<TakeDamage>();
if(target != null)
{
target.GiveDamage(damage);
}
}
}
}
That way, when you try to destroy it, there will actually be something to destroy. However, this will create a new issue. The previously instantiated object will be destroyed instantly when you shoot again. You can fix that by creating a list and adding the bullets there. However, that is NOT efficient. And since you are going to start all over, I suggest you go for an Object Pool. What is that? Glad you asked!
There is a very good video by Jason Weimann on YouTube about object pooling. You might want to give it a go, it is old but definitely not outdated.
https://www.youtube.com/watch?v=uxm4a0QnQ9E
You might need to keep a reference to the shot bullet, to be able to destroy with a coroutine after 3 seconds, it later on like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GunShootingScript : MonoBehaviour
{
public ParticleSystem MuzzleFlash;
public float damage = 10f;
public float range = 100f;
public GameObject bulletPrefab;
public float bulletLife = 3f;
private Queue<GameObject> myQ = new Queue<GameObject>(); //queue to keep track of the shot bullet and handle the delayed destroy
private IEnumerator coroutine;
void Start() {
coroutine = DelayedDestroy(3f);
}
public Camera playerCamera;
private void Update()
{
if(Input.GetKeyDown(KeyCode.Mouse0))
{
//Destroy(bulletPrefab, bulletLife); Commented to avoid immediate destruction
Shoot();
}
}
void Shoot()
{
MuzzleFlash.Play();
myQ.Enqueue(Instantiate(bulletPrefab, playerCamera.transform.position, playerCamera.transform.rotation));
RaycastHit hit;
if(Physics.Raycast(playerCamera.transform.position, playerCamera.transform.forward, out hit, range))
{
TakeDamage target = hit.transform.GetComponent<TakeDamage>();
if(target != null)
{
target.GiveDamage(damage);
}
}
StartCoroutine(coroutine);
}
private IEnumerator DelayedDestroy(float waitTime) {
yield return new WaitForSeconds(waitTime);
GameObject nullcheck = myQ.Dequeue();
if (nullcheck != null) { //in case the queue is empty
Destroy(nullcheck );
}
}
}
Did not debug that, it is to give you the idea. You can check the Queue data structure and coroutines.
Check pooling to achieve what you are making even better :)

Why isn't my program recognizing my target object

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AIController : MonoBehaviour {
[Header("Player interaction")]
[SerializeField] private GameObject target;
[SerializeField] private int damage;
[SerializeField] private float attackDelay;
[SerializeField] private float stunTime;
[SerializeField] private int health;
public int Health{
get{return health; }
set{ health = value;
agent.speed = runningSpeed;
agent.SetDestination(target.transform.position);
onPatrol = false;
if (health <= 0){
Destroy(gameObject);
}
}
}
// Update is called once per frame
void Update () {
CanAttack(target);
}
here i want to make sure that the AI will attack the player only when he is in front of the AI and within .5 meters. the reason i use this instead of OnTriggerEnter is that the AI will only end up attacking once and then just stand on top of the player without really doing anything further
void CanAttack (GameObject other)
{
Vector3 vectorToTarget = other.transform.position - transform.position;
float angleToTarget = Vector3.Angle(transform.forward, vectorToTarget);
if (angleToTarget <= FOV)
{
RaycastHit raycastData;
if (Physics.Raycast(transform.position, vectorToTarget, out raycastData, .5f))
{
GameObject newTarget = raycastData.collider.gameObject;
Attack(target);
}
}
}
I have also used Attack(other) and this still does not work
void Attack(GameObject other)
{
if (!hasAttacked)//if the AI has not recently attacked
{
if (!Player.isShieldOn)//if the player shield is not up
{
if (!Player.hasShieldBraclet)//if the player has the shield braclet
{
other.gameObject.GetComponent<Player>().Health -= damage - ShieldBraclet.dmgReduction;//reduce the amount of damage taken
}
else
{
other.gameObject.GetComponent<Player>().Health -= damage;//otherwise take normal damage
}
StartCoroutine("DelayAttack");//delay time until AI can attack again
}
else
{
StartCoroutine(Stun(stunTime));//if the shield was up stun this enemy
}
}
}
this is used to make sure that the AI waits a moment before attacking again that way the player doesn't die instantly upon touching the AI.
IEnumerator DelayAttack()
{
hasAttacked = true;//the AI has attacked
yield return new WaitForSeconds(attackDelay);//wait to attack again
hasAttacked = false;//the AI can now attack again
}
}
For the most part everything runs fine but when it reaches the code to deal damage to the player it tells me that the object reference is not set to an instance of an object. How can I fix this?
The Specific error code is: NullReferenceException: Object reference not set to an instance of an object Enemy.Attack(UnityEngine.GameObject other).
This is referring to the line
other.gameObject.GetComponent<Player>().Health -= damage - ShieldBraclet.dmgReduction;//reduce the amount of damage taken

unity3D, enemy following issue

I'm trying to make enemies follow my player when the player enters the radius area of an enemy, but make the enemy stop following when my bullet hits object or enters radiusArea.
See my gif for more detail:
Gif
script:
using UnityEngine;
using System.Collections;
public class FlyEnemyMove : MonoBehaviour
{
public float moveSpeed;
public float playerRange;
public LayerMask playerLayer;
public bool playerInRange;
PlayerController thePlayer;
// Use this for initialization
void Start()
{
thePlayer = FindObjectOfType<PlayerController>();
}
// Update is called once per frame
void Update()
{
flip();
playerInRange = Physics2D.OverlapCircle(transform.position, playerRange, playerLayer);
if (playerInRange)
{
transform.position = Vector3.MoveTowards(transform.position, thePlayer.transform.position, moveSpeed * Time.deltaTime);
//Debug.Log(transform.position.y);
}
//Debug.Log(playerInRange);
}
void OnDrawGizmosSelected()
{
Gizmos.DrawWireSphere(transform.position, playerRange);
}
void flip()
{
if (thePlayer.transform.position.x < transform.position.x)
{
transform.localScale = new Vector3(0.2377247f, 0.2377247f, 0.2377247f);
}
else
{
transform.localScale = new Vector3(-0.2377247f, 0.2377247f, 0.2377247f);
}
}
}
I hope someone can help me :(
Physics2D.OverlapCircle detects only the collider with the lowest z value (if multiple are in range). So you either need to change the z values so the player has the lowest or you need to work with Physics2D.OverlapCircleAll and check the list to find the player. Or you could change your layers so only the player itself is on that specific layer you feed into the overlap test.

Categories