Unity2D Melee Combat - c#

I'm new to programming and C#. I'm trying to build a melee system for my platform game following tutorials on yt.
This is my PlayerAttack script:
using System.Collections; using System.Collections.Generic; using UnityEngine;
public class PlayerAttack : MonoBehaviour {
//[SerializeField] private float attackCooldown;
[SerializeField] private float range;
[SerializeField] private int damage;
[SerializeField] private LayerMask enemyLayer;
public Transform AttackPoint;
//private float cooldownTimer = Mathf.Infinity;
private Animator anim;
private Enemy enemyHealth;
private void Awake()
{
anim = GetComponent<Animator>();
enemyHealth = GetComponent<Enemy>();
}
private void Update()
{
if (Input.GetButtonDown("Fire1"))
{
anim.SetTrigger("Attack");
Attack();
Debug.Log("attacking");
}
}
private void OnDrawGizmos()
{
if (AttackPoint == null)
return;
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(AttackPoint.position, range);
}
void Attack()
{
Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(AttackPoint.position, range, enemyLayer);
foreach (Collider2D Enemy in hitEnemies)
{
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
}
}
}
and this one is the EnemyHealth script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
[SerializeField] private int startingHealth;
public int currentHealth;
public Animator anim;
void Start()
{
currentHealth = startingHealth;
}
public void TakeDamage(int _damage)
{
currentHealth = Mathf.Clamp(currentHealth - _damage, 0, startingHealth);
if (currentHealth > 0)
{
//hurt animation
//invulnerability
}
else
{
//die animation
GetComponentInParent<EnemyPatrol>().enabled = false;
GetComponent<EnemyMelee>().enabled = false;
}
}
void Die()
{
if (currentHealth <= 0)
{
Debug.Log("Enemy Dead!");
Destroy(gameObject);
//die animation
}
}
}
I'm getting an error from the Unity editor "object reference not set to an instance of an object" on line 61 of the PlayerAttack:
enter code here
Enemy.transform.GetComponent().TakeDamage(damage);
I checked the scripts names and they are fine, and I checked also if I missed something in the editor , I don't know what's wrong.
When I hit the enemy the game crashes and i get this error.
Thanks

You are trying to call Enemy.transform.GetComponent<Enemy>().TakeDamage(damage); on everything that has collider but some of those objects don't have the Enemy component so you can't call TakeDamage on them. That is where the error comes from. To fix it you have to find only the enemy game objects. To do this you can assign tags to your enemy and check the ones with Enemy tag through the script like this:
foreach (Collider2D Enemy in hitEnemies)
{
if (Enemy.tag == "Enemy")
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
}
There is another way to do it without tags which I don't recommend:
foreach (Collider2D Enemy in hitEnemies)
{
if (Enemy.transform.GetComponent<Enemy>() != null)
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
}

Check if the player has hit the enemy or not:
foreach (Collider2D hitEnemy in hitEnemies) {
if(hitEnemy.TryGetComponent(out Enemy enemy)) {
enemy.TakeDamage(damage);
}
}
Other than the answer:
In your PlayerAttack.cs script, you have a field enemyHealth which you are trying to get a reference to it using enemyHealth = GetComponent<Enemy>(); in your Awake() method. This doesn't get a reference to it because of Enemy.cs and PlayerAttack.cs scripts are attached to different gameobjects.

Related

Unity 2D Enemies don't take damage

