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);
Related
Alright so I have a collision script which works as in if I collide a gameobject with another object with the script attached it does register a collision.
The collision script I am using:
private void OnCollisionEnter(Collision collision)
{
Debug.Log("collision registered");
}
However when I try to collided my instantiated prefabs with my object with my script attached a collision does not get registered.
Does anyone know why this is? I think it's to do with with the prefabs being a "Moveable" but I can't quite get to the bottom of the problem.
Script attached to the prefabs:
public class Moveable : MonoBehaviour
{
public float _speedMetersPerSecond = 50f;
public float resistance = 10f;
public float current = 0f;
public List<Moveable> moveables = new List<Moveable>();
private Vector3? _destination;
private Vector3 _startPosition;
private float _totalLerpDuration;
private float _elapsedLerpDuration;
private Action _onCompleteCallback;
public GameObject Electron;
public Transform Lightbulb;
public SliderChange SliderScript;
public VMT2Counter script2;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// THE LIGHTBULB PART IS FOR THE SPAWN POINT SO DO NOT DELETE
Moveable NextOnPath = Instantiate(Electron, Lightbulb.position, Quaternion.identity).GetComponent<Moveable>();
moveables.Add(NextOnPath.GetComponent<Moveable>());
MoverController.instance.targets.Add(NextOnPath.GetComponent<Moveable>());
}
if (_destination.HasValue == false)
return;
if (_elapsedLerpDuration >= _totalLerpDuration && _totalLerpDuration > 0)
return;
_elapsedLerpDuration += Time.deltaTime;
float percent = (_elapsedLerpDuration / _totalLerpDuration);
transform.position = Vector3.Lerp(_startPosition, _destination.Value, percent);
if (_elapsedLerpDuration >= _totalLerpDuration)
_onCompleteCallback?.Invoke();
// resistance = SliderScript.slider.value;
// current = script2.PotentialDifference / resistance;
}
public void MoveTo(Vector3 destination, Action onComplete = null)
{
var distanceToNextWaypoint = Vector3.Distance(transform.position, destination);
_totalLerpDuration = distanceToNextWaypoint / _speedMetersPerSecond;
_startPosition = transform.position;
_destination = destination;
_elapsedLerpDuration = 0f;
_onCompleteCallback = onComplete;
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.name == "Resistor")
{
Debug.Log("lol");
_speedMetersPerSecond = 25f;
}
}
}
Another script which goes hand in hand with the Moveable script but is not attached to the prefab:
public class MoverController : MonoBehaviour
{
public List<Moveable> targets;
[SerializeField] private Moveable target;
private List<Transform> _waypoints;
private int _nextWaypointIndex;
public static MoverController instance;
void Awake()
{
if (instance == null)
{
instance = this;
}
else
{
Destroy(gameObject);
return;
}
DontDestroyOnLoad(gameObject);
}
private void OnEnable()
{
MoveToNextWaypoint();
}
private void MoveToNextWaypoint()
{
for (int i = 0; i < targets.Count; i++)
{
if (targets[i] == null) continue;
_waypoints = GetComponentsInChildren<Transform>().ToList();
_waypoints.RemoveAt(0);
var targetWaypointTransform = _waypoints[_nextWaypointIndex];
targets[i].MoveTo(targetWaypointTransform.position, MoveToNextWaypoint);
targets[i].transform.LookAt(_waypoints[_nextWaypointIndex].position);
_nextWaypointIndex++;
if (_nextWaypointIndex >= _waypoints.Count)
_nextWaypointIndex = 0;
}
}
}
[![A picture of the prefab][1]][1]
[1]: https://i.stack.imgur.com/uyuge.png*emphasized text*
Video which shows situation: https://clipchamp.com/watch/MXjnTKl2cqg
The collider is a trigger:
Collider A
Collider B
Event triggered
Collider
Collider
Collision
Collider
Trigger
Trigger
Trigger
Collider
Trigger
Trigger
Trigger
Trigger
To fix this problem, either uncheck the box for it to collide normally, or change your event handler to detect triggers instead of colliders:
private void OnTriggerEnter(Collider other) {
Debug.Log("collision registered");
}
See here for more https://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html :)
It is also worth pointing out that if neither of your objects have rigidbodies on, the event will not fire. If you need it to fire but don't want all of the rigidbody physics, you can add one but tick the isKinematic box.
I would like the Enemy(Monster) to shoot players in Shooting Range of the Enemy and return closest players within the Range
I'm quite new to Unity and currently using Unity 2017,2D at the moment, for now.
Thanks so much for your Help Guys!
public class Monster : MonoBehaviour {
[SerializeField]
GameObject Bullet;
public float fireRate;
float nextFire;
// Use this for initialization
void Start () {
fireRate = 1f;
nextFire = Time.time;
}
// Update is called once per frame
void Update () {
CheckIfTimeToFire ();
}
void CheckIfTimeToFire()
{
if (Time.time > nextFire)
{
Instantiate (Bullet, transform.position, Quaternion.identity);
nextFire = Time.time + fireRate;
}
}
}
Here is the Bullet Script
public class Bullet : MonoBehaviour {
public float moveSpeed = 3f;
Rigidbody2D rb;
PlayerAI target;
Vector2 moveDirection;
// Use this for initialization
void Start () {
rb = GetComponent<Rigidbody2D> ();
target = GameObject.FindObjectOfType<PlayerAI>();
moveDirection = (target.transform.position - transform.position).normalized * moveSpeed;
rb.velocity = new Vector2 (moveDirection.x, moveDirection.y);
Destroy (this.gameObject, 2);
}
void OnTriggerEnter2D (Collider2D col)
{
if (col.gameObject.name.Equals ("Player"))
{
Debug.Log ("Hit!");
Destroy (gameObject);
}
}
}
Assuming you have a small number of players, a convenient method would be to hold a reference to players as a collection and iterate to get transform.position of each player. Then you can check the distance to the monster to get the minimum distance.
I also suggest doing the logical check on Monster rather than on Bullet, and pass target to Bullet. So that you can choose not to instantiate a bullet if no player is in range.
public class Monster : MonoBehaviour
{
...
public float range;
private PlayerAI[] players;
...
private void Awake()
{
players = FindObjectsOfType<PlayerAI>();
}
...
private void CheckIfTimeToFire()
{
if (Time.time < nextFire)
{
return;
}
PlayerAI closestPlayer = null;
float closestPlayerDistance = float.MaxValue;
foreach (PlayerAI player in players)
{
float playerDistance = Vector3.Distance(transform.position, player.transform.position);
if (playerDistance > range ||
playerDistance > closestPlayerDistance)
{
// Skip to next player.
continue;
}
closestPlayer = player;
closestPlayerDistance = playerDistance;
}
if (closestPlayer == null)
{
// No player in range. Do nothing.
return;
}
GameObject bullet = Instantiate (Bullet, transform.position, Quaternion.identity);
bullet.GetComponent<Bullet>().Initialize(closestPlayer.transform);
nextFire = Time.time + fireRate;
}
...
}
public class Bullet : MonoBehaviour
{
...
private Transform target;
...
public void Initialize(Transform target)
{
this.target = target;
}
...
}
In the example, I get the players on Awake() of a Monster, but if you are instantiating them in runtime you can hold the PlayerAI collection in a separate manager class. In this case a list could be more useful.
public void PlayerManager : MonoBehaviour
{
public List<PlayerAI> Players { get; private set; }
...
private void Awake()
{
while (playerCount < totalPlayerCount)
{
SpawnPlayer();
}
}
private void SpawnPlayer()
{
PlayerAI player = Instantiate(player, ...);
Players.Add(player);
}
// Remove a dead player, etc.
private void OnPlayerDied(PlayerAI player)
{
Players.Remove(player);
}
...
}
public class Monster : MonoBehaviour
{
...
private PlayerManager playerManager;
...
private void Awake()
{
playerManager = FindObjectOfType<PlayerManager>();
}
void CheckIfTimeToFire()
{
...
foreach (PlayerAI player in playerManager.Players)
{
...
}
...
}
}
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();
}
}
}
}
Hey I am trying to change a float when my player collides with a object. I tried many ways of reference but only got null when trying to debug I came up with this so far. I want to get the gameobject that contains the player script meaning the player and after I want to get the component script tankmovement to change the variable in it.
Getting the null reference error in the powerups script line 79 reset function Tank=GameObject.FindWithTag("Player")
using System.Collections.Generic;
using UnityEngine;
public class PowerUp : MonoBehaviour {
public bool boosting = false;
public GameObject effect;
public AudioSource clip;
public GameObject Tank;
private void Start()
{
Tank = GameObject.Find("Tank(Clone)");
TankMovement script = GetComponent<TankMovement>();
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
if (!boosting)
{
clip.Play();
GameObject explosion = Instantiate(effect, transform.position, transform.rotation);
Destroy(explosion, 2);
GetComponent<MeshRenderer>().enabled = false;
GetComponent<Collider>().enabled = false;
Tank.GetComponent<TankMovement>().m_Speed = 20f;
//TankMovement.m_Speed = 20f;
boosting = true;
Debug.Log(boosting);
StartCoroutine(coolDown());
}
}
private IEnumerator coolDown()
{
if (boosting == true)
{
yield return new WaitForSeconds(4);
{
boosting = false;
GetComponent<MeshRenderer>().enabled = true;
GetComponent<Collider>().enabled = true;
Debug.Log(boosting);
// Destroy(gameObject);
}
}
}
void reset()
{
//TankMovement.m_Speed = 12f;
TankMovement collidedMovement = Tank.gameObject.GetComponent<TankMovement>();
collidedMovement.m_Speed = 12f;
//TankMovement1.m_Speed1 = 12f;
}
}
}
Trying to call on my m_Speed float in the player script to boost the speed of my player when he collides with it. How would you get a proper reference since my player is a prefab.
Tank script
using UnityEngine;
public class TankMovement : MonoBehaviour
{
public int m_PlayerNumber = 1;
public float m_Speed = 12f;
public float m_TurnSpeed = 180f;
public AudioSource m_MovementAudio;
public AudioClip m_EngineIdling;
public AudioClip m_EngineDriving;
public float m_PitchRange = 0.2f;
private string m_MovementAxisName;
private string m_TurnAxisName;
private Rigidbody m_Rigidbody;
private float m_MovementInputValue;
private float m_TurnInputValue;
private float m_OriginalPitch;
private void Awake()
{
m_Rigidbody = GetComponent<Rigidbody>();
}
private void OnEnable ()
{
m_Rigidbody.isKinematic = false;
m_MovementInputValue = 0f;
m_TurnInputValue = 0f;
}
private void OnDisable ()
{
m_Rigidbody.isKinematic = true;
}
private void Start()
{
m_MovementAxisName = "Vertical" + m_PlayerNumber;
m_TurnAxisName = "Horizontal" + m_PlayerNumber;
m_OriginalPitch = m_MovementAudio.pitch;
}
private void Update()
{
// Store the player's input and make sure the audio for the engine is playing.
m_MovementInputValue = Input.GetAxis(m_MovementAxisName);
m_TurnInputValue = Input.GetAxis(m_TurnAxisName);
EngineAudio();
}
private void EngineAudio()
{
// Play the correct audio clip based on whether or not the tank is moving and what audio is currently playing.
if (Mathf.Abs(m_MovementInputValue) < 0.1f && Mathf.Abs(m_TurnInputValue) < 0.1f)
{
if (m_MovementAudio.clip == m_EngineDriving)
{
m_MovementAudio.clip = m_EngineIdling;
m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);
m_MovementAudio.Play();
}
}
else
{
if (m_MovementAudio.clip == m_EngineIdling)
{
m_MovementAudio.clip = m_EngineDriving;
m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);
m_MovementAudio.Play();
}
}
}
private void FixedUpdate()
{
// Move and turn the tank.
Move();
Turn();
}
private void Move()
{
// Adjust the position of the tank based on the player's input.
Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime;
m_Rigidbody.MovePosition(m_Rigidbody.position + movement);
}
private void Turn()
{
// Adjust the rotation of the tank based on the player's input.
float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;
Quaternion turnRotation = Quaternion.Euler(0f, turn, 0);
m_Rigidbody.MoveRotation(m_Rigidbody.rotation * turnRotation);
}
}
Since the TankMovement component you need to access is attached to the GameObject that is colliding with the power, you can get the TankMovement component you need to change by using other.gameObject.GetComponent<TankMovement>():
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
if (!boosting)
{
// stuff
TankMovement collidedMovement = other.gameObject.GetComponent<TankMovement>();
collidedMovement.m_Speed = 20f;
// more stuff
}
}
}
TL;DR: Why can I only "kill" my first enemy, but the rest aren't affected by my attempt to turn their components off?
Overview
Hi Guys and Gals, I'm running into issues with my AI switching to Ragdoll when the player kills them, but more specifically it's not deactivating any other components either.
Basically, I have an AI script that runs a statemachine via IEnumerators and coroutines, I also have a RagdollDeath script just to keep code seperate.
Ideally, when the player shoots the enemy, they switch to a ragdoll state.
How I accomplish this
Besides turning off all the animator, navmesh, and other components when health reaches 0, I also turn off ALL rigid bodies using
void IsKinematic(bool newvalue)
{
foreach (Rigidbody rb in bodyparts)
{
rb.isKinematic = newvalue;
}
}
this creates a beautiful and seamless ragdoll transition from animation.
My Issue
The issue i'm running into, is that when I fire at an enemy, they do exactly as expected, but when I fire at another enemy, it doesn't run my script at all, even though I can see it running via using Print("Something") prompts. I have made sure to prefab my enemy and apply changes to said prefab.
What is even stranger, is that if I clone 2 enemies, and shoot the new one, the first one will go ragdoll across the level! It's almost as if monobehavior is not working. Any insight into what may be causing this would be greatly appreciated.
Full Code that is causing issues
public class ZombieStateMachine : MonoBehaviour {
[SerializeField] GameObject player;
[SerializeField] GameObject los;
[SerializeField] GameObject[] waypoints;
[SerializeField] int timeBetweenWaypoints = 1;
[SerializeField] AudioSource jumpyscare;
private int health = 100;
private SuspenseAudioScript suspensescript;
private NavMeshAgent agent;
public bool canSeePlayer;
public float distanceBetween;
public string routine = "null";
private Animator animator;
public bool isAttacking = false;
private ShootScript shootscript;
private Rigidbody[] bodyparts;
private CapsuleCollider capsule;
void IsKinematic(bool newvalue)
{
foreach (Rigidbody rb in bodyparts)
{
rb.isKinematic = newvalue;
}
}
// Use this for initialization
void Start () {
shootscript = GameObject.FindGameObjectWithTag("Player").GetComponent<ShootScript>();
suspensescript = GetComponent<SuspenseAudioScript>();
animator = GetComponent<Animator>();
agent = GetComponent<NavMeshAgent>();
StartCoroutine(Eyesite());
StartCoroutine(Wander());
bodyparts = GetComponentsInChildren<Rigidbody>();
capsule = GetComponent<CapsuleCollider>();
IsKinematic(true);
}
public void KillZombie()
{
this.StopAllCoroutines();
IsKinematic(false);
animator.enabled = false;
agent.enabled = false;
capsule.enabled = false;
this.enabled = false;
}
Here is the accompanying shoot script
public class ShootScript : MonoBehaviour {
[SerializeField] public int health = 100;
[SerializeField] AudioSource gunshotsound;
[SerializeField] Light gunshotflash;
public float impactforce = 2f;
private ZombieStateMachine enemyscript;
private Rigidbody rb;
private CharacterController m_CharacterController;
private Camera cam;
private CapsuleCollider enemycol;
public UnityStandardAssets.Characters.FirstPerson.FirstPersonController fpscontrol;
// Use this for initialization
void Start () {
fpscontrol = GetComponent<UnityStandardAssets.Characters.FirstPerson.FirstPersonController>();
enemycol = GameObject.FindGameObjectWithTag("Enemy").GetComponent<CapsuleCollider>();
enemyscript = GameObject.FindGameObjectWithTag("Enemy").GetComponent<ZombieStateMachine>();
cam = GetComponentInChildren<Camera>();
rb = GetComponent<Rigidbody>();
m_CharacterController = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update () {
Shoot();
}
public void Shoot()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
Debug.DrawRay(cam.transform.position, cam.transform.forward, Color.red, 1.0f);
RaycastHit hit;
if (Physics.Raycast(cam.transform.position, cam.transform.forward, out hit, 2000f))
{
if(hit.transform.tag == "Enemy")
{
enemyscript.KillZombie();
gunshotsound.Play();
StartCoroutine(GunshotFlash());
}
else
{
gunshotsound.Play();
StartCoroutine(GunshotFlash());
print("You MIssed!");
}
}
}
}
IEnumerator GunshotFlash()
{
while (true)
{
gunshotflash.enabled = true;
yield return new WaitForSeconds(0.05f);
gunshotflash.enabled = false;
yield return new WaitForSeconds(1);
break;
}
}
public void PlayerDeath()
{
rb.AddForce(this.transform.forward * 2);
rb.isKinematic = false;
m_CharacterController.enabled = false;
fpscontrol.enabled = false;
//rb.useGravity = true;
}
}
Not sure if you ever came across the solution to this but I believe the problem is within your start method for the Shoot script.
enemycol = GameObject.FindGameObjectWithTag("Enemy").GetComponent<CapsuleCollider>();
Doing this will return the first game object that has the "Enemy" tag, it would be be better to just do:
enemycol = GetComponent<CapsuleCollider>();
Probably the same thing needs to be done with the line right under that, the enemyscript variable:
enemyscript = GameObject.FindGameObjectWithTag("Enemy").GetComponent<ZombieStateMachine>();
change to
enemyscript = GetComponent<ZombieStateMachine>();