Squanchy
Squanchy

Reputation: 133

Understanding Unity's GameObject.Find(), GetComponent() and objects recycling

New to unity.

So I created a simple a simple muzzle flash particle animation that is supposed to be displayed on enemies gun when the player gets close to him, simulating a shot without the actual bullet. However I get a null reference exception in this part muzzleFlash.Play(); I believe it's because I am not actually getting the muzzle flash component in the start function with the code I have, actually I know that is it after going to in to debug mode I found out. I am having a really hard time figuring out how to access that component. Below is my code and I'm also posting a picture of my hierarchy. Thanks in advance.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StaticShootingEnemy : MonoBehaviour
{

    [SerializeField] private float _range = 12f;
    private Transform _player;

    private bool _alive;
    private float _distance;
    private ParticleSystem muzzleFlash;

    // Use this for initialization
    void Start()
    {
        _player = GameObject.Find("Player").transform;
        _alive = true;
        muzzleFlash = (ParticleSystem)this.gameObject.GetComponent("muzzleFLash");

    }

    // Update is called once per frame
    void Update()
    {
        _distance = Vector3.Distance(this.transform.position, _player.transform.position);
        if (_alive && _distance < _range)
            AttackPlayer();
    }


    private void AttackPlayer()
    {
        //Turning enemy to look at player
        transform.LookAt(_player);
        Ray ray = new Ray(transform.position, transform.forward);
        RaycastHit hit;
        if (Physics.SphereCast(ray, 0.75f, out hit))
        {
            //TODO: Fix enemy shooting fast when gettting close to him.
            GameObject hitObject = hit.transform.gameObject;
            if (hitObject.GetComponent<PlayerController>())
            {
                muzzleFlash.Play();
                Debug.Log("Player Hit!");
            }
            else
                muzzleFlash.Stop();
        }
    }

    public void SetAlive(bool alive)
    {
        _alive = alive;
    }

}

Hierarchy

Upvotes: 1

Views: 2106

Answers (3)

earthQuake
earthQuake

Reputation: 106

So back to your comment, you could implement something like a pool for your muzzle flashes.

public class MuzzleFlashEffect : MonoBehaviour
{
    [SerializeField] private ParticleSystem particleEffect;
    private Queue<MuzzleFlashEffect> poolQueue;

    public void SetPoolQueue(Queue<MuzzleFlashEffect> queue)
    {
        poolQueue = queue;
    }

    public void Play()
    {
        StartCoroutine(Playing());
    }

    private IEnumerator Playing()
    {
        particleEffect.Play();

        while (particleEffect.isPlaying)
        {
            yield return null; // wait until particle animation is done, then recycle effect
        }

        particleEffect.Stop();
        poolQueue.Enqueue(this);  // recycle this effect
    }

    // you can do the same thing for Animation as well, or even write some abstract PoolableVFX class that would be usefull for Animation , ParticleSystems etc.. 
}

//assume you have some game controller that manage what is going on in the scene
public class GameController : MonoBehaviour
{
    [SerializeField] private MuzzleFlashEffect muzzleFlashPrefab;
    private Queue<MuzzleFlashEffect> poolQueue = new Queue<MuzzleFlashEffect>(10); // 10 is enough i guess and it's good to set it at instantiation to avoid memory fragmentation

    private MuzzleFlashEffect GetMuzzleFlash(Vector3 pos, Quaternion rot)
    {
        MuzzleFlashEffect muzzleFlash;

        // if we already have some effects, then play them, otherwise make a new one and recycle it then
        if (poolQueue.Count > 0)
        {
            muzzleFlash = poolQueue.Dequeue();
        }
        else
        {
            muzzleFlash = Instantiate(muzzleFlashPrefab);
            muzzleFlash.SetPoolQueue(poolQueue);
        }

        muzzleFlash.transform.position = pos;
        muzzleFlash.transform.rotation = rot;

        return muzzleFlash;
    }

    void Update()
    {
        // your fancy logic ...


        GameObject mutantGunEnd = new GameObject("mutant");
        //assume that here you want your muzzle flash effect, so you do:

        var muzzleFlash = GetMuzzleFlash(mutantGunEnd.transform.position, mutantGunEnd.transform.rotation); // or you might want to pass mutantGunEnd.transform.forward instead, it depends... 
        muzzleFlash.Play();


        // your fancy logic ...
    }
}

So, in this case you have only as many instance of ParticleEffect as you need and saving some resources. You could also create a universal generic pool for any type of object you want to recycle. (you want to recycle instead of instantiation, cuz Instantiation is cpu expensive). M.b this is a bit overkill here, but i just wanted to share how would i think about this here

Upvotes: 1

slaphshot33324
slaphshot33324

Reputation: 658

What component is the staticshootingenemy script on? if it is not on the same component as the particle system then its not finding it because this.gameObject.GetComponent("muzzleFLash") does not exist on that component. You can use GameObject.Find("muzzleFLash") to search for the particle system.

Upvotes: 1

earthQuake
earthQuake

Reputation: 106

You probably have an object "muzzleFlash" as child to object your script attached to. So, in this case you'd better have a reference to your ParticleSystem object that is called muzzleFlash.

[SerializeField] private ParticleSystem muzzleFlash; // drag and drop your ParticleSystem muzzleFlash in inspector

or at least you could find that muzzleFlash like this

GameObject muzzleFlashObj = GameObject.Find("muzzleFlash"); ParticleSystem muzzleFlash = muzzleFlashObj.GetComponent<ParticleSystem>();

In your case it's null because there is probably no component that is called MuzzleFlash on that object. The component you want to get is ParticleSystem.

Upvotes: 1

Related Questions