How to make killed enemies reappear when the master timeline is on loop?

Hey Everyone,

Since the player path was in a loop in the waypoint circuit, I wanted to implement the same with timelines.
So I have put the Master timeline on loop. But I am facing a problem when it loops.
I want the enemy ships that I have already destroyed in the enemy waves to reappear when the master timeline goes on loop and the enemy waves repeat.
Currently, only the enemies that are not destroyed appear when the waves repeat.

Gameplay video linked below:
Current Gameplay video

The code part of my game is exactly the same as done by Ben in the tutorials.
Current KillEnemy method in Enemy.cs is as below:

private void KillEnemy()
    {
        GameObject fx = Instantiate(deathFX, transform.position, Quaternion.identity);
        fx.transform.parent = parent;
        Destroy(gameObject);
    }

Instead of Destroy I am thinking of just disabling the gameObject and enabling it again later. I think I can achieve this if I can somehow access the control tracks of the enemy waves in code and whenever the track starts or is about to start I enable all the enemies the particular wave has and then upon hit by the player shipā€™s bullets the enemies are just disabled.

But I donā€™t know how to do that. There must be a better solution to this problem than the one I am thinking.

Please help me in resolving this problem.

Hi Dhruv,

Destroyed is destroyed. If you want to make them appear again, donā€™t destroy them. Disable them. When the player ship reaches the end of the Timeline path, you could enable the enemies again, reset their respective health values, etc.

Also please feel free to ask our helpful community of students for advice in our official Discord chat.

Hi Nina,

I understand that i will have to disable and re-enable them.
But as soon as the timeline path ends it will again start from the beginning as I have turned looping on.

So how do I access and keep track of the timelineā€™s current time from the code?
Thatā€™s where I am finding it difficult.

Hmm :thinking: maybe this could Work.
In top

At Start Set

gameObject.setActive(true);

At

private void KillEnemy()
    {
        GameObject fx = Instantiate(deathFX, transform.position, Quaternion.identity);
        fx.transform.parent = parent;
        gameObject.setActive(false);
    }

Hi BlackImpulse,
Thanks for your response.
Sadly, this alone does not work. It deactivates the enemy on kill but when the master timeline repeats and the enemy wave of that enemy comes again, the enemy would not be active.
So, I still need a way to activate the enemies of the wave when their animation is about to start. This is where I am stuck.

Hmm yeah i See i Seeā€¦ Hmm

But what ist about this:

private void KillEnemy()
{
GameObject fx = Instantiate(deathFX, transform.position, Quaternion.identity);
fx.transform.parent = parent;
gameObject.setActive(false);
Yield Return Waitforseconds(10)
gameObject.setActive(true)
}

This should disable the enemies and enable Them after 10 Seconds again. If the Timeline repeats, there active and should jump to the Startposition.

1 Like

Hi BlackImpulse,

So, as suggested is made the code as below:

private void KillEnemy()
{
    GameObject fx = Instantiate(deathFX, transform.position, Quaternion.identity);
    fx.transform.parent = parent;
    // Destroy(gameObject);
    gameObject.SetActive(false);
    yield return new WaitForSeconds(10);
    gameObject.SetActive(true);
}

During execution i am getting the below error in console:

Assets/Scripts/Enemy.cs(44,18): error CS1624: The body of Enemy.KillEnemy()' cannot be an iterator block because voidā€™ is not an iterator interface type

Seems to me its related to the return type of the KillEnemy function but not sure.

Also could you please explain this ā€œyield return new WaitForSeconds(10);ā€ statement.
Havenā€™t used or seen such yield return type statements before.

ah hmm iĀ“m not sure but it should be ā€œprivate KillEnemy()ā€ Sorry for that. Void means this function doesnĀ“t return anything, but you return something in yield return new WaitForSeconds(10);

I donĀ“t know, but if iĀ“m right, it comes up later in the coroutines section.
Shortly it means:
Yield I think, thats an accessor. If you use it, you doesnĀ“t have to write IEnumerator
KillEnemy
return return something
new something new
WaitForSeconds it is saying the same like his name. Wait for seconds
(10) in brackets how long in time he should wait.

So, this ā€œyield return new WaitForSeconds (10)ā€ is an order to wait 10 seconds bevor he goes further. Instead of 10 for 10 seconds you can take 5 seconds or something else, too.

Hope IĀ“m right and you can understand it. Im just on my learning tour too^^

Instead of void use IEnumerator,
And call it by StartCoroutine(KillEnemy());

Hmm but then will every enemy blow up on Start or not?

I made it ā€œprivate KillEnemy()ā€ but threw error that a return type is required.

So, i checked in the scripting APIs for WaitForSeconds and made the code as below to remove compilation errors:

private void OnParticleCollision(GameObject other)
{
    ProcessHits();
    if (hits <= 0)
    {
        StartCoroutine(KillEnemy());
    }
}

private void ProcessHits()
{
    scoreBoard.ScoreHit(scorePerHit);
    // todo consider hit FX
    hits--;
}

private IEnumerator KillEnemy()
{
    GameObject fx = Instantiate(deathFX, transform.position, Quaternion.identity);
    fx.transform.parent = parent;
    // Destroy(gameObject);
    gameObject.SetActive(false);
    yield return new WaitForSeconds(10);
    gameObject.SetActive(true);
}

But it now shows this error.

Coroutine couldnā€™t be started because the the game object ā€˜Pincer (0)ā€™ is inactive!
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
Enemy:OnParticleCollision(GameObject) (at Assets/Scripts/Enemy.cs:33)

