How to Make Enemy Attack When In Range of The Player? [duplicate] - c#

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 1 year ago.
So my game is a 2D top down movement game and my script does make my enemy attack but it constantly loops the attack animation because I obviously don't know what EXACTLY to put code wise to make the enemy attack when in range of the player to do damage instead of letting him constantly loop. Also i seem to be getting an error when i get close to my enemy as of right now it says
NullReferenceException: Object reference not set to an instance of an object
EnemyCombat.Attack () (at Assets/EnemyCombat.cs:36)
EnemyCombat.Update () (at Assets/EnemyCombat.cs:25)
Also, Here is the EnemyCombat script
enemy attausing System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyCombat : MonoBehaviour
{
public Animator animator;
public Transform AttackPoint;
public float attackRange = 0.5f;
public LayerMask enemyLayers;
public int attackDamage = 5;
public float attackRate = 2f;
float nextAttackTime = 0f;
// Update is called once per frame
void Update()
{
if (Time.time >= nextAttackTime)
{
Attack();
nextAttackTime = Time.time + 1f / attackRate;
}
}
void Attack()
{
animator.SetTrigger("Attack");
Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(AttackPoint.position, attackRange, enemyLayers);
foreach (Collider2D enemy in hitEnemies)
{
enemy.GetComponent<Enemy>().TakeDamage(attackDamage);
}
}
void OnDrawGizmosSelected()
{
if (AttackPoint == null)
return;
Gizmos.DrawWireSphere(AttackPoint.position, attackRange);
}
}

To fix your endless attack loop:
// Update is called once per frame
void Update()
{
if (attackRate >= nextAttackTime) /* checks that nextAttackTime is less than or equal to the attackRate */
{
Attack();
nextAttackTime = Time.deltaTime * 5f; // adds 5 seconds to nextAttackTime
}
else
{
nextAttackTime -= Time.deltaTime; /* if nextAttackTime is greater than the attackRate, subtract one from nextAttackTime. this only happens once per second because you use Time.deltaTime */
}
}

From that NullReference Error it looks like the major problem you're having is that there is no actual point in the code or in the game hierarchy that you are telling your script which gameObject it is referring to.
Line 36 is trying to retrieve that information with .GetComponent<Enemy()>, so you need to provide that reference.
You could do this in the script fairly easily by creating a public variable that you can drag the enemy gameObject into in your hierarchy in Unity.
Try something like:
public GameObject enemyObject;
This will create a variable in the script visible in the hierarchy when you select the script which you can drag the appropriate gameObject into.
There might need to be some adjustments to it because I can't see the rest of your code, but this seems to be the issue.
Another option would be trying to manually adding it in:
void Start()
{
GameObject enemyObject = gameObject.GetComponent<Enemy>();
}
this is taken from Unity Scripting Documentation

Related

How can I destroy an object after X seconds? [duplicate]

This question already has answers here:
destroy a specific gameobject after an amount of time in unity
(3 answers)
Closed 2 years ago.
The problem is in that way it will create more and more objects if automatic or if using the mouse.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScifiEffects : MonoBehaviour
{
public GameObject spawnEffect;
public bool automaticFire = false;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (automaticFire == false)
{
if (Input.GetMouseButtonDown(0))
{
GameObject effect = Instantiate(spawnEffect, transform.position, Quaternion.FromToRotation(Vector3.up, spawnEffect.transform.forward)) as GameObject;
}
}
else
{
GameObject effect = Instantiate(spawnEffect, transform.position, Quaternion.FromToRotation(Vector3.up, spawnEffect.transform.forward)) as GameObject;
}
}
}
If I add :
Destroy(effect);
Either in the mouse or if automatic the gameobject will be destroy at once and the effect will not happen. I could use a coroutine but then I will have to call the StartCoroutine inside the Update either if automatic or not and it will start many coroutines.
Destroy takes an optional second parameter
t The optional amount of time to delay before destroying the object.
So you can e.g. simply do
if (Input.GetMouseButtonDown(0))
{
GameObject effect = Instantiate(spawnEffect, transform.position, Quaternion.FromToRotation(Vector3.up, spawnEffect.transform.forward)) as GameObject;
Destroy(effect, 3.5f);
}
To destroy the effect object after 3.5 seconds
Note however that you probably should not Instantiate an object every frame in your else case. Rather you could define a certain interval like e.g.
// adjust in the Inspector
public float maxSpawnPerSecond = 2f;
...
private float timer;
void Update()
{
if (!automaticFire)
{
if (Input.GetMouseButtonDown(0))
{
Spawn();
}
}
else
{
if(maxSpawnPerSecond > 0)
{
timer -= Time.deltaTime;
if(timer <= 0)
{
Spawn();
timer = 1 / maxSpawnPerSecond;
}
}
}
}
private void Spawn()
{
GameObject effect = Instantiate(spawnEffect, transform.position, Quaternion.FromToRotation(Vector3.up, spawnEffect.transform.forward)) as GameObject;
Destroy(effect, 3.5f);
}
There are no downsides to starting a bunch of coroutines other than regular coding issues like infinite loops.
Also coroutines has exactly what you need with a method called WaitForSeconds() that only works in coroutines. As far as I know
void Update()
{
if(true) // Change if statement so you don't create an infinite loop and crash your program
{
StartCoroutine("SpecialEffects");
}
}
IEnumerator SpecialEffects()
{
// Special Effects
yield return new WaitForSeconds(seconds);
// Code that destroys something
}
You can add Thread.Sleep(miliseconds); to wait before it happens. You could also make a Task which sleeps for a while and then does the delete, make sure to await it.