I'm a newbie in unity and game development. I'm trying to build a combat 2d game, but I can't damage enemies and I'm banging my head against a wall for 1 week, because I can't see where is the error.
This is my Player script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAttack : MonoBehaviour
{
//[SerializeField] private float attackCooldown;
[SerializeField] private float range;
[SerializeField] private int damage;
[SerializeField] private LayerMask enemyLayer;
public Transform AttackPoint;
//private float cooldownTimer = Mathf.Infinity;
private Animator anim;
private Enemy enemyHealth;
private void Awake()
{
anim = GetComponent<Animator>();
enemyHealth = GetComponent<Enemy>();
}
private void Update()
{
if (Input.GetButtonDown("Fire1"))
{
anim.SetTrigger("Attack");
Attack();
Debug.Log("attacking");
}
}
private void OnDrawGizmos()
{
if (AttackPoint == null)
return;
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(AttackPoint.position, range);
}
public void Attack()
{
Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(AttackPoint.position, range, enemyLayer);
foreach (Collider2D Enemy in hitEnemies)
{
if (Enemy.CompareTag("Enemy"))
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
Debug.Log("Enemy Hit!");
}
}
}
And this is the Enemy script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
[SerializeField] private float startingHealth;
public float currentHealth;
public Animator anim;
private bool dead;
void Start()
{
currentHealth = startingHealth;
}
public void TakeDamage(float _damage)
{
//currentHealth = Mathf.Clamp(currentHealth - _damage, 0, startingHealth);
if (currentHealth > 0)
{
//hurt anim
Debug.Log("damage taken");
}
if (!dead)
{
//dead anim
GetComponent<EnemyPatrol>().enabled = false; //enemy cannot move while he is dead
GetComponent<EnemyMelee>().enabled = false; //enemy cannot attack
dead = true;
}
}
private void Update() //for testing
{
if (Input.GetKeyDown(KeyCode.P))
TakeDamage(1);
}
}
I tried testing it with the debug, and unity tells me that I'm hitting the enemy but without damaging him.
I tried also to damage the enemy pressing P for testing, and this works.
Thank you guys in advance for your help,
Federico.
Built Player and Enemy scripts. The player can be damaged by enemies but the player cannot damage the enemies.
In your attack function, here:
if (Enemy.CompareTag("Enemy"))
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
Debug.Log("Enemy Hit!");
Only the TakeDamage line is inside the if statement, the log is not so it always executes. YOu need to change it look like this:
if (Enemy.CompareTag("Enemy"))
{
Enemy.transform.GetComponent<Enemy>().TakeDamage(damage);
Debug.Log("Enemy Hit!");
}
So the issue is therefore that CompareTag is returning false, if you're seeing the enemy hit log and not the take damage log. The enemy does not have the correct tag.

GameObject not being removed from List?

I've tried 2/3 different ways of removing my gameObject from my List but none are working. When I debug the method the debug log is showing up as it should yet the gameobject still isn't removed from the list.
When my teammates kill an enemy I want the enemy to be removed from the list and then destroyed so I can continue to iterate through the List to find the closest enemy to begin attacking. Because the gameObject's are not being removed I get a null reference and i can loop through my for loop to check.
1st Script: List is created and used in a for loop, removing and destroying the enemy also occurs in here.
public class FriendlyManager : MonoBehaviour
{
public NavMeshAgent navMeshAgent;
public Transform player;
public static FriendlyManager singleton;
public float health;
public float minimumDistance;
public int damage;
public List<GameObject> enemies;
private GameObject enemy;
private GameObject enemyObj;
// animations
[SerializeField] Animator animator;
bool isAttacking;
bool isPatroling;
// attacking
[SerializeField] Transform attackPoint;
[SerializeField] public GameObject projectile;
public float timeBetweenAttacks;
bool alreadyAttacked;
private void Awake()
{
navMeshAgent = GetComponent<NavMeshAgent>();
animator = GetComponent<Animator>();
enemyObj = new GameObject();
}
private void Start()
{
singleton = this;
isAttacking = false;
isPatroling = true;
animator.SetBool("isPatroling", true);
}
private void Update()
{
for(int i = 0; i < enemies.Count; i++)
{
if(Vector3.Distance(player.transform.position, enemies[i].transform.position) <= minimumDistance)
{
enemy = enemies[i];
Attacking(enemy);
}
}
}
private void Attacking(GameObject enemy)
{
// stop enemy movement.
navMeshAgent.SetDestination(transform.position);
enemyObj.transform.position = enemy.transform.position;
transform.LookAt(enemyObj.transform);
if (!alreadyAttacked)
{
isAttacking = true;
animator.SetBool("isAttacking", true);
animator.SetBool("isPatroling", false);
Rigidbody rb = Instantiate(projectile, attackPoint.position, Quaternion.identity).GetComponent<Rigidbody>();
rb.AddForce(transform.forward * 32f, ForceMode.Impulse);
alreadyAttacked = true;
Invoke(nameof(ResetAttack), timeBetweenAttacks);
}
}
private void ResetAttack()
{
alreadyAttacked = false;
animator.SetBool("isAttacking", false);
}
public void DestroyEnemy(GameObject enemy)
{
enemies.Remove(enemy);
Debug.Log("AHHHHHHH M GOING CRAZY");
Destroy(gameObject);
}
}
}
2nd Script: Deals with the damage and checks enemy's currentHealth. (I have to post it as an image because for Stack Overflow is being annoying.) ._.
Shouldn't it just be
Destroy(enemy);
and not
Destroy(gameObject);

Why doesn't "OnTriggerEnter2D()" work when two specific objects collide?

