In a small simulation game (A.I. spaceship shooter) that I am developing, I am trying to come up with an effective shield function or IEnumerator which can be called or started and do multiple things:
Count down the shield's cooldown if it is above zero
Activate the shield for the set duration (5 seconds) if the cooldown has ended
Deactivate the shield when the duration expires
However, I run into some problems when trying this using only an Ienumerator. I have been able to use IEnumerators to count down timers and cooldowns before but trying to do both a cooldown and duration doesn't seem to work as Unity does not let me WaitForSeconds twice without leaving the IEnumerator.
Similarly, each ship has a turret and inside of that turret is an IEnumerator which fires or counts down its cooldown, whichever is needed for the situation.
// Fire continuously if in range and we have more than 1 shot left
// Otherwise, reload for (rate) seconds and reset shots left
public IEnumerator Fire(Vector2 target) {
firing = true;
if (cooldown <= 0) {
if (bullets > 0) {
// Fire a bullet
bullets--;
// Instatiate the bullet
}
} else {
// Reload
cooldown = rate;
bullets = count;
}
} else {
yield return new WaitForSeconds(1);
cooldown--;
}
firing = false;
yield break;
}
The Fire Coroutine is called by using the firing flag to check whether it is running or not and if it is not call
var fire = turret.Fire(shootTarget + offset);
if (!turret.firing && InRange() == true) {
StartCoroutine(fire);
}
every second or so if the ship is alive and we have a target.
I do think that my current use of the IEnumerator is not recommended because it has to be called at least every second, but with how small the environment is at the moment, it doesn't appear to be an issue.
Any help is appreciated.
Quick code without coroutines, no idea if it works in game. Should give you some idea though, I hope.
public class Shield : MonoBehaviour {
public float duration;
public float cooldown;
public float lastActivated;
public bool IsActivated => (lastActivated - (Time.time - duration)) > 0;
public bool onCoolDown => (lastActivated - (Time.time - cooldown)) > 0;
public void Activate(){
if(onCoolDown) return;
lastActivated = Time.time;
}
void LateUpdate(){
if(IsActivated) //Show shield effects, blahblah
else //Do nothing, blahblah
}
}
Related
I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here
I am making a FPS zombie game that is essentially a rip off of COD Nazi Zombies. I am 2 months into self-learning Unity with C# and everything is going well, however this small issue is bugging me and I could use a hand.
The Problem : The players health heals far too fast nomatter what values I change healingAmount and healingFreq to. Furthermore, the health seems to increasingly get faster. Is there an better way of writing a self heal method than to use a Coroutine?
Relevent code below...
public int currentHealth = 100;
public int maxHealth = 100;
public int healingAmount = 1;
public int healingFreq = 1;
public void Start()
{
SetHealthBar(currentHealth);
}
public void Update()
{
StartCoroutine(SlowHeal());
}
IEnumerator SlowHeal()
{
while (currentHealth < maxHealth)
{
yield return new WaitForSeconds(healingFreq); // Stops loop for a desired amount of seconds at a time.
currentHealth += healingAmount; // Adds the desired healing amount to the players current health.
SetHealthBar(currentHealth); // Updates healthbar to the players current health.
if (currentHealth > maxHealth) // Limits the healing to the max in the event it may overflow
{
currentHealth = maxHealth;
}
}
}
You only need to call StartCoroutine() method once in your Start method. StartCoroutine() will handle regular calls to SlowHeal(). Try moving your StartCoroutine() call to your Start method and see how things pan out.
Once the zombie has healed once, there is no way to start the healing process again. I would propose the following.
public void Update()
{
if ( ( currentHealth < maxHealth ) && !healing )
{
healing = true;
StartCoroutine(SlowHeal());
}
}
IEnumerator SlowHeal()
{
while (currentHealth < maxHealth)
{
yield return new WaitForSeconds(healingFreq);
currentHealth += healingAmount;
if (currentHealth > maxHealth)
{
currentHealth = maxHealth;
}
SetHealthBar(currentHealth);
}
healing = false;
}
Topher is basically correct in his assessment on what's happening, but I would write the code differently. Likewise Chris Walsh is correct in saying you only need to call StartCoroutine in Start.
public void Start()
{
SetHealthBar(currentHealth);
}
IEnumerator SlowHeal()
{
while (true)
{
yield return new WaitForSeconds(healingFreq);
currentHealth += healingAmount;
if (currentHealth > maxHealth)
{
currentHealth = maxHealth;
}
SetHealthBar(currentHealth);
}
}
Because the zombie will always heal once it is not at full health, there is no reason to terminate the coroutine when the zombie reaches full health: the code is already there to insure the zombie doesn't heal above maximum, so further healing and no healing are effectively identical.
In this way the coroutine can be started once (in Start) and never needs to be messed with after that. No checking for whether or not the coroutine is already running, no more bools to insure we don't start a second cortoutine, nothing special is needed if we insure that the coroutine itself never exits (coroutines are automatically stopped when the script is disabled or destroyed*).
*This does not apply to coroutines started by other, non-self objects.
i have two objects(the same character, but, in different functions) which i want change the character when animation stop and when it runs triggered by a click. For example, I have the Kick_Player where there is the animation triggered by the click, and when the Kick_Player ends the animation, i want it automatically changes to Player_Stopped. The poses are different each other, 'cause this i need to do these changes.
I tried something with this.MyAnimator.GetCurrentAnimatorStateInfo(0).IsName("My_Animation") but, i got unsuccessful tries. Is there a way to do that ?
public class TapController : MonoBehaviour {
Animator Anim;
public GameObject CharacterToController; //Kick_Player
public GameObject CharacterToBeStopped; //Player_Stopped
void Start(){
Anim = CharacterToController.GetComponent<Animator>();
CharacterToBeStopped.SetActive(false);
}
void Update(){
if(input.GetMouseButtonDown(0)){
if(!CharacterToController.activeSelf){
CharacterToController.SetActive(true);
}
Anim.Play("Kick_Ball");
if(!this.Anim.GetCurrentAnimatorStateInfo(0).IsName("Kick_Ball") {
CharacterToController.SetActive(false);
CharacterToBeStopped.SetActive(true);
}
}
}
I made this code to test, but it doesn't work
Using the IsName function requires that you prefix the base layer name of the animation state before the actual animation state.
The default base name is usually "Base Layer"
if(!this.Anim.GetCurrentAnimatorStateInfo(0).IsName("Base Layer.Kick_Ball")
Note that you have to do that outside your if(input.GetMouseButtonDown(0)){ otherwise that will never get chance to be checked.
I've seen reports of IsName not working for some people so if you do that but still have issues, consider doing it another way.
void Update()
{
if (Input.GetMouseButtonDown(0))
{
if (!CharacterToController.activeSelf)
{
CharacterToController.SetActive(true);
}
Anim.Play("Kick_Ball");
StartCoroutine(PlayAndWaitForAnim(Anim, "Kick_Ball"));
}
}
const string animBaseLayer = "Base Layer";
int animHash = Animator.StringToHash(animBaseLayer + ".Kick_Ball");
public IEnumerator PlayAndWaitForAnim(Animator targetAnim, string stateName)
{
targetAnim.Play(stateName);
//Wait until we enter the current state
while (targetAnim.GetCurrentAnimatorStateInfo(0).fullPathHash != animHash)
{
yield return null;
}
float counter = 0;
float waitTime = targetAnim.GetCurrentAnimatorStateInfo(0).length;
//Now, Wait until the current state is done playing
while (counter < (waitTime))
{
counter += Time.deltaTime;
yield return null;
}
//Done playing. Do something below!
Debug.Log("Done Playing");
CharacterToController.SetActive(false);
CharacterToBeStopped.SetActive(true);
}
I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here
I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here