Why here StartCorotine() is necessary - unity3d

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirePistol : MonoBehaviour {
public GameObject TheGun;
public GameObject MuzzleFlash;
public AudioSource GunFire;
public bool IsFiring = false;
void Update () {
if (Input.GetButtonDown("Fire1"))
{
if (IsFiring == false)
{
StartCoroutine(FiringPistol());
}
}
}
IEnumerator FiringPistol ()
{
IsFiring = true;
TheGun.GetComponent<Animation>().Play("PistolShot");
MuzzleFlash.SetActive(true);
MuzzleFlash.GetComponent<Animation>().Play("MuzzleAnim");
GunFire.Play();
yield return new WaitForSeconds(0.5f);
IsFiring = false;
}
}
I am writing a gun mechanic And
I wonder why we need
yield return new WaitForSeconds(0.5f); .What is the difference without this command .It's really unnecessary to write this code coz the time is short .Afterall , will it be an error like scene crash after i deleting this code ?Any help is greatly appreciated !

In general: Every method returning IEnumerator has to contain at least one yield statement. In Unity you have to use StartCoroutine to run an IEnumerator as Coroutine.
In your specific case: So you can delay your code by 0.5 seconds!
It is short but 0.5 is about 30 frames!
Someone using e.g. something like AutoClicker could jam the fire key each frame so he would cause significantly more damage then someone playing "normal" (due to physical limitations of your keyboard and finger ;) )
You are just avoiding that and limit down firing to a maximum of 2x per second.
In general - as usual - there are multiple ways to achieve that and you could go without Coroutines entirely but it makes coding so much cleaner and easier to maintain then doing everything in Update!
As some alternative examples for simple delays as here you could also either do a simple timer in Update
private float timer;
void Update ()
{
if(timer > 0)
{
// reduce the timer by time passed since last frame
timer -= Time.deltaTime;
}
else
{
if(Input.GetButtonDown("Fire1"))
{
FiringPistol();
timer = 0.5f;
}
}
}
void FiringPistol()
{
TheGun.GetComponent<Animation>().Play("PistolShot");
MuzzleFlash.SetActive(true);
MuzzleFlash.GetComponent<Animation>().Play("MuzzleAnim");
GunFire.Play();
}
or you can also use Invoke with a given delay.
bool canFire;
void Update ()
{
if (Input.GetButtonDown("Fire1") && canFire)
{
FiringPistol();
}
}
void FiringPistol()
{
TheGun.GetComponent<Animation>().Play("PistolShot");
MuzzleFlash.SetActive(true);
MuzzleFlash.GetComponent<Animation>().Play("MuzzleAnim");
GunFire.Play();
Invoke(nameof(AfterCooldown), 0.5f);
}
void AfterCooldown()
{
canFire = true;
}
In general btw you should store the Animation references to not use GetComponent over and over again:
// if possible already reference these via the Inspector
[SerializeField] private Animation theGunAnimation;
[SerializeField] private Animation muzzleFlashAnimation;
private void Awake()
{
// as fallback get them on runtime
// since this is a fallback and in best case you already referenced these via the Inspector
// we can save a bit of resources and use GetComponent
// only in the case the fields are not already set
// otherwise we can skip using GetComponent as we already have a reference
if(!theGunAnimation) theGunAnimation = TheGun.GetComponent<Animation>();
if(!muzzleFlashAnimation) muzzleFlashAnimation = MuzzleFlash.GetComponent<Animation>();
}
then later you reuse them
theGunAnimation.Play("PistolShot");
MuzzleFlash.SetActive(true);
muzzleFlashAnimation.Play("MuzzleAnim");
GunFire.Play();

Related

Unity Microphone.Start() lag, how to get rid of it?