I am creating this flappy bird style game in unity with C#.
I have a scored function in the Game Controller script. Here it is...
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class GameController : MonoBehaviour
{
private int score = 0;
public float starScrollSpeed;
public float groundScrollSpeed;
public float skyScrollSpeed;
public GameObject gameOverText;
public GameObject playAgain;
public bool gameOver = false;
public static GameController instance;
public Text scoreText;
// Start is called before the first frame update
void Awake()
{
if(instance == null)
{
instance = this;
}
else if(instance != this)
{
Destroy(gameObject);
}
}
// Update is called once per frame
void Start()
{
}
void Update()
{
}
public void BirdScored()
{
if (gameOver)
{
return;
}
score++;
scoreText.text = "SCORE " + score.ToString();
}
public void PlaneDestroyed()
{
gameOverText.SetActive(true);
playAgain.SetActive(true);
gameOver = true;
}
}
Actually Bird and Plane is the same thing.
What I want to do is to make the bird score/run the BirdScored() function when the Plane overlaps with a star. The Plane has a Rigidbody2D and a collider and stars have a Rigidbody2D but no collider because In the bird script if the plane collide, it destroys.
Here is the Bird Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bird : MonoBehaviour
{
private bool isDead = false;
private Rigidbody2D rb2d;
public float upforce = 200f;
private Animator anim;
// Start is called before the first frame update
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
if (isDead == false)
{
if (Input.GetMouseButtonDown(0))
{
rb2d.velocity = Vector2.zero;
rb2d.AddForce(new Vector2(0, upforce));
}
}
anim.SetTrigger("Flap");
}
void OnCollisionEnter2D()
{
isDead = true;
anim.SetTrigger("Die");
GameController.instance.PlaneDestroyed();
}
}
And here is the star script...
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Stars : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.name == "Plane")
{
GameController.instance.BirdScored();
}
}
}
What is wrong and what should I do?
Put a Colider2D on the star, and check the Is Trigger option in the inspector.
The Is Trigger is disable the collision with any other collider2d so your plane wont be destroyed by the OnCollisionEnter2D, but the OnTriggerEnter2D will trigger properly.
I can see in your screenshot that the collider isn't set to "is trigger", which makes it unable to register trigger collisions.

Vector3 is being reset to 0,0,0 Unity

I'm making a game right now where the enemies become alerted to the player and chase them indefinetely until the player stand on a "safe plate" in game. When the player stands here the enemies should then return to their original position.
The issue is that whenever the player stands on the plate I get a null reference exception error and I can see the original guard position has been reset to 0,0,0 in the console. I'm assuming the reason for the null reference exception is because the world origin isn't on the nav mesh I'm using, although I could easily be wrong about that. I just can't seem to figure out why the vector3 value has changed at all since the guardPosition variable is initiated on Start and never touched again.
I've included both my enemyAi class (script attached to enemy) and my class associated with stepping on plates. If there's anything more needed to include let me know. If anyone has any ideas, help would be appreciated. Cheers.
Screenshot on console after stepping on plate
public class EnemyAI : MonoBehaviour
{
DeathHandler dh;
[SerializeField] Transform target;
[SerializeField] float chaseRange = 5f;
[SerializeField] float killRange = 2f;
[SerializeField] GameObject explosionEffect;
NavMeshAgent navMeshAgent;
float distanceToTarget = Mathf.Infinity;
bool isDead = false;
bool isAlert = false;
Vector3 guardPosition;
void Start()
{
GameObject gob;
gob = GameObject.Find("Player");
dh = gob.GetComponent<DeathHandler>();
guardPosition = transform.position;
navMeshAgent = GetComponent<NavMeshAgent>();
}
void Update()
{
distanceToTarget = Vector3.Distance(target.position, transform.position);
print(guardPosition + " guard position during update");
//alert a dude
if (distanceToTarget <= chaseRange && !isDead)
{
isAlert = true;
}
//chase a dude
if (isAlert == true)
{
ChasePlayer();
print(isAlert);
}
}
public void ChasePlayer()
{
navMeshAgent.SetDestination(target.position);
//explode a dude
if (distanceToTarget <= killRange)
{
Explode();
dh.KillPlayer();
}
}
public void SetAlertStatus(bool status)
{
isAlert = status;
}
public void ReturnToPost()
{
//isAlert = false;
print(guardPosition + " guard position after stepping on plate");
navMeshAgent.SetDestination(guardPosition);
}
void Explode()
{
Instantiate(explosionEffect, transform.position, transform.rotation);
isDead = true;
Destroy(gameObject);
}
void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, chaseRange);
}
}
public class SafeSpots : MonoBehaviour
{
EnemyAI enemyAi;
void Start()
{
GameObject gob;
gob = GameObject.FindGameObjectWithTag("Enemy");
enemyAi = gob.GetComponent<EnemyAI>();
}
public void OnTriggerStay(Collider other)
{
if (other.gameObject.tag == "Player")
{
enemyAi.SetAlertStatus(false);
enemyAi.ReturnToPost();
}
}
}
You are getting a disabled instance of EnemyAI. Instead, use FindGameObjectsWithTag then iterate through all of them and add their EnemyAI to a list which you can iterate through when necessary. By the way, it's better to use CompareTag when possible to reduce garbage:
public class SafeSpots : MonoBehaviour
{
List<EnemyAI> enemyAis;
void Start()
{
GameObject[] enemies= GameObject.FindGameObjectsWithTag("Enemy");
enemyAis = new List<EnemyAI>();
foreach (GameObject enemy in enemies)
{
enemyAis.Add(enemy.GetComponent<EnemyAI>());
}
}
public void OnTriggerStay(Collider other)
{
if (other.CompareTag("Player"))
{
foreach(EnemyAI enemyAi in enemyAis)
{
enemyAi.SetAlertStatus(false);
enemyAi.ReturnToPost();
}
}
}
}