Text is supposed to be displayed when childCount equals 0, but it doesn't

this should be a very simple answer. I'm following a Unity with C# tutorial for making a simple Space Invaders game, and at one point it is shown that when our enemyHolder object has no child objects left (when all enemies are destroyed) the attached text under the winText function should be displayed.
So we have
if (enemyHolder.childCount == 0)
{
winText.enabled = true;
}
When I run the code the text isn't displayed after the enemies are destroyed and no child object is left. It's like the code stops getting read at that point, although the character is still movable and you can generate new shots.
If I create two "Enemy" child objects and tell it to display the winText rather when the childCount reaches 1, it does work.
So why is it not working when the function calls for == 0?
EDIT: Complete class code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EnemyController : MonoBehaviour
{
private Transform enemyHolder;
public float speed;
public GameObject shot;
public Text winText;
public float fireRate = 0.997f;
// Start is called before the first frame update
void Start()
{
winText.enabled = false;
InvokeRepeating("MoveEnemy", 0.1f, 0.3f);
enemyHolder = GetComponent<Transform>();
}
void MoveEnemy()
{
enemyHolder.position += Vector3.right * speed;
foreach (Transform enemy in enemyHolder)
{
if (enemy.position.x < -10.5 || enemy.position.x > 10.5)
{
speed = -speed;
enemyHolder.position += Vector3.down * 0.5f;
return;
}
if (enemy.position.y <= -4)
{
GameOver.isPlayerDead = true;
Time.timeScale = 0;
}
if (enemyHolder.childCount == 1)
{
CancelInvoke();
InvokeRepeating("MoveEnemy", 0.1f, 0.25f);
}
if (enemyHolder.childCount == 0)
{
winText.enabled = true;
}
}
}
}
Your code is inside the void MoveEnemy() function.
I'm assuming your script is attached to in-game enemies. Your code doesn't run because the MoveEnemy function no longer runs if there are no enemies.
So, you need to handle enemy movement and scene handling in different scripts.
The code that checks the enemy holder's number of children should be placed inside a void Update() function. This Update() function should be placed on an object that never gets deleted. Its advantage is that it runs every frame.
As a convention, devs generally use separate empty objects or even the camera to attach scripts which contain Update functions that handle the scene. Good luck!
Read more on Update

Run code on every frame two colliders are in contact