Using unity 2020.3 and the XR Plug-in (currently only oculus but will be moving to openxr i hope) and trying to start the microphone when secondary button is pushed. It works but starting the microphone causes lag. Coroutine doens't help, I tried threading which stops the lag but then can't do anything with the audioclip. This has been asked a few times over the years but no answer yet. Here's the code:
void Update()
{
foreach(var d in devices){
if (d.TryGetFeatureValue(CommonUsages.secondaryButton, out isPressed)){
if (isPressed && !wasTalking)
{
wasTalking = true;
asource.PlayOneShot(walkietalkie);
//start_recording = new Thread(startRecording);
//start_recording.Start();
startRecording();
}
else if (wasTalking && !isPressed){
finishRecording();
wasTalking = false;
}
}
private void startRecording(){
recording = Microphone.Start(null, false, 30, freq);
startRecordingTime = Time.time;
yield return null;
}
Edit: I've removed the useless coroutine. Why the -1 to my question?
The primary way I reckon that you could alleviate the stutter is moving this work to another thread, which is notoriously hard with Unity.
A solution to this could be using or adapting another method of recording audio like NAudio.
Use coroutines.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class TestMic : MonoBehaviour
{
[SerializeField] AudioSource self;
public int samplerate = 44100;
public int time = 10;
// Start is called before the first frame update
void Start()
{
//Debug.Log(Microphone.devices);
StartCoroutine("test");
}
IEnumerator test()
{
self.clip = Microphone.Start(null, true, time, samplerate);
self.loop = true;
self.Play();
yield return null;
}
// Update is called once per frame
void Update()
{
}
}

Simple Coroutine Animation script breaks unity

Alright, so I'm using a coroutine to wait 6 seconds before executing a method to change a float in an animator, really simple stuff. My issue is that something in this script is causing my unity editor to completely lock up when I place it on a gameobject, and I don't know why. I don't think I have any infinite loops going, but I'm not sure. Anyone have any ideas? thx ahead of time.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class handanimatordock : MonoBehaviour
{
public Animator hand;
private float Blendfloat;
bool ChangeHand = false;
private float timefloat = 0.0f;
// Start is called before the first frame update
void Start()
{
StartCoroutine(waiter());
}
// Update is called once per frame
void Update()
{
}
public void changeHands()
{
if (ChangeHand == false)
{
ChangeHand = true;
while (Blendfloat != 1.0f)
{
Blendfloat = Blendfloat + 0.01f;
hand.SetFloat("Blend", Blendfloat);
}
}
else
{
ChangeHand = false;
while (Blendfloat != 0.0f)
{
Blendfloat = Blendfloat - 0.01f;
hand.SetFloat("Blend", Blendfloat);
}
}
}
IEnumerator waiter()
{
//Wait for 4 seconds
yield return new WaitForSeconds(6);
changeHands();
StartCoroutine(waiter());
}
}
You probably have an infinite loop in
while (Blendfloat != 1.0f)
{
Blendfloat = Blendfloat + 0.01f;
hand.SetFloat("Blend", Blendfloat);
}
Never directly compare two float values using == or !=.
Due to floating point impression a value like
10f * 0.1f
might end up being 1.000000001 or 0.99999999 though logically you would expect exactly 1. So your condition is probably never false!
Usually you rather give it a certain range like e.g.
while(Mathf.Abs(Blendfloat - 1) > certainThreshold)
Unity has for that Mathf.Approximately
while(!Mathf.Approximately(Blendfloat, 1f)
which basically equals comparing to a threshold of Mathf.Epsilon
which(Math.Abs(Blendfloat - 1) > Mathf.Epsilon)
Note that anyway what you have right now will execute the entire loop in one single frame.
If you really wanted it to fade over time you need to do one iteration per frame in e.g. a Coroutine!

how to work with playerprefs in a character selection menu in unity

I have a character selection menu in which you should have enough coins to buy (unlock) characters. Once one of the characters is selected, your coins must be decreased according to it's price and when you restart the game and that character is selected again, it must not be locked and your coins must not be decreased either. I tried different ways but none of them worked. I accidentally came to this video. here is my script;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEditor;
public class MainMenu3 : MonoBehaviour {
int SceneIndex;
public Button BluePlane;
public Button WhitePlane;
public GameObject wood;
public static int character_number;
public int isPlanesold;
// Use this for initialization
void Start () {
isPlanesold = PlayerPrefs.GetInt ("isPlanesold");
if (!PlayerPrefs.HasKey ("Number")) {
PlayerPrefs.SetInt ("Number", 1);
}
character_number = PlayerPrefs.GetInt ("Number");
UIManager2.coin_score = PlayerPrefs.GetInt ("Score");
SceneIndex = SceneManager.GetActiveScene ().buildIndex;
}
public void Blueplane () {
character_number = 1;
SceneManager.LoadScene ("Menu2");
}
public void BuyWhitePlane () {
if (UIManager2.coin_score >= 1 && isPlanesold ==0) {
WhitePlane.interactable = true;
PlayerPrefs.SetInt ("isPlanesold", 1);
character_number = 2;
UIManager2.coin_score--;
wood.SetActive (false);
PlayerPrefs.SetInt ("Score", UIManager2.coin_score);
PlayerPrefs.SetInt ("Number", character_number);
SceneManager.LoadScene ("Menu2");
}
else
WhitePlane.interactable = false;
}
void Update () {
if (Input.GetKeyDown (KeyCode.Escape))
SceneManager.LoadScene (SceneIndex - 1);
}
when for example I earn 1 coin by BluePlane that's already unlocked by default and click on WhitePlane button, it works fine but when I restart the game, the button returns to WhitePlane.interactable = false; I know what should I do but I really don't know how!
Ok from what I can see you have 2 problems.
Firstly, you're not giving isPlanesolda default value. When you get a PlayerPref for the first time you assign it a default value incase no value for it currently exists.
Secondly, I think its because you don't have an 'already unlocked' option for BuyWhitePlane, only 'not unlocked' and 'can't unlock'. If isPlaneSoldgets set to 1 (like it does when you buy the plane) you can't select the WhitePlane again (ie, when you reload the game).
Also I would call PlayerPrefs.Save() after the user makes a purchase incase the game exits prematurely or crashes, this way the user will keep their progress.
Therefore, I think your script should look something like this:
public class MainMenu3 : MonoBehaviour
{
[SerializeField] Button BluePlane;
[SerializeField] Button WhitePlane;
[SerializeField] GameObject wood;
int character_number;
int isPlanesold;
int score;
void Start()
{
isPlanesold = PlayerPrefs.GetInt("isPlanesold", 0); // I guess you're using this like a true/false? So the plane won't to be sold at first, so it would be zero. Could also convert to a bool with Convert.ToBoolean()
character_number = PlayerPrefs.GetInt("Number", 1);
score = PlayerPrefs.GetInt("Score", 0);
}
public void Blueplane()
{
PlayerPrefs.SetInt("Number", 1);
PlayerPrefs.Save();
SceneManager.LoadScene("Menu2");
}
public void BuyWhitePlane()
{
if (isPlaneSold == 1)
{
PlayerPrefs.SetInt("Number", 2);
PlayerPrefs.Save();
SceneManager.LoadScene("Menu2");
}
else if (score >= 1 && isPlanesold == 0)
{
PlayerPrefs.SetInt("isPlanesold", 1);
PlayerPrefs.SetInt("Number", 2);
PlayerPrefs.SetInt("Score", score--);
PlayerPrefs.Save();
// wood.SetActive(false); If you're immediately changing scenes, whats the point in doing this?
SceneManager.LoadScene("Menu2");
}
else
{
Debug.Log("You don't have the score!");
// Do stuff, maybe an UI error message?
// If you wanted to make the button not intractable if the user didn't have the score you could check the score value in start and if it was 0 set WhileButton.intractable = false;
}
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex - 1);
}
If you're planning to add more planes and isPlaneSold's value will increase past 1 then I would recommend adding a method to you don't end up repeating the same
PlayerPrefs.SetInt("isPlanesold", 1);
PlayerPrefs.SetInt("Number", 2);
PlayerPrefs.SetInt("Score", score--);
PlayerPrefs.Save();
code.
On a side note, using PlayerPrefs to store information like this (ie the users unlocks and coins) is bad because a PlayerPref's data is in an insecure fire somewhere in the users device. It should only be used to store stuff that the user can already freely (such as settings). A better solution would be to use BinaryFormatter, I would recommend this video to learn how to use them.
Furthermore, I can't see the rest of your code so I can't be sure but as you're saving UIManager2.coinscore and character_numberin PlayerPrefsthey don't need to be static? Static variables can cause some strange behaviour if you don't know what you're doing, and as a rough guideline should be avoided.
Hope this helps and best of luck!

Which happens first: OnCollisionStay() or LateUpdate()

I need to instantiate an object with a collider, check its collisions, then if its not touching anything, delete it.
is the following code always guaranteed to execute it the right order? (ie: collisions THEN lateUpdate?)
public class RemoveIfColliding : MonoBehaviour
{
bool touching = false;
private void OnCollisionStay(Collision collision)
{
print("I am touching " + collision.other.name);
touching = true;
}
private void LateUpdate()
{
if (!touching)
{
Destroy(gameObject);
}
}
}
Have a look at Order of Execution for Event Functions:
I would intepret it as all messages from the Physics block are called before the GameLogic. It states
The physics cycle might happen more than once per frame if the fixed time step is less than the actual frame update time.
but they should still be done before the Update and LateUpdate calls.
Even if LateUpdate is called first, the object will be destroyed in its next iteration. So basically at seemengly the same time.
But I don't think this is the best way to do it. You can simplify the code by not using the late update and the boolean at all. Just do it like this:
public class RemoveIfColliding : MonoBehaviour
{
private void OnCollisionStay(Collision collision)
{
print("I am touching " + collision.other.name);
Destroy(gameObject);
}
}
Hope this helps!

Wait for some time in between onMouseDown() function in unity2D

I have built this 2D project in unity where you tap on blocks and they destroy using the onMouseDown() function. My problem is after tapping a block and it destroys, how can I make the player wait for a certain amount of time before he can tap on another block in the game. I have tried using PlayerPrefs and subtracting Time.deltaTime from a certain float variable but it did not work.
Note: all the blocks share the same destroy script!!!
float waitTime = 1.5f;
static float lastClickTime = float.NegativeInfinity;
void OnMouseDown ()
{
float time = Time.time;
if( time > ( lastClickTime + waitTime ) )
{
lastClickTime = time;
DestroyThisBlock();
}
}
If they all share the same script, you can start with defining a static bool variable and a static event, say:
public static bool isLockedDown = false;
public static event Action onBlockDestroyed;
Then, on destruction function, first keep a check about this locked down. If this is false, then destroy, turn the lock to true, and invoke the static listener that'll be read by another script, which in turn will start a coroutine that'll turn this static lock to false after given set of seconds.
public class Block : MonoBehaviour {
void DestroyBlock()
{
if(isLockedDown)
return;
isLockedDown = true;
onBlockDestroyed.Invoke();
////destroy block///
}
}
public classBlockManager : MonoBehaviour {
void Awake()
{
Block.onBlockDestroyed += BeginUnlocking
}
void BeginUnlocking()
{
StartCoroutine(UnlockTimer);
}
IEnumerator UnlockTimer()
{
yield return new WaitForSeconds(1f);
BLock.isLockedDown = false;
}
}