This line:
StopCoroutine (ShieldUpgrade ());
won’t do anything. A coroutine returns a unique object which represents your actual method. If you call it again it’s a completely different object. That’s why Unity can’t stop the coroutine that way. You have to save either the IEnumerator that way used to start the coroutine or, which is actually easier, save the “Coroutine” object StartCoroutine returns. Use that object to stop the coroutine:
private Coroutine currentCoroutine = null;
// [...]
if (other.gameObject.tag == "Shield" && health.currentHealth > 0) {
Manager.count -= 1;
Destroy (other.gameObject);
Shield.fillAmount = 1f;
if (currentCoroutine != null)
StopCoroutine (currentCoroutine);
currentCoroutine = StartCoroutine (ShieldUpgrade ());
}
However stopping a coroutine is usually not a good idea. In your case it might work, but in other cases things can go wrong when the coroutine can’t finish it’s thing.
For powerups it’s usually a good idea to simply write a seperate “powerup effect script” which you attach to the player that has the effect of the powerup. In this case you can simply use a public variable as timer which can easily be modified from outside to simply add or reset the timer.
A powerup script would simply activate it’s effect in “Start” and could use Update or a coroutine to drive the counter / timer. Once the timer has finished the script destroys itself.
The actual powerup simply use GetComponent to check if the player already has the powerup effect script attached and if so it can adjust the timer / counter. If not it simply uses AddComponent to attach the script.
Instead of AddComponent it’s also possible to use prefabs for each powerup effect which gets instantiated and added as child to the player. The advantage of this approach is that you can have some visual effects bundled within the prefab and you can generalize the actual powerup script.
public class PowerUp : MonoBehaviour
{
public PowerUpEffect powerUpPrefab; // set in inspector
void OnTriggerEnter(Collider aOther)
{
PowerUpEffect instance = (PowerUpEffect)Instantiate(powerUpPrefab);
instance.transform.parent = aOther.transform.root;
instance.transform.localPosition = Vector3.zero;
instance.transform.localRotation = Quaternion.Identity;
}
// implement your powerup logic like cooldown / respawning / ...
}
public class PowerUpEffect : MonoBehaviour
{
public float effectTime = 8;
public bool retriggerable = true;
public float timer;
public void Init()
{
var type = this.GetType();
var effects = transform.root.GetComponentsInChildren<PowerUpEffect>();
for(int i = 0; i < effects.Length; i++)
{
// if there's already a powerup of the same type
if (effects*.GetType() == type)*
{
if (effects*.OnDuplicate(this))*
{
// there is already a powerup so destroy this one and exit
Destroy(gameObject);
return;
}
else
{
// we allow duplicates so just break out of the loop
break;
}
}
}
// Start the powerup effect
InternalInit();
}
public virtual void ReTriggger()
{
timer = effectTime;
}
public virtual void AddTime(float additionalTime)
{
timer += additionalTime;
}
public virtual void RemoveEffect()
{
timer = -1;
}
// can be overridden to handle duplicates.
// if return value is true the duplicate is destroyed
public virtual bool OnDuplicate(PowerUpEffect aOther)
{
// if it’s retriggerable, retrigger it.
if (retriggerable)
{
ReTrigger();
}
return true;
}
public virtual void OnInitEffect(){};
public virtual void OnDestroyEffect(){};
private void InternalInit()
{
StartCoroutine(LifeTime());
OnInitEffect();
}
IEnumerator LifeTime()
{
timer = effectTime;
while (timer > 0)
{
timer -= Time.deltaTime;
yield return null;
}
OnDestroyEffect();
Destroy(gameObject);
}
}
An actual effect could look like this:
public class ShieldPowerUpEffect : PowerUpEffect
{
public override void OnInitEffect()
{
// use transform.root and GetComponent / GetComponentInChildren to access other components on the player
// modify values of the player’s components here to start the effect
}
public override void OnDestroyEffect()
{
// revert what you’re done above and cancel the effect
}
}
If you want to add the time of the newly picked up power-up instead of retrigger it, just override “OnDuplicate” like this:
public class ShieldPowerUpEffect : PowerUpEffect
{
public override void OnInitEffect()
{
// use transform.root and GetComponent / GetComponentInChildren to access other components on the player
// modify values of the player’s components here to start the effect
}
public override void OnDestroyEffect()
{
// revert what you’re done above and cancel the effect
}
public override bool OnDuplicate(PowerUpEffect aOther)
{
AddTime(effectTime);
return true;
}
}
By overriding “OnDuplicate” and returning false you would allow multiple instances of the same powerup on the same player. This could be useful for speed-powerups where you want the effect to stack. So the power up multiplies the players speed factor for example by 1.5f and when done it divides it by the same amount. If two powerups are active at the same time you have to ensure that the actions of the effect(s) don’t interfer with each other.
With those script all you have to do is placing the PowerUp script on the powerups in the scene and assign a prefab of the desired effect to it’s public variable. You might want to turn the powerup into a prefab as well for easier reuse.
Keep in mind that the powerup effect is added to the root object of the player that picks up the powerup. If you want the hierarchy to be more clean you could provide an empty gameobject within the player called “powerups” where you attach the effect prefabs.