I have a List that contains all the enemy GameObjects, when an enemy dies their GameObject is destroyed however this leaves a null reference in my script as there is a missing GameObject in the List. To fix this I am aware I need to remove the GameObject from the List before the object is destroyed. However, where I deal with the enemy's health and destroying the GameObject is on a different GameObject to the initial script.
I'm unsure of how to go about calling the first script so I can reference the List used.
Script1: Contains the original List.
Script 2: The script I want to destroy the GameObject in and reference from.
public class FriendlyManager : MonoBehaviour
{
public NavMeshAgent navMeshAgent;
public Transform player;
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()
{
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;
// attack player.
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 class Damageable : MonoBehaviour
{
[SerializeField] float maxHealth = 100f;
float currentHealth;
public static int numOfEnemies = 5;
Rigidbody rb;
// get list.
public void Start()
{
rb = GetComponent<Rigidbody>();
currentHealth = maxHealth;
}
public void TakeDamage(float damage)
{
currentHealth -= damage;
if (currentHealth <= 0)
{
Die();
}
}
public void Die()
{
numOfEnemies--;
List.Remove(gameObject);
Destroy(gameObject);
}
}
CodePudding user response:
use Singleton
Pattern:
public class FriendlyManager : MonoBehaviour
{
public static FriendlyManager singleton; // you can save single user class on this field.
public List<GameObject> enemies;
void Start()
{
singleton = this;
}
}
on enemy class:
public void Die()
{
FriendlyManager.singleton.enemies.Remove(gameObject); // remove it from singleton class
Destroy(gameObject);
}
CodePudding user response:
Since your FriendlyManager
contains a list of all enemies, you can pass a reference to them, so they know to which manager they belong to. This also removes the necessity for having a static int
lying around, as they can just get the amount of enemies through a property.
public class FriendlyManager : MonoBehaviour
{
[SerializeField]
private List<Damagable> enemies;
public int EnemyCount => enemies.Count;
private void Start()
{
for (int i = 0; i < enemies.Count; i )
enemies.Manager = this;
}
public void EnemyDied(Damagable enemy)
{
enemies.Remove(enemy);
// Maybe do more notifications here
}
}
public class Damageable : MonoBehaviour
{
public FriendlyManager Manager { get; set; }
public void Die()
{
Manager.EnemyDied(this);
Destroy(gameObject);
}
}
A second option would be to use events. However, classic C# events can be tricky in Unity, because they tend to leave dangling references, keeping objects alive despite them being already destroyed by the engine. However, this can make it even more easier to decouple your logic while still keeping the information flow.
public class Damageable : MonoBehaviour
{
public delegate void OnDiedHandler(Damagable sender);
public event OnDiedHandler Died;
public void Die()
{
Died?.Invoke(this);
Destroy(gameObject);
}
}
public class FriendlyManager : MonoBehaviour
{
[SerializeField]
private List<Damagable> enemies;
public int EnemyCount => enemies.Count;
private void Start()
{
for (int i = 0; i < enemies.Count; i )
enemies.Died = EnemyDied;
}
private void EnemyDied(Damagable enemy)
{
enemies.Remove(enemy);
// Maybe do more notifications here
}
}