StuckInPhDNoMore
StuckInPhDNoMore

Reputation: 2689

Possible to create a more efficient homing missile system in Unity3D?

I've written a homing missile script for my action platformer and I cant help but notice that this might not be the most efficient of weapons.

void Start()
    {
        target = GameObject.FindGameObjectWithTag("Enemy");
        rb = GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        direction = (target.transform.position - transform.position).normalized;
        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        rotatetoTarget = Quaternion.AngleAxis(angle, Vector3.forward);
        transform.rotation = Quaternion.Slerp(transform.rotation, rotatetoTarget, Time.deltaTime * rotationSpeed);
        rb.velocity = new Vector2(direction.x * fowrardSpeed, direction.y * fowrardSpeed);
    }

It works perfectly but there are a few issues with it. How do I ask it to randomly select a different enemy every time it is instantiated? Rather than all going for one enemy?

Also once that enemy has died, it should choose a new target, but wont performing GameObject.Find() in Update() be a bad thing? I understand that GameObject.Find() is to be avoided as it goes over all of the gameObjects in the scene until it finds what it is looking for, and if need be should only be used in Start(). Now I had to use GameObject.Find() when the weapon instantiates as I could not find of any other way to locate the target for the weapon. So is there a better way to chose a new target once that target is destroyed? My game is a game where reaction time matters and I dont want to create any unnecessary lag due to this weapon

Thank you

Upvotes: 3

Views: 1344

Answers (1)

WQYeo
WQYeo

Reputation: 4071

You could have an EnemyCache and MissileSpawner script.

Your EnemyCache (most likely a singleton) would have a list of enemies in the world;
Enemies are added into that list when they are spawned, and removed from that list when they die.

Your MissileSpawner (or something that spawns the projectiles) script would then need to assign the missiles a target each time it spawns a new missile.
It can fetch a target for the new missile via the EnemyCache. (You can even filter the list to get the closest target too!)

Finally, your missile script can fetch a new target from the EnemyCache if the old target died.

Overall, it should look similar to this:

YourMissile.cs

public class YourMissile : MonoBehaviour {
    // ...

    GameObject target;

    public void Update() {
        // target is destroyed or gone
        if (target == null) {
            SetTargetFromEnemyCache();
        }
    }

    private void SetTargetFromEnemyCache() {
        if (EnemyCache.Instance.TryGetFirstEnemy(out Enemy newTarget)) {
            target = newTarget.gameObject;
        } else {
            Debug.LogWarning("No enemy for missile to target!");
        }
    }

    // ...

    public void SetTarget(GameObject targetToSet) {
        target = targetToSet;
    }

    // ...
}

EnemyCache.cs

public class EnemyCache : MonoBehaviour {
    // Singleton
    public static EnemyCache Instance { get; private set; }

    private List<Enemy> cachedEnemies;

    private void Awake() {
        Instance = this;
        cachedEnemies = new List<Enemy>();
        // TODO: Subscribe to a delegate or event, that adds into the 'cachedEnemy' whenever an enemies spawned.
        // Also, an event that removes from 'cachedEnemy' when an enemy dies too.
    }

    // ...

    /// <summary>
    /// Tries to fetch the first enemy in the cache.
    /// </summary>
    /// <param name="enemy">The fetched enemy; Null if there was nothing in cache</param>
    /// <returns>True if there is an enemy fetched; False if none</returns>
    public bool TryGetFirstEnemy(out Enemy enemy) {
        if (cachedEnemies.Count > 0) {
            enemy = cachedEnemies[0];
            return true;
        }
        enemy = null;
        return false;
    }
}

YourMissileSpawner.cs

public class YourMissileSpawner : MonoBehaviour {
    [SerializeField]
    private YourMissile missilePrefab;

    // ...

    public void SpawnProjectile() {
        YourMissile newMissile = Instantiate(missilePrefab);

        // Set position... etc...

        // Try to get a target for the new missile
        if (EnemyCache.Instance.TryGetFirstEnemy(out Enemy enemyToTarget)) {
            newMissile.SetTarget(enemyToTarget.gameObject);
        } else {
            Debug.LogWarning("No enemy for newly spawned missile to target!");
        }

        // NOTE: The above is optional,
        // since 'YourMissile' sets a new target from EnemyCache
        // if the target is null; (checks per update)

        // But I included it here in case you want it to filter
        // what kind of enemy it needs to target on start :)
    }
}

Upvotes: 2

Related Questions