im trying to make it so that if my character continues to touch a gameobject with a damage script the player continuesly gets damaged. instead of this result i only get damaged once when touching the gameobject. there are no error messages. ive tried to replace the if with a while loop and it ended up crashing my game. is there any way to loop a if statement preferably with a way to time it.
if (other.tag == "Player")
{
healthScript.healthPoints -= damage;
}
this is the if statement im trying to loop.
First, I am under the assumption you are using Colliders as triggers with the Is Trigger attribute selected. I am also assuming this is a 2D game. If not, this same approach will work, you will just have to change the methods from 2D to 3D.
You will want to add the OnTriggerEnter2D() and OnTriggerExit2D() methods to your player health script. These will allow us to detect when the player is standing on the damaging object. From here, we will start a coroutine which can be used to deal damage in a timed manner.
using System.Collections;
using UnityEngine;
public class HealthScript : MonoBehaviour
{
public float healthPoints = 100f;
public float damage = 5f;
public bool OnDamagingObject = false;
IEnumerator DealDamage()
{
while (OnDamagingObject)
{
healthPoints -= damage;
yield return new WaitForSeconds(1f);
}
}
void OnTriggerEnter2D(Collider2D hitInfo)
{
GameObject collider = hitInfo.gameObject;
if (collider.tag == "DamagingObject")
{
OnDamagingObject = true;
StartCoroutine(DealDamage());
}
}
void OnTriggerExit2D(Collider2D hitInfo)
{
GameObject collider = hitInfo.gameObject;
if (collider.tag == "DamagingObject")
{
OnDamagingObject = false;
}
}
}
For those who wish to use OnTriggerStay(), I will also provide a solution for that. This time, we will keep the script on the damaging object, following the structure the question asker is using.
using UnityEngine;
public class DamageScript : MonoBehaviour
{
public HealthScript healthScript;
public float damage = 5f;
void OnTriggerStay2D(Collider2D hitInfo)
{
GameObject other = hitInfo.gameObject;
if (other.CompareTag("Player"))
{
healthScript.healthPoints -= damage;
}
}
}
As mentioned in the comments of my other answer, implementing both OnTriggerEnter() and OnTriggerExit() allow more control over dealing damage to the player. One of the downsides to using OnTriggerStay() is I am unsure how to deal the damage in a timed manner. Additionally, OnTriggerStay() limits how you can deal damage to the player. If in the future you wanted to give 5 damage to the player when they first touch the damaging object but 2 damage for every second thereafter while touching it, it is not possible to do so using OnTriggerStay().

I want my object to fall when the player is near it

I want my objects to fall when the player got to that scene . My game have a long map and I want them not to fall when I start the game .There is any code for player detection in the view? for the objects to fall?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FallDown : MonoBehaviour
{
public float fallSpeed = 8.0f;
//Variables for starting position and length until reset
private Vector3 _startingPos;
public float FallDistance = 5f;
void Start()
{
transform.Translate(Vector3.down * fallSpeed * Time.deltaTime, Space.World);
// Save starting position
_startingPos = transform.position;
}
void Update()
{
transform.Translate(Vector3.down * fallSpeed * Time.deltaTime, Space.World);
// If the object has fallen longer than
// Starting height + FallDistance from its start position
if (transform.position.y > _startingPos.y + FallDistance)
{
transform.position = _startingPos;
}
}
}
While the answer above is correct, Colliders might not be what you want to implement in this case. Colliders are used to, well, detect collisions, while you want the objects to fall when the player is at a specific distance from them. For this, I'd suggest adding a reference to the player GameObject first:
private GameObject playerRef;
And in the Start function find the player:
playerRef = GameObject.Find("yourPlayerGameObjectNameHere");
Get the GameObjects that you want to fall either by finding them, like above, or by passing a public reference to them through the inspector. Afterwards, you can use Vector3.distance between each GameObject and the player, like so:
if( Vector3.Distance(player.transform.position, fallingObject.transform.position) < yourDistanceHere ){
// Make the object fall
}
Have you tried implementing this behavior using Colliders?
To do it so, all you've got to do is add both a Collider Component and a RigidBody to your player and to the falling objects.
Once you've added them and configured their parameters, you can check the collision by using the method OnColissionEnter. This method will be triggered every time that a collision is detected by the GameObject that is holding the script. In your case, the falling objects should hold it.
private void OnCollisionEnter(Collision other)
{
//MAKE THE OBJECTS FALL
}

Trying to destroy my player using Vector3.distance

I'm trying to destroy my player whenever the explosion particle effect comes within a certain distance of the player. Here is what I tried and this script was added to my explosion prefab:
using UnityEngine;
using System.Collections;
public class ExplosionController : MonoBehaviour {
GameObject playerObj;
Transform player;
Transform playerPos;
// Use this for initialization
void Start () {
player= GameObject.FindGameObjectWithTag("Player").transform;
playerObj = GameObject.FindGameObjectWithTag("Player");
}
void Update()
{
float playerDis = Vector3.Distance(player.position, transform.position);
if (playerDis == 4)
{
Debug.Log(playerDis);
Destroy(playerObj);
}
}
}
My Debug attempt at the bottom condition didn't yield any console output, so I assume I made a mistake in the playerDis variable.
I can't comment because I don't have 50 reputation points yet, but I would change your
if (playerDis == 4)
to
if (playerDis <= 4)
This should catch any collisions that may not exactly equal 4.00.... because Update may not be called fast enough.
With the keyword in your question being 'within'.. You should look for a distance from 0 to 4. So:
void Update()
{
float playerDis = Vector3.Distance(player.position, transform.position);
if (playerDis <= 4)
{
Debug.Log(playerDis);
Destroy(playerObj);
}
}
I assume that your distance is never exactly equal to 4, considering that Vector3.Distance() returns a float.

Categories