Getting a NullReferenceException when accessing a component that I've created and retrieved

First of all, how original of me to post another dreaded
NullReferenceException: Object reference not set to an instance of an object
but I have scoured the web looking for a solution for like 2 hours now and have come up with nothing... Here is are the two scripts i have :
GROUNDED:
using UnityEngine;
using System.Collections;
public class GroundCheck : MonoBehaviour {
private Player player;
void Start()
{
player = GetComponent<Player>();
}
void OnTriggerEnter2D(Collider2D col)
{
player.grounded = true;
}
void OnTriggerExit2D(Collider2D col)
{
player.grounded = false;
}
}
PLAYER:
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour {
public float maxSpeed = 3;
public float speed = 50f;
public float jumpPower = 150f;
public bool grounded;
private Rigidbody2D rb2d;
private Animator anim;
// Use this for initialization
void Start () {
rb2d = gameObject.GetComponent<Rigidbody2D>();
anim = gameObject.GetComponent<Animator>();
}
// Update is called once per frame
void Update () {
anim.SetBool("Grounded", grounded);
anim.SetFloat("Speed", Mathf.Abs(Input.GetAxis("Horizontal")));
}
void FixedUpdate()
{
float h = Input.GetAxis("Horizontal");
rb2d.AddForce((Vector2.right * speed) * h);
if (rb2d.velocity.x > maxSpeed)
{
rb2d.velocity = new Vector2(maxSpeed, rb2d.velocity.y);
}
if (rb2d.velocity.x < -maxSpeed)
{
rb2d.velocity = new Vector2(-maxSpeed, rb2d.velocity.y);
}
}
}
The exact error is:
NullReferenceException: Object reference not set to an instance of an object
GroundCheck.OnTriggerEnter2D (UnityEngine.Collider2D col)
(atAssets/scripts/GroundCheck.cs:15)
Here is my scene:
Here is my boxcollider (if it helps):
If both of the GroundCheck and PLAYER classes are on same GameObject then change the Start() method of GroundCheck class like this:
void Start()
{
player = gameObject.GetComponent<Player>();
}
If they are not on same GameObject then use the following code:
void Start()
{
GameObject playerObj = GameObject.Find("Name of gameObject that player script is in that");
player = playerObj.GetComponent<Player>();
}
In PLAYER class add static modifier to defination of grounded:
public static bool grounded;
Your ground check script isn't on the same object as the player script, that means you can't use getcomponent to get the player script. So you haven't set the player var to anything which is causing the error. Set the player var to the gameobject that has the player script in the editor then in your start method use player.GetComponent();
void OnTriggerEnter2D(Collider2D col) <-- in collider param request gameObject, getcomponent to col is prefered, only control if object collision is player. col.gameObject.getcomponent<Player>().grounded=true;
if(col.Name.Equals("Player")
{
col.gameObject.getcomponent<Player>().grounded=true;
}
I had a similar problem. I hope it's helps
http://docs.unity3d.com/ScriptReference/Collision2D.html
Collider2d have gameobject component, trigger enter get Collider this object.
in http://docs.unity3d.com/ScriptReference/Collider2D.OnCollisionEnter2D.html
see example use in collider (not trigger is only example) to use, acces gameObject.
Not necessary findtag when object(player) is passing for parameter in event OnTriggerEnter, Exit or Stay

Categories

Resources