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

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;
}
}

Related

Trigger Collinder doesnt work with Input.GetKey

I'm trying to make a game where u can cut down trees, but when I tested my script it didn't work at all. This script is for cutting down trees. When I put TakeDmg and Destroytree in the private void OnTriggerEnter2D(Collider2D other) it works fine, but when I add if(Input.GetKey(KeyCode.E)), it stops working I really don't know that's the problem.
public int treeHp;
public GameObject logPrefab;
public Animator anim;
private void OnTriggerEnter2D(Collider2D other){
if(Input.GetKey(KeyCode.E)){
TakeDmg();
Destroytree();
}
}
public void TakeDmg(){
anim.SetTrigger("TreeDamage");
treeHp = treeHp -1;
}
public void Destroytree(){
if(treeHp <= 0){
//spawn la loguri
Destroy(gameObject);
}
}
Thanks :D
You code would require the User to already hold down the E key before(or in the same frame as) hitting the tree.
I would rather expect that you want to first move close to the tree and then press E in order to cut it.
You should rather use OnTriggerStay2D
Sent each frame where another object is within a trigger collider attached to this object (2D physics only).
in order to listen for the E key as long as you are within the trigger. I would then also use Input.GetKeyDown in order to handle the press only once instead of every frame.
Otherwise you would do
anim.SetTrigger("TreeDamage");
treeHp = treeHp -1;
ever frame while holing E down which is A) frame-rate-dependent and B) probabl not what you want to do here.
public float cooldownDuration;
private bool coolDown;
// Listen for the key as long as you stay in the collider
private void OnTriggerStay2D(Collider2D other)
{
if(!coolDown && Input.GetKeyDown(KeyCode.E))
{
TakeDmg();
Destroytree();
StartCoroutine(CoolDown());
}
}
IEnumerator CoolDown()
{
coolDown = true;
yield return new WaitForSeconds(cooldownDuration);
coolDown = false;
}
As alternative if you actually want to continue cutting while holding down E you could do it like
// Adjust in the Inspector: Seconds to wait before next cut
public float cutInterval = 1f;
private bool colliderExitted;
// Listen for the key as long as you stay in the collider
private void OnTriggerStay2D(Collider2D other)
{
if(Input.GetKeyDown(KeyCode.E))
{
StartCoroutine(CuttingRoutine())
}
}
private void OnTriggerExit2D(Collider2D other)
{
colliderExitted = true;
}
private IEnumerator CuttingRoutine()
{
colliderExitted = false;
while(!colliderExitted && Input.GetKey(KeyCode.E))
{
anim.SetTrigger("TreeDamage");
treeHp = treeHp -1;
// Add a delay before the next iteration
yield return new WaitForSeconds(cutInterval);
}
}

Using a timer in conjunction with 2 push buttons from arduino