So it seems we cannot run a coroutine from a script which is on an inactive game object.

try Camera.main.StartCoroutine(ā€¦
or any game object that you can access from that script and it will be active.

Hi 111100,

If I use Camera.main.StartCoroutine(ā€¦ or with any other gameobject, I get the error

Assets/Scripts/Enemy.cs(33,25): error CS1061: Type UnityEngine.Camera' does not contain a definition for StartCoroutineā€™ and no extension method StartCoroutine' of type UnityEngine.Cameraā€™ could be found. Are you missing an assembly reference?

Similar error comes when using other gameobjects as well

Yes, it has StartCoroutine(KillEnemy()) for example on the main camera in a script with a reference to this enemy and there it starts to be complicated.

But what you can try.

private IEnumerator KillEnemy()
{
    MeshRenderer meshRenderer = GetComponent<MeshRenderer>();
    GameObject fx = Instantiate(deathFX, transform.position, Quaternion.identity);
    fx.transform.parent = parent;
    // Destroy(gameObject);
     meshRenderer.enabled = false; 
    gameObject.SetActive(false);
    yield return new WaitForSeconds(10);
    meshRenderer.enabled=true;
   
}

maybe this could work. You donĀ“t deactivate this gameobject, you make it unvisible. In theorieā€¦

Here is my solution! Try it.
Also You have to make enemy OnEnable to reset health and maybe position idk what else.

    private void OnParticleCollision(GameObject other)
    {
        ProcessHits();
        if (hits <= 0)
        {
            EnemyHandler.onEnemyKill?.Invoke(this);
            PlayDeath();
        }
    }

    private void ProcessHits()
    {
        scoreBoard.ScoreHit(scorePerHit);
        // todo consider hit FX
        hits--;
    }

    void PlayDeath()
    {
        GameObject fx = Instantiate(deathFX, transform.position, Quaternion.identity);
        fx.transform.parent = parent;
    }

And here new class. Add it on empty gameObject!

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

public delegate void OnEnemyKill(Enemy enemy);
public class EnemyHandler : MonoBehaviour
{
    public List<Enemy> enemies = new List<Enemy>();
    public static OnEnemyKill onEnemyKill = null;
    public int enemyCount = 0;
    // Start is called before the first frame update
    void Start()
    {
        enemyCount = FindObjectsOfType<Enemy>().Length;
        onEnemyKill = KillEnemy;
    }
    private void OnDisable()
    {
        onEnemyKill -= KillEnemy;
    }

    void KillEnemy(Enemy enemy)
    {
        enemies.Add(enemy);
        enemy.gameObject.SetActive(false);
        if(enemies.Count == enemyCount)
        {
            foreach(Enemy obj in enemies)
            {
                obj.gameObject.SetActive(true);
            }
            enemies.Clear();
        }
    }
}
1 Like

I also had this idea but along with the mesh renderer we will have to deactivate the following:

  1. Afterburner particles attached to the enemy ship in the visual polish lecture
  2. Box Collider of the enemy, otherwise player may collide with the invisible enemy and die.

Disabling the children of the enemy along with the mesh and collider would do the trick for now but it would be a bad coding practice and might cause problems later upon further improving the game

Hi BlackImpulse and 111100,

Good News, I have finally fixed the issue. I mixed up both your suggestions and did this.

Enemy.cs

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

public class Enemy : MonoBehaviour {

[SerializeField] GameObject deathFX;
[SerializeField] Transform parent;
[SerializeField] int scorePerHit = 12;
[SerializeField] int hits = 100;


ScoreBoard scoreBoard;
int hitsLeft;

// Use this for initialization
void Start ()
{
    AddBoxCollider();
    scoreBoard = FindObjectOfType<ScoreBoard>();
    hitsLeft = hits;
}

private void AddBoxCollider()
{
    Collider boxCollider = gameObject.AddComponent<BoxCollider>();
    boxCollider.isTrigger = false;
}

private void OnParticleCollision(GameObject other)
{
    ProcessHits();
    if (hitsLeft <= 0)
    {
        KillEnemy();
    }
}

private void ProcessHits()
{
    scoreBoard.ScoreHit(scorePerHit);
    // todo consider hit FX
    hitsLeft--;
}

private void KillEnemy()
{
    GameObject fx = Instantiate(deathFX, transform.position, Quaternion.identity);
    WaveHandler waveHandler = FindObjectOfType<WaveHandler>();
    fx.transform.parent = parent;
    // Destroy(gameObject);
    gameObject.SetActive(false);
    waveHandler.ResetEnemy(gameObject, this);
}

public void ResetHitsLeft()
{
    hitsLeft = hits;
}

}

I have created a new script ā€œWaveHandler.csā€ which I have added to the game object containing all the enemy waves. The script is as below:

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

public class WaveHandler : MonoBehaviour {

// Use this for initialization
void Start () {
	
}

public void ResetEnemy(GameObject enemyGameObject, Enemy enemyObject)
{
    StartCoroutine(HoldBeforeReset(enemyGameObject,enemyObject));
}

private IEnumerator HoldBeforeReset(GameObject enemyGameObject, Enemy enemyObject)
{
    yield return new WaitForSeconds(10);
    enemyGameObject.SetActive(true);
    enemyObject.ResetHitsLeft();
}

// Update is called once per frame
void Update () {
	
}

}

Thanks guys for all your help.

1 Like

That sounds very good :slight_smile:
IĀ“m happy that this work :smiley: IĀ“m interessted also in wich way, Ben or an other instructor clear this problem

1 Like

I am also interested to know how they would have resolved this.
:slightly_smiling_face:

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms