Coroutine completely freezes Unity 2020.3 - c#

I'm making a first-person game and am having trouble animating my character. I have the right animations in place. I need to find a way to make the game detect when the player has just landed on the ground so that I can play the 'landing' animation. The problem is that the only way I've thought of thus far to do this is with a coroutine. But the coroutine, when initiated, completely freezes my whole application. I suspect it's because the act of being midair initiates the coroutine once per frame. Here's the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAnimationHandler : MonoBehaviour
{
Animator animator;
IEnumerator LandDetect()
{
while (!playermove.isGrounded)
{
animator.SetBool("Midair", true);
}
animator.SetTrigger("Land");
yield return null;
}
PlayerMove playermove;
void Start()
{
// These are the two most important components for this
// script. I'll need PlayerMove for the mini-API
// that I have in there and I'll need the animator
// for obvious reasons.
playermove = GetComponent<PlayerMove>();
animator = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
JumpHandler();
}
void JumpHandler()
{
if (!playermove.isGrounded)
{
if (playermove.doubleJumpOccur)
{
animator.SetTrigger("DoubleJump");
StartCoroutine(LandDetect());
}
else
{
animator.SetBool("Midair", true);
StartCoroutine(LandDetect());
}
}
else if (playermove.jumpOccur)
{
animator.SetTrigger("Jump");
StartCoroutine(LandDetect());
}
}
}

I suspect that the application doesn't leave
while (!playermove.isGrounded)
{
animator.SetBool("Midair", true);
}
until you land. This loop is continuously executed in the same frame (which freezes your game) because you're not skipping frames. You need to put yield return 0 (0, not null. Null won't cause the coroutine to skip any frames) somewhere in it so it can be resumed in the next frame. Also may remove yield return null at the end of the method, I doesn't do anything at this point
That's how I think it should look:
IEnumerator LandDetect()
{
while (!playermove.isGrounded)
{
animator.SetBool("Midair", true);
yield return 0;
}
animator.SetTrigger("Land");
}
Also I don't know if using a coroutine is the best choice here. I usually use some trigger/collision detection for stuff like this.

Related

Unity Freezes-(Unity newbie)

I'm totally newbie in Unity and C#, and will apprecaite your Help.
i'm trying to make tower Defense Game,with help of Tutorial, but it seems to be a Problem with while under my Game Loop,it will freez each time i use While. and something else that i notice is EnemyIDsToSummon.Count is whole the time 0.
here you can see my GameLoop:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameLoopManager : MonoBehaviour
{
private static Queue<int> EnemyIDsToSummon;
public bool LoopShouldEnd;
private void Start()
{
EnemyIDsToSummon= new Queue<int>();
EntitySummoner.Init();
StartCoroutine(GameLoop());
InvokeRepeating("SummonTest", 0f, 1f);
}
void SummonTest(){
EnqueueEnemyIDToSummon(1);
}
IEnumerator GameLoop(){
while(LoopShouldEnd==false){
//Spawn Enemies
if(EnemyIDsToSummon.Count>0)
{
for(int i=0;i<EnemyIDsToSummon.Count;i++){
EntitySummoner.SummonEnemy(EnemyIDsToSummon.Dequeue());
}
}
//Spawn Towers
//Move Enemies
//Tick Towers
//Apply Effects
//Damge Enemies
//Remove Enemies
// remove Towers
}
yield return null;
}
public static void EnqueueEnemyIDToSummon(int ID){
EnemyIDsToSummon.Enqueue(ID);
}
}
and here is the tutorial i've used:
https://www.youtube.com/watch?v=-Pj5o5I_Wl4&t=1263s
You've written a Unity coroutine GameLoop, which has a while loop in it.
The engine will enter the while loop and never leave, because nothing sets LoopShouldEnd to true, or breaks out of the loop.
Usually, you'd fix this by yielding from the coroutine (yield return null) from inside the while loop, and setting LoopShouldEnd to false on the frame you want it to stop, or ending the while loop once you're done spawning inside that frame (you can break to end it immediately)
When the yield is outside the while loop, it will only happen after the while loop finishes, which is never, so no other code runs and the editor freezes.
Some useful documentation on coroutines:
https://docs.unity3d.com/Manual/Coroutines.html

Unity Animation playing from coroutine

As the title states the animation is not playing. the line telling it to play is in a coroutine and the code is before a waitforseconds(3f).
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Play : MonoBehaviour {
public Animator AnimatorRef;
// Use this for initialization
void Start () {
if (AnimatorRef == null)
{
AnimatorRef = GetComponent<Animator>();
}
}
public void PlayGame()
{
StartCoroutine(TitlePlay());
Debug.Log("playing");
}
IEnumerator TitlePlay()
{
Debug.Log("playing1");
AnimatorRef.SetBool("Enlarge", true);
yield return new WaitForSeconds(3f);
Debug.Log("playing2");
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
it grabs the animator reference fine and all three of the comments show.
2 Considerations.
1st -
Did you check your transitions and AnimationController?
You can open up the AnimationController to see if the bool changes during runtime, if it does you'll know there is a transition error somewhere between your animation states.
2nd -
If you comment out the "LoadScene" part, does the animation then play correctly?
I suspect that the Animation bool is for some reason not allowed to carry out it's actions before the entire method has been run through, could be wrong though.
My unity shutdown without saving but it now works. If anyone has this problem remake the animation and MAKE SURE you go from the initial animation and click add clip. Then it worked for me :)

Cannot use InvokeRepeating with method parameters. How do I work around this?

I'm trying to implement a damage over time system, but Unity keeps saying "Trying to Invoke method...Couldn't be Called." The method I want to call uses the parameters "Collider coll", but from my research you can't invoke if the method has said paremters.
Here is my code:
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class DamageOverTime : MonoBehaviour
{
public int PHP; //PHP = Player Health from PlayerHealth.cs script.
public int Damage; //Amount of damage.
public int DamageOverTime; //Damage over time.
public float DamageInterval_DOT = .25f; //Damage interval for damage over time.
public string Level;
PlayerHealth player;
void Start()
{
player = GameObject.Find("Player").GetComponent<PlayerHealth>();
InvokeRepeating("OnTriggerEnter", DamageInterval_DOT, DamageInterval_DOT);
}
void Update()
{
PHP = GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP;
if (PHP <= 0)
{
SceneManager.LoadScene(Level);
}
}
void OnTriggerEnter(Collider coll)
{
if (coll.gameObject.tag == "Player")
{
GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP = PHP - Damage;
}
if (coll.gameObject.tag == "Ball")
{
gameObject.SetActive(false);
SceneManager.LoadScene(Level);
}
}
}
My goal is to get the OnTriggerEnter function to loop ever 1/4 of a second (or lower possibly). Current upon entering a collider my health is drained by 60% in about a second which is far too fast. How should I work around this?
You can't use InvokeRepeating with OnTriggerEnter, because it's a trigger, which means it will trigger once when entrance of its holder occured.
Also InvokeRepeating means that you want to keep repeating an action continously which is not the case here. You want your trigger to occur once and then remove health points over time.
Solution - Coroutine
Unity3D makes custom usage of IEnumerable and yield keyword called Coroutine that always returns an IEnumerator. How it works? It will return control on every yield there is in our Coroutine and then will go back to exact point where it gave back control instead of starting function execution from scratch.
Code:
void OnTriggerEnter(Collider coll)
{
if (coll.gameObject.tag == "Player")
{
StartCoroutine("DamageOverTimeCoroutine");
}
if (coll.gameObject.tag == "Ball")
{
gameObject.SetActive(false);
SceneManager.LoadScene(Level);
}
}
public IEnumerator DamageOverTimeCoroutine()
{
var dotHits = 0;
while (dotHits < 4)
{
//Will remove 1/4 of Damage per tick
GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP -= Damage / 4;
dotHits++;
//Will return control over here
yield return new WaitForSeconds(DamageInterval_DOT);
//And then control is returned back here once 0.25s passes
}
}
There's of course room for improvement in this Coroutine. You can pass parameters to it, same as you can to any other method in C#. Also you can implement other invervals that are not hardcoded. Code above is just a simple example on how to deal with such scenarios.
For continous damage over time
public IEnumerator DamageOverTimeCoroutine()
{
var dotHits = 0;
var player = GameObject.Find("Player").GetComponent<PlayerHealth>();
while (true)
{
//Stop removing damage, player is dead already
if (player.PlayerHP <= 0)
yield break;
//Will remove 5 Damage per tick
player.PlayerHP -= 5;
dotHits++;
//Will return control over here
yield return new WaitForSeconds(DamageInterval_DOT);
//And then control is returned back here once 0.25s passes
}
}
To stop Coroutine somewhere else from code use StopCoroutine("DamageOverTimeCoroutine") to stop certain coroutine type or StopAllCoroutines() to stop all coroutines that are active now.

Trouble with WaitForSeconds() in Unity

I am trying to invoke a shooting animation in the Update function and then wait for 0.5 seconds before spawning a laser shot. The below code isn't working for me. What can I do to achieve the desired result?
void Update()
{
if (Input.GetMouseButtonDown (0))
{
animator.SetTrigger("Shoot"); // Start animation
WaitAndShoot();
}
}
IEnumerator WaitAndShoot()
{
yield return new WaitForSeconds(0.5f);
Instantiate (shot, shotSpawn.transform.position,shotSpawn.transform.rotation);
}
You're forgetting to call it as a coroutine using StartCoroutine().
It should be:
void Update()
{
if (Input.GetMouseButtonDown (0))
{
animator.SetTrigger("Shoot"); // Start animation
StartCoroutine(WaitAndShoot());
}
}
IEnumerator WaitAndShoot()
{
yield return new WaitForSeconds(0.5f);
Instantiate (shot, shotSpawn.transform.position,shotSpawn.transform.rotation);
}
Keep in mind that this still allows you to trigger multiple shots before the first shot has been spawned. If you want to prevent that from happening, keep track of a shot being fired or not with a boolean, and check for that in addition to your GetMouseButtonDown.

Flashing GameObject in Unity

How can I create a flashing object in Unity using SetActiveRecursively (Moment = 1 second).
My example (for changes):
public GameObject flashing_Label;
private float timer;
void Update()
{
while(true)
{
flashing_Label.SetActiveRecursively(true);
timer = Time.deltaTime;
if(timer > 1)
{
flashing_Label.SetActiveRecursively(false);
timer = 0;
}
}
}
Use InvokeRepeating:
public GameObject flashing_Label;
public float interval;
void Start()
{
InvokeRepeating("FlashLabel", 0, interval);
}
void FlashLabel()
{
if(flashing_Label.activeSelf)
flashing_Label.SetActive(false);
else
flashing_Label.SetActive(true);
}
Take a look on unity WaitForSeconds function.
By passing int param. (seconds), you can toggle your gameObject.
bool fadeIn = true;
IEnumerator Toggler()
{
yield return new WaitForSeconds(1);
fadeIn = !fadeIn;
}
then call this function by StartCoroutine(Toggler()).
You can use the Coroutines and new Unity 4.6 GUI to achieve this very easily. Check this article here which falsges a Text. YOu can modify it easily for gameobject easily
Blinking Text - TGC
If you just need the code, here you go
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class FlashingTextScript : MonoBehaviour {
Text flashingText;
void Start(){
//get the Text component
flashingText = GetComponent<Text>();
//Call coroutine BlinkText on Start
StartCoroutine(BlinkText());
}
//function to blink the text
public IEnumerator BlinkText(){
//blink it forever. You can set a terminating condition depending upon your requirement
while(true){
//set the Text's text to blank
flashingText.text= "";
//display blank text for 0.5 seconds
yield return new WaitForSeconds(.5f);
//display “I AM FLASHING TEXT” for the next 0.5 seconds
flashingText.text= "I AM FLASHING TEXT!";
yield return new WaitForSeconds(.5f);
}
}
}
P.S: Even though it seems to be an infinite loop which is generally considered as a bad programming practice, in this case it works quite well as the MonoBehaviour will be destroyed once the object is destroyed. Also, if you dont need it to flash forever, you can add a terminating condition based on your requirements.
Simple way is to use InvokeRepeating() and CancelInvoke() method.
InvokeRepeating("BlinkText",0,0.3) will repeatedly call BlinkText() for every 0.03 time Interval.
CancelInvoke("BlinkText") will stop the repeating invoke.
Here's the example :
//Call this when you want to start blinking
InvokeRepeating("BlinkText", 0 , 0.03f);
void BlinkText() {
if(Title.gameObject.activeSelf)
Title.gameObject.SetActive(false);
else
Title.gameObject.SetActive(true);
}
//Call this when you want to stop blinking
CancelInvoke("BlinkText");
Unity Documentation

Categories

Resources