So I am using two push buttons (connected to an Arduino Uno) as an input to my game. The player has to push down both buttons at the same time for the character to move in the game. I want the player to hold down the buttons for a different amount of time in each level. I have a working Arduino and a working Unity timer and player script, but am not able to get the code to do what I want. What I basically want is that only when the player presses the buttons down, does the timer start counting down. Right now, the timer starts as soon as the scene begins. I know that I somehow have to reference the timer script to the button object, I have tried this but it still doesn't work. Note that the timer UI does have a Timer tag on it. I have also referenced the Player Controller script in the Timer script. Right now, Its giving me a range of errors. I have attached an image depicting these errors.error image
The Timer script:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
//int startTime = 0;
public bool buttonPressed = false;
public int timeLeft;
public Text countdownText;
GameObject Character;
void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
//StartCoroutine("LoseTime");
BeginTimer();
}
void Update()
{
countdownText.text = ("Time Left = " + timeLeft);
if (timeLeft <= 0)
{
//StopCoroutine("LoseTime");
//countdownText.text = "Times Up!";
Invoke("ChangeLevel", 0.1f);
}
}
public void BeginTimer()
{
Character.GetComponent<PlayerController>().Update();
//gameObject.GetComponent<MyScript2>().MyFunction();
if (buttonPressed == true )
{
StartCoroutine("LoseTime");
}
else if (buttonPressed == false)
{
StopCoroutine("LoseTime");
}
}
IEnumerator LoseTime()
{
while (true)
{
yield return new WaitForSeconds(1);
timeLeft--;
}
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
The Player Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
public class PlayerController : MonoBehaviour
{
SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
//player == GameObject.FindWithTag("Player").GetComponent<>();
public float Speed;
public Vector2 height;
public float xMin, xMax, yMin, yMax;
public bool buttonPressed = false;
GameObject Character;
public void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
if (!sp.IsOpen)
{ // If the erial port is not open
sp.Open(); // Open
}
sp.ReadTimeout = 1; // Timeout for reading
}
public void Update()
{
if (sp.IsOpen)
{ // Check to see if the serial port is open
try
{
string value = sp.ReadLine();//To("Button"); //Read the information
int button = int.Parse(value);
//float amount = float.Parse(value);
//transform.Translate(Speed * Time.deltaTime, 0f, 0f); //walk
if (button == 0) //*Input.GetKeyDown(KeyCode.Space*/) //jump
{
buttonPressed = true;
Character.GetComponent<Rigidbody2D>().AddForce(height, ForceMode2D.Impulse);
Character.GetComponent<Rigidbody2D>().position = new Vector3
(
Mathf.Clamp(GetComponent<Rigidbody2D>().position.x, xMin, xMax),
Mathf.Clamp(GetComponent<Rigidbody2D>().position.y, yMin, yMax)
);
Timer tmr = GameObject.Find("Timer").GetComponent<Timer>();
tmr.BeginTimer();
}
}
catch (System.Exception)
{
}
}
void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
}
I think the problem may be with how I am referencing the scripts in each other.
In your timer you have a quite strange mixup of Update and Coroutine. Also note that BeginTimer is called exactly once! You also shouldn't "manually" call Update of another component.
I wouldn't use Update at all here. Simply start and stop a Coroutine.
The Timer script should only do the countdown. It doesn't have to know more:
public class Timer : MonoBehaviour
{
public int timeLeft;
public Text countdownText;
private bool timerStarted;
public void BeginTimer(int seconds)
{
// Here you have to decide whether you want to restart a timer
timeLeft = seconds;
// or if you rather want to continue counting down
//if(!timerStarted) timeLeft = seconds;
StartCoroutine(LoseTime());
}
public void StopTimer()
{
StopAllCoroutines();
}
private IEnumerator LoseTime()
{
timerStarted = true;
while (timeLeft > 0)
{
yield return new WaitForSeconds(1);
timeLeft --;
countdownText.text = $"Time Left = {timeLeft}";
}
// Only reached after the timer finished and wasn't interrupted meanwhile
// Using Invoke here is a very good idea since we don't want to interrupt anymore
// if the user lets go of the button(s) now
Invoke(nameof(ChangeLevel), 0.1f);
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
In general avoid to use Find at all. If anyhow possible already reference things in the Inspector! If needed you can use Find but only once! What you never want to do is use any of the Find and GetComponent variants repeatedly - rather store the reference the first time and re-use it - and especially not in Update no a per frame basis. They are very expensive calls!
public class PlayerController : MonoBehaviour
{
public float Speed;
public Vector2 height;
// I prefer to use Vector2 for such things
public Vector2 Min;
public Vector2 Max;
public bool buttonPressed = false;
// Already reference these via the Inspector if possible!
public Rigidbody2D Character;
public Timer timer;
public Rigidbody2D _rigidbody;
private SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
private void Awake()
{
FetchReferences();
}
// This is my "secret" tip for you! Go to the component in the Inspector
// open the ContextMenu and hit FetchReferences
// This already stores the references in the according fields ;)
[ContextMenu("FetchReferences")]
private void FetchReferences()
{
if(!Character)Character = GameObject.FindWithTag("Player"). GetComponent<Rigidbody2D>();
if(!timer) timer = GameObject.Find("Timer").GetComponent<Timer>();
}
private void Start()
{
if (!sp.IsOpen)
{
sp.Open(); // Open
}
sp.ReadTimeout = 1;
}
private void Update()
{
// I wouldn't do the serialport open check here
// your if block simply silently hides the fact that your application
// doesn't work correctly! Rather throw an error!
try
{
string value = sp.ReadLine(); //Read the information
int button = int.Parse(value);
//TODO: Since it isn't clear in your question how you get TWO buttons
//TODO: You will have to change this condition in order to only fire if both
//TODO: buttons are currently pressed!
buttonPressed = button == 0;
if (buttonPressed)
{
Character.AddForce(height, ForceMode2D.Impulse);
// The clamping of a rigidbody should always be done ine FixedUpdate!
// Pass in how many seconds as parameter or make the method
// parameterless and configure a fixed duration via the Inspector of the Timer
timer.BeginTimer(3.0f);
}
else
{
// Maybe stop the timer if condition is not fulfilled ?
timer.StopTimer();
}
}
catch (System.Exception)
{
// You should do something here! At least a Log ...
}
}
private void FixedUpdate()
{
// Here I wasn't sure: Are there actually two different
// Rigidbody2D involved? I would assume you rather wanted to use the Character rigidbody again!
Character.position = new Vector3(Mathf.Clamp(Character.position.x, Min.x, Max.x), Mathf.Clamp(Character.position.y, Min.y, Max.y));
}
// Did you mean OnApplicationQuit here?
private void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
Typed on smartphone but I hope the idea gets clear

Coroutine not starting

I'm using a lightning GameObject prefab to have a visual effect when i'm firing my weapon. When I fire, I enable that lightning object and I have a generic component that deactivates it after a certain delay.
The problem is that the "should wait" log is never reached, and instead of waiting the set delay, it waits much longer and doesn't actually deactivate the GameObject.
Here's the code for the DeactivateAfter component
public class DestroyAfter : MonoBehaviour {
[SerializeField]
private float delay;
private bool firstRun = true;
void OnEnable () {
if (firstRun == false) {
StartCoroutine(DestroyMethod());
}
firstRun = false;
}
public IEnumerator DestroyMethod() {
Debug.LogFormat("Should wait; Time: {0}", Time.time);
yield return new WaitForSeconds(delay);
Debug.LogFormat("Should deactivate; Time: {0}", Time.time);
gameObject.SetActive(false);
}
}
The condition never be true, you need to set firstRun condition to true first.
private bool firstRun = **true**;
void OnEnable () {
if (firstRun == **true**) {
StartCoroutine(DestroyMethod());
}
firstRun = false;
}
And i ever like to set flag first and later do what you want:
private bool firstRun = **true**;
void OnEnable () {
if (firstRun == **true**) {
firstRun = false;
StartCoroutine(DestroyMethod());
}
}
I think you should use particle system for your weapon fire. Anyway I think your code is not working because you are deactivating the game object instead of deactivating the component. Activate and deactivate your component using something like this :
gameObject.GetComponent<YourComponent>().enabled = false;
First Solution - Instantiate and Destroy with delay
If I understood you correctly the lightning GameObject is already a prefab you instantiate when firing and you want to destroy it after a delay.
So instead of a Coroutine solution and enabling or disabling the Object, this could be way simpler done using instantiate and destroy after as your script is actually called:
GameObject obj = GameObject.Instantiate(yourPrefab, position, rotation);
GameObject.Destroy(obj, delay);
This function can be called from anywhere as long as you have yourGameObject provided to the calling class. So you don't even need an own class for this.
The second float parameter is a delay in seconds (see API)
For example you could have a shooting class like:
public class Shooting : MonoBehavior
{
[SerializeField]
private GameObject lightningPrefab;
[SerializeField]
private float delay;
public void OnShooting(Vector3 position, Quaternion rotation){
GameObject obj = GameObject.Instantiate(lightningPrefab, position, rotation);
/* Directly after instantiating the Object
* you can already "mark" it to be destroyed after x seconds
* so you don't have to store it as variable anywhere */
GameObject.Destroy(obj, delay);
}
}
You then also could add some check to not constantly spawn the prefab:
[...]
private GameObject lightningObject;
public void OnShooting(Vector3 position, Quaternion rotation){
if(lightningObject == null ){
lightningObject = GameObject.Instantiate(lightningPrefab, position, rotation);
GameObject.Destroy(lightningObject, delay);
}
}
On this way there is always only one of your lightning objects a time.
Second Solution - Timeout
Another way without having to instantiate and destroy the object all the time would be a simple timeout instead of the Coroutine:
public class Shooting : MonoBehavior
{
[Tooltip("The lightning Object")]
[SerializeField]
private GameObject lightningObject;
[Tooltip("The time in sec the lightningObject stays visible")]
[SerializeField]
private float visibleDelay;
/* track the time that passed since the last OnShooting call */
private float timePassed;
/* Additional bool to not perform Update when not needed */
private bool isVisible;
/* Update is called once each frame */
private void Update(){
/* If the object is not visible do nothing */
if (isVisible != true) return;
/* update the timePassed */
timePassed += Time.deltaTime;
/* if delay passed since last OnShooting call
* deactiavte the object */
if (timePassed >= visibleDelay){
lightningObject.SetActive(false);
}
}
public void OnShooting(){
/* Activate the Object */
lightningObject.SetActive(true);
/* set isVisible */
isVisible = true;
/* reset the timePassed */
timePassed = 0;
}
}
Hmm. Your code works fine in my project.
What i'm doing, some object have your script and I deactivate and then activate the gameObject when scene is playing.
Your Destroy Method is public.
Check your project, maybe somewhere calls StopAllCoroutines() or StopCoroutine(DestroyMethod());

Stopping the update function in Unity

I have some code that when it executes, it pushes a character forward. The issue is that the character never stops moving and continues forever. Is there a way to stop the character from moving after 2 seconds? Here is the code I'm using:
public class meleeAttack : MonoBehaviour
{
public int speed = 500;
Collider storedOther;
bool isHit = false;
void Start()
{
}
void Update()
{
if (isHit == true )
{
storedOther.GetComponent<Rigidbody>().AddForce(transform.forward * speed);
}
}
void OnTriggerStay(Collider other)
{
if (other.gameObject.tag == "Player" && Input.GetKeyUp(KeyCode.F))
{
storedOther = other;
isHit = true;
}
}
}
I'm not sure if there's a way to stop the update() function so it stops the character movement.
The Update function is part of the life cycle of a Unity script. If you want to stop the Update function to be executed, you need to deactivate the script. For this, you can simply use:
enabled = false;
This will deactivate the execution of the life cycle of your script, and so prevent the Update function to be called.
Now, it looks like you are applying a force to your character to make it move. What you might want to do then is, after two seconds, remove any force present on your character. For that, you can use a coroutine, which is a function that will not just be executed on one frame, but on several frames if needed. For that, you create a function which returns an IEnumerator parameter, and you call the coroutine with the StartCoroutine method:
bool forcedApplied = false;
void Update()
{
if (isHit == true && forceApplied == false)
{
storedOther.GetComponent<Rigidbody>().AddForce(transform.forward * speed);
forceApplied = true;
StartCoroutine(StopCharacter);
isHit = false;
}
}
IEnumerator StopCharacter()
{
yield return new WaitForSeconds(2);
storedOther.GetComponent<Rigidbody>().velocity = Vector3.zero;
forceApplied = false;
}
Those might be different ways to achieve what you want to do. It's up to you to choose what seems relevant to your current gameplay and to modify your script this way.

How to run some code in Unity3D after every x seconds?

I need to execute a code every x seconds till a condition is met in Unity3D C#. The code should run irrespective of other code as long as the condition is true and stop otherwise. As soon as the condition turns false, it should stop executing and run again if condition becomes true (counting no. of seconds from 0 again if possible). How to do that?
Actually, there's a better way than using a coroutine with yield. InvokeRepeating method has less overhead and doesn't need the ugly while(true) construct:
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour {
public Rigidbody projectile;
public bool Condition;
void LaunchProjectile() {
if (!Condition) return;
Rigidbody instance = Instantiate(projectile);
instance.velocity = Random.insideUnitSphere * 5;
}
void Start() {
InvokeRepeating("LaunchProjectile", 2, 0.3F);
}
}
Also, how is your condition defined? It's much better if it's a property — this way you don't have to check it every time:
public class Example : MonoBehaviour {
public Rigidbody projectile;
private bool _condition;
public bool Condition {
get { return _condition; }
set
{
if (_condition == value) return;
_condition = value;
if (value)
InvokeRepeating("LaunchProjectile", 2, 0.3F);
else
CancelInvoke("LaunchProjectile");
}
void LaunchProjectile() {
Rigidbody instance = Instantiate(projectile);
instance.velocity = Random.insideUnitSphere * 5;
}
}
Something like this should work.
void Start(){
StartCoroutine("DoStuff", 2.0F);
}
IEnumerator DoStuff(float waitTime) {
while(true){
//...do stuff here
if(someStopFlag==true)yield break;
else yield return new WaitForSeconds(waitTime);
}
}
MonoBehaviour.InvokeRepeating
Description Invokes the method methodName in time seconds, then repeatedly every repeatRate seconds.
Note : This does not work if you set the time scale to 0.
using UnityEngine;
using System.Collections.Generic;
// Starting in 2 seconds.
// a projectile will be launched every 0.3 seconds
public class ExampleScript : MonoBehaviour
{
public Rigidbody projectile;
void Start()
{
InvokeRepeating("LaunchProjectile", 2.0f, 0.3f);
}
void LaunchProjectile()
{
Rigidbody instance = Instantiate(projectile);
instance.velocity = Random.insideUnitSphere * 5;
}
}