I wrote my own function of rotating a group of objects. I want ot make a new one, with smooth rotating, so I need a timer. I have tried to call Timer a few times, but it doesnt work. Here's my code:
public class rotate_around : MonoBehaviour
{
public Transform sphere;
// Update is called once per frame
void Update ()
{
if (Input.GetKeyDown(KeyCode.RightArrow))
{
sphere.RotateAround(new Vector3(3, 3, 3), new Vector3(0, 1, 0), 45);
timer();
}
}
public IEnumerator timer()
{
yield return new WaitForSeconds(5);
// actually I tried to add Debug.Log("blahblahblah") here, but it still didnt output anything
}
}
Try using StartCoroutine(timer()); instead of just timer();
StartCoroutine("timer", true);
would be the best way to do it!
timer() would just call a normal method. IEnumerator works different.
As mentioned before, the correct way to start a coroutine is StartCoroutine(timer());
from your code it isn't clear what you are trying to achieve, currently you do nothing after waiting those five seconds
Also you probably should implement some sort of mechanism that prevents user from spamming coroutines. You can set a bool flag, or hold reference to the coroutine you started - I often do
Coroutine myRoutine; // in global scope
if (myRoutine==null) myRoutine=StartCoroutine(timer()); // in event handler
myRoutine will null itself when its finished
Related
I am quite new in Unity and I have a simple question regarding Animation events.
In my code snipet I have the public variables - I noticed that I can't reference an AnimationEvent - and some simple stuff to do with them.
public GameObject completeLevelUI; // a panel
public Text endUI; // the text on the panel that show your success or fail
public AnimationEvent nextSceneEvent; // AnimatonEvent in the animation of the panel above
public void GameOver() { nextSceneEvent.time = 2; completeLevelUI.SetActive(true); endUI.text = "LEVEL\nFAILED"; }
public void GameWon() { nextSceneEvent.time = 6; completeLevelUI.SetActive(true); endUI.text = "LEVEL\nCOMPLETED"; }
public void LoadEnd() { SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1); }
The AnimationEvent's target method is LoadEnd().
The default firing time is 1.5 seconds and the code won't change it.
The reason why I need different fire times can't be seen in the shared code - it is not important now. I figured out some other ways to solve it, I am just curious why it isn't working.
I have tried to change the activating and the time setting code piece but it is the same.
Somehow I have to reference the AnimationEvent? Is it a problem that this script is called multiple times, even from places where there is no AnimationEvent - I didn't receive any exceptions during my examination.
Any ideas? Thanks for helping!
I guess you have an animation playing and at the end you want to trigger the new scene.
You can use different approaches.
First, you add the animation event directly to the animation clip and you set the method in the inspector. The method has to be on a component attached to the same game object as the animation.
Second, you launch the animation and wait for the end of it to call the method:
void EndProcess()
{
StartCoroutine(EndProcessSequence());
}
IEnumerator EndProcessSequence()
{
Animation anim = GetComponent<Animation>();
anim.Play("animName");
yield return new WaitWhile(()=> anim.isPlaying);
LoadNewScene();
}
Here' s a reusable version
IEnumerator EndProcessSequence(string animName, Action onComplete)
{
Animation anim = GetComponent<Animation>();
anim.Play(animName);
yield return new WaitWhile(()=> anim.isPlaying);
onComplete?.Invoke();
}
I'm new to unity. I have 4 GameObjects say Red, Green, Blue, Yellow - with index mapped 0, 1, 2 and 3 respectively. Also, I kept index sequence in the list List<int> systemTrack.
Now, I'm calling the sequence of animator with respect to list { 0, 1, 2, 3 } (this can be random 0-3).
I can perform one animator loop at a given time.
void Start() {
animator.Play("blue_animation", 0, 0);
}
How can I call in sequence based on list ? Perhaps Update() is right place to call.
So far I found this thread - But it has single Animator object with multiple state called in sequence which is not my case.
Few others discussions also available for Animation component Not for the new Animator component.
In your current class you could use a Coroutine like
List<AnimatorQueueController> animatorQueues;
private void Start()
{
StartCoroutine(RunAllControllersSequencial());
}
IEnumerator RunAllControllersSequencial()
{
foreach (var queue in animatorQueues)
{
// This runs the routine and waits until it finishes
yield return queue.RunAnimationQueueAndWait();
}
}
Now the only thing to do is define/implement how each if these controllers "knows", that its own animation queue has finished.
There are probably many possible ways to go. Straight away: None of them will be beautiful ;) Since the Animator uses a lot of string based stuff you always will end up either having to make sure all animation/trigger names are written correctly, or you will have to reference the animationClip and either hope the state is called the same or you have to expensively find the states via the according clip, you'd have to know how much time to wait for the queue to finish, etc.
This said, also this solution won't be perfect but in my opinion for your setup it would be the most convenient ;)
You could e.g. use an Animation Event and do something like
public class AnimationQueueController : MonoBehaviour
{
[SerializeField] private Animator _animator;
// Here set the first state name for each instance via the Inspector
[SerializeField] private string firstStateName = "first_state";
private void Awake ()
{
if(!_animator) _animator = GetComponent<Animator>();
}
// Simply have another routine you can yield so it is executed and at the same time waits until it is done
public IEnumerator RunAnimationQueueAndWaitUntilEnd()
{
hasReachedEnd = false;
_animator.Play(firstStateName, 0 ,0);
yield return new WaitUntil(() => hasReachedEnd);
}
// This is the method you will invoke via the Animation Event
// See https://docs.unity3d.com/Manual/script-AnimationWindowEvent.html
public void AnimationEnd()
{
hasReachedEnd = true;
}
}
This at least reduces your implementation overhead to
make sure you fire the Animation Event at the end of the last state
make sure you provide the name of the first state in the AnimatorQueueController
in your original script instead of Animator references rather store the AnimatorQueueController in your list
So I've ran into something weird, when using a co-routine in Unity to simulate a NPC (walks towards a target, idles for x seconds, walks to a target --repeat--).
I've found that starting the co-routine with a variable that holds the IEnumerator will not run twice, while starting the co routine with the method passed in directly runs as expected, repeatable.
Why does this work this way?
Whats happening 'under the hood'? I cant wrap my head around why this is, and it bugs me.
Below my IEnumerator method that simulates idle time.
private IEnumerator sitIdle()
{
var timeToWait = GetIdleTime();
_isIdle = true;
yield return new WaitForSeconds(timeToWait);
_isIdle = false;
}
If this gets called a second time per Scenario #1 (below), it runs as expected when called multiple times. It just repeats the process over and over.
If however, if it gets called per Scenario #2 (below) as a variable, it will kick off once, but refuse to enter a second time and just plain 'skip' it in code.
void LateUpdate()
{
_idleRoutine = sitIdle; //this is not actually in the late update, just moved here for reference.
if (_agent.hasPath)
{
if (isTouchingTarget())
{
StartCoroutine(sitIdle2()); //Scenario #1
StartCoroutine(_idleRoutine); //Scenario #2
_currentTarget = null;
_agent.ResetPath();
}
}
Tl;dr: StartCoroutine(variable to IEnumerator) is not repeatable, while StartCoroutine(IEnumerator()) works fine, why cant I pass the IEnumerator as a variable?
The return value of SitIdle() is an IEnumerator. IEnumerators do not repeat once they are completed and have no iterations remaining, which is what StartCoroutine tells Unity to do. Each time the coroutine is resumed at a yield is another iteration Unity tells the coroutine to perform.
If you really would like to store the coroutine as a variable, so you can choose between one or another, you could store the method you're interested in as a delegate, then call that to get your fresh IEnumerator:
IEnumerator sitIdle()
{
var timeToWait = GetIdleTime();
_isIdle = true;
yield return new WaitForSeconds(timeToWait);
_isIdle = false;
}
IEnumerator sitIdleAlternative()
{
var timeToWait = GetIdleTime() + 2f;
_isIdle = true;
yield return new WaitForSeconds(timeToWait);
_isIdle = false;
}
delegate IEnumerator IdleDelegate ();
IdleDelegate _idleRoutine;
void LateUpdate()
{
_idleRoutine = new IdleDelegate(sitIdleAlternative); //this is not actually in the late update, just moved here for reference.
_idleRoutine = new IdleDelegate(sitIdle);
if (_agent.hasPath)
{
if (isTouchingTarget())
{
StartCoroutine(sitIdle2()); //Scenario #1
StartCoroutine(_idleRoutine()); //Scenario #2
_currentTarget = null;
_agent.ResetPath();
}
}
}
I want to make countdown timer that will return value of bool when he is active , so I can check on other script if is active give double points if is not then you need to give normal points..
I want to make it more complicated and I want to add time on timer if the timer is active, if is not then we use default time on countdown...
I don't know how to use courutine specially when I need to add time if the timer is not over..
Lets say like a example:
I pick up power up and timer starts for 5 seconds counting to 0.
If i pick up again powerup and timer is on lets say 3 , Power up need to have now 8 seconds. When powerup is over he must go from 5 seconds when player pick up new one..
Here is my code that doesn't work how I want also my code doesn't have a function to add time to power up when power up is active.. In other words I don't know how i can check if powerup is active and if yes just to add to counter 5 more seconds..
Here is code that doesn't contain adding time it only contains working counter..
void startDoublePoints()
{
StartCoroutine("doublePoints");
Time.timeScale = 1;
}
//Simple courutine
IEnumerator doublePoints()
{
while (true)
{
yield return new WaitForSeconds(1);
timeLeft--;
}
}
I hope someone will explain me more about how I can achieve my goal.. I hope I explained what I need to achieve.. If you do not understand something please ask on comment and I will try to explain it again..
Thank you so much community I don't know how would I learn anything without this great place :)
float powerUpTimer;
bool isDoublePoints = false;
void Update()
{
// Check timer only when Power up time
if(isDoublePoints)
{
// Countdown the timer with update time
powerUpTimer -= Time.deltaTime;
if(powerUpTimer <= 0)
{
// End of power up time
isDoublePoints = false;
powerUpTimer = 0;
}
}
}
// Add any time player picks to timer
public void OnPickPowerUp(float buffTime)
{
isDoublePoints = true;
powerUpTimer += buffTime;
}
I need to keep up to an X (changeable amount) instances of countdown timers, each being added by the user, once a single one of them reaches 0 it calls for a function while the other counters keep counting.
for that purpose i tried to create a queue of them, but as it turns out, I can't change the value of an iteration variable in foreach:
public struct Debuff
{
public float Timer;
public int Stack;
public int MaxStack;
...
}
public Debuff Poisoned;
public void CalcDamage(...)
{
...
if (Poisoned.Stack < Poisoned.MaxStack)
{
Poisoned.Stack++;
PoisonStacksTimer.Enqueue(Poisoned.Timer);
InvokeRepeating("Poisoning", 0.1F, 1F);
}
else
{
PoisonStacksTimer.Dequeue();
PoisonStacksTimer.Enqueue(Poisoned.Timer);
}
}
public void Poisoning()
{
foreach(float PTimer in PoisonStacksTimer)
{
TakeDamage(Poisoned.DmgTranslate);
PTimer -= Time.deltaTime; // here lies at least one of the problems
if (PTimer <= 0)
{
Poisoned.Stack--;
PoisonStacksTimer.Dequeue();
CancelInvoke("Poisoning");
}
}
}
is there any other way to use countdown timers in queue? or maybe a better way to achieve my objective?
this is my first time asking a question here so I hope I explained my problem clearly.
Off the top my head, I'd use coroutines.
Every time you need to start a timer, you start an associated coroutine, so if you, at a specific moment, have for example 4 active timers, there'll be 4 coroutines.
In order to keep a reference to every single coroutine, use a List<IEnumerator>: every time a timer needs to be started, add the new IEnumerator reference and start the coroutine, so when a coroutine ends naturally or needs to be stopped, you can remove that reference from the list while keeping the other alive coroutines' references in the list.