My goal is simple, like a lot of applications, I want to make my cursor invisible after a certain time of inactivity of my user.
My solution doesn't seem really optimized, here's the algorithm :
void Start {
Timer t = new Timer(5000);//Create a timer of 5 second
t.start();
}
void Update {
if(MouseMoved){
t.reset();
}
timeLeft = t.deltaTime;
if ( timeLeft == 5000 )
{
Cursor.visible = false;
}
}
I really don't like to check at every frame if the mouse is moved, I'd prefer that my mouse moved trigger something, but I'm lost here, is anyone have a better solution ?
This should be the good way to achieve the task, using coroutine, without any memory issue:
private Coroutine co_HideCursor;
void Update()
{
if (Input.GetAxis("Mouse X") == 0 && (Input.GetAxis("Mouse Y") == 0))
{
if (co_HideCursor == null)
{
co_HideCursor = StartCoroutine(HideCursor());
}
}
else
{
if (co_HideCursor != null)
{
StopCoroutine(co_HideCursor);
co_HideCursor = null;
Cursor.visible = true;
}
}
}
private IEnumerator HideCursor()
{
yield return new WaitForSeconds(3);
Cursor.visible = false;
}
There are two ways to check for things like this outside of the update loop, that I know of:
1 - Custom Events.
Writing your own custom event would allow you to call something like "OnMouseMove()" outside of the update function and it would only execute when the mouse cursor's position changes.
2 - Coroutines
Creating a separate coroutine would allow you to perform this check less frequently. To do this, you make an IEnumerator and put your mouse movement logic in there. Something like:
IEnumerator MouseMovement()
{
while(true)
{
if(MouseMoved)
{
//Do stuff
}
yield return new WaitForSeconds(1f);
}
}
That coroutine would perform the check once every second while it is running. You would start the coroutine by saying:
StartCoroutine(MouseMovement());
And to stop it, you call
StopCoroutine(MouseMovement());
If you Start it when the timer reaches 0 and stop it when the cursor is moved, you can also prevent the coroutine from running all the time, only using it while the cursor is inactive.
Related
I have implemented the dialog system from this Brackeys video in my project. Everything works perfectly, but when I have done the build for android I have seen that printing long texts slows the game down.
The code divides the sentence to be displayed in the UI into an array of characters and then prints each character one by one, with a small delay. I have been doing several tests and first I thought that the problem was the coroutine from where the characters were printed. But then I have removed the code from the coroutine and I have seen that the more characters it prints, the more the game slows down.
void FixedUpdate()
{
if(typeSentence)
{
if(t <= 0)
{
TypeChar();
t = charDelay;
}
t -= Time.fixedDeltaTime;
}
}
private void TypeChar()
{
GUIs[dialogue.UIIndex].dialogueText.text += charSentence[sentenceIndex];
sentenceIndex++;
if (sentenceIndex >= charSentence.Length)
{
typeSentence = false;
sentenceIndex = 0;
GUIs[dialogue.UIIndex].continueButton.SetActive(true);
}
}
I don't know if there is a more efficient way to do it, or if someone can explain to me what is happening and why it slows down so much.
Instead of triggering TypeChar() method in fixed update, you can convert TypeChar() to a Coroutine that can handle time more performing way.
private IEnumerator TypeChar()
{
while(sentenceIndex >= charSentence.Length)
{
if(typeSentence)
{
GUIs[dialogue.UIIndex].dialogueText.text += charSentence[sentenceIndex];
sentenceIndex++;
yield return new WaitForSeconds(charDelay);
}
}
typeSentence = false;
sentenceIndex = 0;
GUIs[dialogue.UIIndex].continueButton.SetActive(true);
}
And you can delete typeSentence variable completely if you do not change it out of scope.
And you can call it at Start instead of FixedUpdate.
private void Start()
{
StartCoroutine(nameof(TypeChar));
}
I'm making a practice game to get used to coding where you have to shoot a bird. When you run out of bullets, you need to press the 'r' key to reload your bullets. I want there to be a delay between when the button is pressed and when the bullets reload, but so far what I found is code that freezes everything (shown below). Is there a way to keep the code from freezing everything?
Summary: The code below freezes everything (the whole game) when pressing the 'r' button. Is there code I can use that won't freeze everything and will only wait 2 seconds before running the next action?
IEnumerator TimerRoutine()
{
if (Input.GetKeyDown(KeyCode.R))
{
yield return new WaitForSeconds(2); //Fix this, freezes everything
activeBullets = 0;
}
}
Use Coroutines To set This delay
if (Input.GetKeyDown(KeyCode.R) && isDelayDone) // defined isDelayDone as private bool = true;
{
// When you press the Key
isDelayDone = false;
StartCoroutine(Delay());
IEnumerator Delay()
{
yield return new WaitForSeconds(2);
isDelayDone = true;
activeBullets = 0;
}
}
Your problem is that you are waiting 2 seconds after the key press but not waiting for the actual key event it.
Here is a modified version of your method doing what you want.
IEnumerator TimerRoutine()
{
while(activeBullets == 0) // Use the bullets value to check if its been reloaded
{
if (Input.GetKeyDown(KeyCode.R)) // Key event check each frame
{
// Key event fired so wait 2 seconds before reloading the bullets and exiting the Coroutine
yield return new WaitForSeconds(2);
activeBullets = reloadBulletsAmount;
break;
}
yield return null; // Use null for the time value so it waits each frame independant of how long it is
}
}
(I know this has an accepted answer I just feel this method would be better)
I have an array of positions that I want my camera to move/lerp between. There are two buttons (button A and button B) that trigger the camera to move position. If the user presses button A, the camera will lerp to the previous position in the array. If the user presses button B, the camera will lerp to the next position in the array. However, before moving to a new position, I want the camera to lerp to an intermediate position, pause there for a couple of seconds, and then move. Here is the pseudocode for what I have at the moment:
void Update()
{
if (buttonPress == a) {
positionToMoveTo = positions[currentPosition--];
}
if (buttonpress == b) {
positionToMoveTo = positions[currentPosition++];
}
}
void LateUpdate()
{
camera.lerp(intermediatePosition);
StartCoroutine(pause());
}
IEnumerator pause()
{
yield return new WaitForSeconds(3f);
camera.lerp(positionToMoveTo);
}
This doesn't work though because I get strange jittering when switching camera positions and my intermediate position doesn't always occur. I think my problem has something to do with execution order but I can't figure it out. Any help would be great :)
You start a new Coroutine every frame since LateUpdate runs every frame after all Update calls are finished!
You could avoid this by a slightly different approach:
private bool isIntermediate;
private bool moveCamera;
private void LateUpdate ()
{
if(!moveCamera) return;
if(isIntermediate)
{
camera.lerp(intermediatePosition);
}
else
{
camera.lerp(positionToMoveTo);
}
}
private IEnumerator MoveCamera()
{
moveCamera = true;
isIntermediate=true;
yield return new WaitForSeconds(3f);
isIntermediate=false;
// Wait until the camera reaches the target
while(camera.transform.position == PositionToMoveTo){
yield return null;
}
// Stop moving
moveCamera = false;
// just to be sure your camera has exact the correct position in the end
camera.transform.position = PositionToMoveTo;
}
Alternatively you could do all the movement in the Coroutine without LateUpdate (but honestly I'm not sure if the Coroutines are done before or after Update)
private IEnumerator MoveCamera()
{
float timer = 3f;
while(timer>0)
{
timer -= Time.deltaTime;
camera.lerp(intermediatePosition);
yield return null;
}
// Wait until the camera reaches the target
while(camera.transform.position == PositionToMoveTo){
camera.lerp(PositionToMoveTo);
yield return null;
}
// just to be sure your camera has exact the correct position in the end
camera.transform.position = PositionToMoveTo;
}
This second one would be cleaner bjt as said I don't know if it is a requirement for you to have it run in LateUpdate
Note: the == operator of Vector3 has a precision of 0.00001. If you need a better or weaker precision you have to change to
if(Vector3.Distance(camera.transform.position, PositionToMoveTo) <= YOUR_DESIRED_THRESHOLD)
Now all you have to do is to call your Coroutine Everytime you want to change the camera position.
void Update()
{
if (buttonPress == a)
{
// Make sure the Coroutine only is running once
StopCoroutine(MoveCamera);
positionToMoveTo = positions[currentPosition--];
StartCoroutine (MoveCamera);
}
if (buttonpress == b)
{
// Make sure the Coroutine only is running once
StopCoroutine (MoveCamera);
positionToMoveTo = positions[currentPosition++];
StartCoroutine (MoveCamera);
}
}
The code in my script works fine. But sometimes it does not drop after I contact the rigid body collides. When it touches another corner, it restarts the StartCoroutine. I want to run it once to get ahead of it. How can I get it? (More descriptive: In my game, a ball is falling from above, and it stops for 3 seconds when hit by obstacles. I do not want it to stop in that obstacle again after it has hit once.
public void OnCollisionEnter2D(Collision2D col)
{
if (col.collider.CompareTag("Player"))
{
hitEffect.transform.position = col.contacts[0].point;
hitEffect.gameObject.SetActive(true);
GameManager.Instance.playerController.anim.Squeeze();
col.gameObject.GetComponent<Rigidbody2D>().simulated = false;
StartCoroutine (SetKinematic_Coroutine(col));
}
}
public IEnumerator SetKinematic_Coroutine(Collision2D col)
{
yield return new WaitForSeconds(1f);
col.gameObject.GetComponent<Rigidbody2D>().simulated = true;
}
Best way for preventing multi call of a coroutine is to use a reference:
private IEnumerator coroutine = null;
private void Method()
{
if(condition == true && this coroutine == null)
{
this.coroutine = MyCoroutine();
StartCoroutine(this.coroutine);
}
}
private IEnumerator MyCoroutine()
{
yield return null;
this.coroutine = null; // Set it back to null when leaving the coroutine.
}
When the condition is met and the coroutine is null (you are not running it already), it will assign to the reference and call the starting of the coroutine. While the coroutine is running, this.coroutine is not null and the double condition cannot be met anymore. When the coroutine is done, this.coroutine is set to null so next time the double condition is run, they will be both true.
You can also use a basic boolean for flag, but the usage of the IEnumerator reference can also be used to cancel.
You could turn the collider of the ball off whilst it is stopped so it can't receive any more hits during that time.
public IEnumerator SetKinematic_Coroutine(Collision2D col)
{
//turn the collider off
col.gameObject.GetComponent<Collider2D>().enabled = false;
yield return new WaitForSeconds(1f);
col.gameObject.GetComponent<Rigidbody2D>().simulated = true;
//turn the collider back on after we have waited
col.gameObject.GetComponent<Collider2D>().enabled = true;
}
Add a list of objects already hit by it and, for every hit, check if the object it is colliding with is in the list, if not add it and run the code.
private List<Collider2D> collided = new List<Collider2D>();
public void OnCollisionEnter2D(Collision2D col) {
if (col.collider.CompareTag("Player") && !collided.Contains(col.collider)) {
collided.Add(col.collider);
// ...
The Contains call might give you trouble if to many colliders are added to the list frequently, but otherwise this should do it.
I have an animatormanager able to create run time animation from 1 animation (start frame, end frame and delay then the speed is calculated, will do morphing and more later). My unity version is 5.3.1f1
Everything work fine, some stuff are not done but are optional. The animator can queue requested animation to prevent interupting the current one.
The thing is, I can only start 1 time an animation with an object. The code to launch the next animation is exactly the same but there is just nothing to do.
Event if its 2 time the same animation with the same animation data
All formula are good and tested.
I did intensive debugging with breakpoint to make sure everything is fine at any point
Is there something to prevent me from starting an animation 2 time on an object one after an other?. I have no error or warning at all. The first animation work fine no matter the settings I put in my AnimData struct, but the second time, nothing happen.
Here is the essential:
public int? startAnim(int index)
{
if(index < animIndex.Count)
{
startAnim(animIndex[index]);
}
return null;
}
//private because the struct is internal, this make sure the animator keep control of the list.
private int? startAnim(AnimData animD)
{
if(locked)
{
#if UNITY_EDITOR
Debug.Log("anim locked");
#endif
return null;
}
//current anim (queue anim) not finished
if(endTime > Time.time)
{
if(canQueue)
{
addToQueue(animD);
return animDataQ.Count;
}
else
{
return null;
}
}
else
{
endTime = Time.time + animD.TotalTime;
StartCoroutine(animManager(animD));
return 0;
}
return null;
}
#endregion anim starters
private IEnumerator animManager(AnimData animData)
{
animator.speed = Mathf.Abs(animData.calculateSpeed(animLength, AnimType.main, currentKey).Value);
//animator.Play(0,0, animData.StartKey/animLength);
if(animData.AnimSpeed > 0)
{
animator.Play(0,0, animData.StartKey/animLength/2);
}
else
{
//animator.Play(0,0, (animLength * 2) - (animData.StartKey/animLength));
animator.Play(0,0, (((animLength*2) - animData.StartKey)/(animLength * 2)));
}
//animator.Play(0,0, (animData.AnimSpeed > 0) ? animData.StartKey/animLength : ((animLength * 2) - (animData.StartKey/animLength)));
yield return new WaitForSeconds(animData.Delay);
animator.Stop();
yield return null;
endAnim();
}
private void addToQueue(AnimData animD)
{
animDataQ.Enqueue(animD);
endTime += animD.TotalTime;
queueTime += animD.TotalTime;
}
private void endAnim()
{
if(canQueue && animDataQ.Count > 0)
{
StartCoroutine(animManager(animDataQ.Dequeue()));
}
}
Thanks for your time.
i found a work around.
I dont want to mark it as an accepted solution since its not "clean" in my opinion
there is certainly some kind of problem with the animator for some specific case like mine.
my solution was to use animator.speed = 0 instead of animator.stop();
everything worked instantly with that small change
i will ask my question to the unity forum because there is something strange for sure