I am creating a sort of game with Arduino and Processing. In my code, I use Daniel Shiffman's class Timer, but would like to create two different Timers using two different instances of the class.
My problem is that these two instances seem to be getting mixed up, with each one doing parts of what the other should be doing.
For example, timer should run for 10 seconds and correctTimer should run for 3 seconds, but they both run for 10 seconds. Additionally, when timer is finished, it should set the background to red and when correctTimer is finished, it should set the background to blue. However, both Timers set the background to blue when they are finished.
Does anyone have any ideas of how to fix this?
import processing.serial.*;
int end = 10;
String serial;
Serial port;
float[] array;
// --------------------------------------------------
PImage img;
PImage correct;
PImage incorrect;
float thumb;
float index;
float middle;
float ring;
float pinky;
// --------------------------------------------------
String alphabet;
int randomNum;
String letter;
// --------------------------------------------------
int savedTime;
int totalTime;
int passedTime;
boolean quit = false;
class Timer {
Timer(int tempTotalTime) {
totalTime = tempTotalTime;
}
void start() {
savedTime = millis();
//quit = false;
}
boolean isFinished() {
passedTime = millis() - savedTime;
if (passedTime > totalTime) {
return true;
} else {
return false;
}
}
}
Timer timer;
Timer correctTimer;
// --------------------------------------------------
boolean checkLetter(String letterPicked, float flexR_THUMB, float flexR_INDEX, float flexR_MIDDLE, float flexR_RING, float flexR_PINKY) {
if (letterPicked == "A") {
if (flexR_THUMB > 12000 && flexR_THUMB < 22000 &&
flexR_INDEX > 27958 && flexR_INDEX < 38500 &&
flexR_MIDDLE > 26035 && flexR_MIDDLE < 41650 &&
flexR_RING > 16492 && flexR_RING < 26000 &&
flexR_PINKY > 37528 && flexR_PINKY < 53500) {
return true;
} else {
return false;
}
}
return false; }
// --------------------------------------------------
void setup() {
size(1280, 950);
background(255);
port = new Serial(this, "/dev/tty.usbmodem1421", 9600);
port.clear();
serial = port.readStringUntil(end);
serial = null;
correct = loadImage("img/RIGHT.png");
incorrect = loadImage("img/WRONG.png");
correctTimer = new Timer(3000);
startOver();
}
// --------------------------------------------------
void startOver() {
background(255);
letter = "A";
img = loadImage("img/" + letter +".png");
image(img, 0, 0, 1280, 950);
timer = new Timer(10000);
timer.start();
}
// --------------------------------------------------
void draw() {
while(port.available() > 0) {
serial = port.readStringUntil(end);
}
if (serial != null) {
float[] array = float(split(serial, ','));
thumb = array[0];
index = array[1];
middle = array[2];
ring = array[3];
pinky = array[4];
}
if (checkLetter(letter, thumb, index, middle, ring, pinky) == true && quit == false) {
image(correct, 0, 0, 1280, 950);
quit = true;
correctTimer.start();
} else if (timer.isFinished() && quit == false) {
background(255, 0, 0);
quit = true;
correctTimer.start();
}
if (correctTimer.isFinished()) {
background(0, 0, 255);
}
}
Please try to post a MCVE instead of your whole project. Just put together a small example that demonstrates the problem. That makes it much easier for us to help you.
But your problem is caused by your savedTime, totalTime, and passedTime variables being outside the Timer class. Basically that means they're shared between all instances of the Timer class. You can use println() statements to confirm this.
To fix your problem, just move those variables inside the Timer class, so each instance has its own copy of them.
If you're still having trouble, please post a MCVE in a new question post, and we'll go from there. Good luck.
Related
I have a QuestionGenerator.cs script which at the start, creates a question that is a comparison between two randomly generated numbers that can be up to 3 digits. When the user clicks on the correct option button, another random question is generated.
Let's say the numbers are a and b. I pass a - b and the array { "<", "=", ">" } as the arguments to the method SetButtonListeners(). The logic goes like this:
When a - b is less than 0, the sign should be <
When a - b is equal to 0, the sign should be =
When a - b is greater than 0, the sign should be >
Actual question:
Every time SetButtonListeners() is called, listeners are added to the button, hence when it's called the second time, a listener already exists to that button. I want to remove the previously added listener if there's any and then add the new listener. As a hack, I am currently removing all the listeners the button has
buttons[i].onClick.RemoveAllListeners();
How do I go with only removing the previously set button listener and not all the listeners the button has?
A minimal reproducible code to depict my problem:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class QuestionsGenerator : MonoBehaviour {
static public QuestionsGenerator instance { get => return s_Instance; }
static private QuestionsGenerator s_Instance;
public Text questionText;
public Button[] buttons; // length is 3
private void Awake() {
if (s_Instance == null) {
s_Instance = this;
} else {
Destroy(gameObject);
}
}
private void Start() {
GenerateQuestion();
}
private void GenerateQuestion() {
ComparisionUpto3Digits();
}
private void ComparisionUpto3Digits() {
int a = Random.Range(1, 1000);
int b = Random.Range(1, 1000);
questionText.text = a + " ___ " + b;
int ans = a - b;
string[] options = { "<", "=", ">" };
SetButtonListeners(ans, options);
}
private void SetButtonListeners(int answer, string[] options) {
// length of both buttons[] and options[] is the same
for (int i = 0; i < buttons.Length; i++) {
buttons[i].GetComponentInChildren<Text>().text = options[i];
// removing all listeners here
buttons[i].onClick.RemoveAllListeners();
if ((answer < 0 && i == 0) || (answer == 0 && i == 1) || (answer > 0 && i == 2)) {
buttons[i].onClick.AddListener(() => {
HandleCorrectAnswer();
});
} else {
buttons[i].onClick.AddListener(() => {
HandleWrongAnswer();
});
}
}
}
private void HandleCorrectAnswer() {
GenerateQuestion();
}
private void HandleWrongAnswer() {
Debug.Log("Game Over");
}
}
Constantly adding new listeners does not seem like a good solution. Handle correct/wrong answer in the same listener instead by passing a different argument to the listening function. You can pass parameters like this: https://answers.unity.com/questions/1288510/buttononclickaddlistener-how-to-pass-parameter-or.html
EDIT:
To be more precise, you can simply do something like this:
private int a,b;
private void Awake(){
for(int buttonIndex = 0; buttonIndex < buttons.Length; buttonIndex++){
int closureIndex = buttonIndex;
buttons[closureIndex].onClick.AddListener(delegate{HandleAnswer(closureIndex);});
}
}
private void HandleAnswer(int buttonIndex){
int answer = a-b;
if ((answer < 0 && buttonIndex == 0) || (answer == 0 && buttonIndex == 1) || (answer > 0 && buttonIndex == 2)) {
[good answer]
}else{
[wrong answer]
}
}
EDIT:
Added closure index according to comment (https://answers.unity.com/questions/1376530/add-listeners-to-array-of-buttons.html?childToView=1376656#answer-1376656)
I'm try to create a function to stop player control, but when I pass the duration as a parameter in the function, the variable keeps ina huge number never really decreasing.
public void StopAllPlayerControl(float duration) {
stopPlayerDuration = duration;
stopPlayerDuration -= Time.deltaTime;
//stop here what you need.
playerInput.enableMovement = false;
if (stopPlayerDuration <= 0) {
//restore here to normal state
playerInput.enableMovement = true;
}
}
And Im calling this funct in a Update method like this
StopAllPlayerControl(3);
Image here to show that stopPlayerDuration getsm huge but stays in those big numbers never really decreasing. huge number
I used a combination of both answers, organized my logic better and, in this case, I used a coroutine.
This function starts the coroutine and I can call it and control it with a bool
public void startStopPlayerCo(float dur) {
//function to start stop player courutine from anywhere.
StartCoroutine(StopPlayerCo(dur));
}
This is the coroutine that works well now
public IEnumerator StopPlayerCo(float dur) {
while (startCO) {
GetComponent<PlayerMove>().enabled = false;
yield return new WaitForSeconds(dur);
//reset
GetComponent<PlayerMove>().enabled = true;
startCO = false;
}
}
And I call it in Update Method like this:
startStopPlayerCo(2) //2 seconds, or any var for desired time
Try this:
float startTime = 0;
float duration = 0;
public void StopAllPlayerControl(float duration)
{
this.duration = duration;
if(playerInput.enableMovement)
{
startTime = Time.time;
playerInput.enableMovement = false;
}
if(Time.time - startTime >= this.duration)
{
playerInput.enableMovement = true;
}
}
Explaination
Step 1
Firstly, we need to check if movement is already enabled. If it is, we mark the point that we started the timer, and disable movement. We also store our duration in a variable, and reference it locally with the `this` keyword.
if(playerInput.enableMovement)
{
startTime = Time.time;
playerInput.enableMovement = false;
}
Step 2
If the timer exceeds the duration then movement is enabled.
if(Time.time - startTime < duration)
{
playerInput.enableMovement = true;
}
It is also good practice to enclose this method in a boolean lock to prevent it from being called again while the timer is running, unless you want such a feature to be allowed.
I am trying to disable a player movement for 1 second when it collides with a specific object. The object collides and it detects that just fine however currently the entire game freezes and does not return.
Ive implemented a 0 value (as found in other examples) to the X and Y movements with a timer. This freezes the game entirely and I have to kill process and reopen Unity.
public Boolean frozen = false;
float shipDisabledTimer = 1f;
// Start is called before the first frame update
void Start()
{
SetUpMoveBoundaries();
gameSession = FindObjectOfType<GameSession>();
}
// Update is called once per frame
void Update()
{
Move(frozen);
Fire();
if (SceneManager.GetActiveScene() == SceneManager.GetSceneByName("Game"))
{
if (Input.GetKey(KeyCode.Escape))
SceneManager.LoadScene("Game Over");
}
}
public void Move(Boolean Frozen)
{
UnityEngine.Debug.Log("Update1 - " + Frozen);
var deltaX = Input.GetAxis("Horizontal") * Time.deltaTime * moveSpeed;
var deltaY = Input.GetAxis("Vertical") * Time.deltaTime * moveSpeed;
var newXPos = Mathf.Clamp(transform.position.x + deltaX, xMin, xMax);
var newYPos = Mathf.Clamp(transform.position.y + deltaY, yMin, yMax);
transform.position = new Vector2(newXPos, newYPos);
if (frozen == true)
{
float timer = 0f;
timer += Time.deltaTime;
UnityEngine.Debug.Log(frozen);
while (timer < shipDisabledTimer)
{
newXPos = 0;
newYPos = 0;
}
disableship(frozen = false);
}
}
public Boolean disableship(Boolean frozen)
{
return frozen;
}
private void OnTriggerEnter2D(Collider2D other)
{
UnityEngine.Debug.Log(other.gameObject.name);
if (other.gameObject.name.Equals("Orb Shot(Clone)"))
{
UnityEngine.Debug.Log("Orb hit player");
//StartCoroutine(countdownTimer());
disableship(frozen = true);
}
}
The ship alone should freeze. No other game object should freeze. All scores, counters, enemies should continue onward. Only the player X,Y are disabled for 1 second.
This
while (timer < shipDisabledTimer)
{
newXPos = 0;
newYPos = 0;
}
entirely blocks your game's main thread since the timer nor shipDisabledTime are not changed within the loop so it will never end.
There are a few ways you could go here.
Invoke
Invoke allows you to call a method after a certain delay so you could easily do something like
public void Freeze()
{
frozen = true;
Invoke("Unfreeze"; shipDisabledTime);
}
private void Unfreeze()
{
frozen = false;
}
Coroutine
A Coroutine is like a temporary Update method. Simplest way in your case is using WaitForSeconds and do something like
public void Freeze()
{
StartCoroutine (FreezeRoutine());
}
private IEnumerator FreezeRoutine()
{
frozen = true;
yield return new WaitForSeconds(shipDisabledTime);
frozen = false;
}
simple timer
Or you could use a simple timer but in Update (not a while loop) like
private float timer = -1;
public void Freeze()
{
frozen = true;
timer = shipDisabledTime;
}
private void Update()
{
if(timer > 0)
{
timer -= Time.deltaTime;
if(timer <= 0)
{
timer = -1;
frozen = false;
}
}
...
}
And then either way you simply don't take any movement input in the meantime
public void Move()
{
if(frozen) return;
...
}
Except for the Invoke solution you can also extend them and decided whether you want to e.g. stack multiple freezes, ignore them while already frozen or simply start the timers over.
General note: In c# rather simply use bool it's basically the same but easier to read and write ;)
Also note that this call
disableship(frozen = false);
...
public Boolean disableship(Boolean frozen)
{
return frozen;
}
Is pretty strange ... first of all this method does absolutely nothing than just return the same value you pass in as parameter .. you are hiding the frozen field with a same named parameter so this does nothing!
Second your method returns a Boolean but you are not assigning it to anything.
If you want to change the value simply set it using frozen = XY but don't pass this in as parameter further.
Aaand avoid calling Debug.Log every frame .. it will slow down your app even in a build where you don't even see the log!
I want my boost work by starting at 0 when the game starts.
In my game I can pick up boost coins to fill up my booster canister.
public static int boost;
private void OnTriggerEnter2D(Collider2D otherObject)
{
if (otherObject.tag == "Boost")
{
boost++;
GetComponent<AudioSource>().PlaySound(BoostSound, 5.7F);
Destroy(otherObject.gameObject);
}
}
After the player picks up enough boost coins to use the booster in the 2d vehicle.
you need a minimum of 3 boost to start booster. Each booster coin is equal to 1 second of boost.
I just want the player to be able to use the booster if they have over 3 boost coins collected, if they have 3 or more than that represents how many seconds they can use the boost for.
Button call code
public void BoostButton()
{
StartCoroutine("Use_VehicleBoost");
}
Booster code that a button calls.
IEnumerator Use_VehicleBoost()
{
// Check which boost package player picked
int boostLevel = SecurePlayerPrefs.GetInt("BoostLevel");
if (boostLevel == 0)
{
Debug.Log("Boost Level: None");
yield return null;
}
else if (boostLevel == 1)
{
Debug.Log("Boost Level: 1");
float aceleRate = 400, maxFWD = -2500;
while (Player.boost >= 3)
{
vehicleController.GetComponent<CarMovement>().accelerationRate += aceleRate;
vehicleController.GetComponent<CarMovement>().maxFwdSpeed += maxFWD;
// Meant to slowly take one point/ Time second away from boost tank
Player.boost = Player.boost - Mathf.RoundToInt(Time.deltaTime);
}
if (Player.boost <= 0)
{
yield return null;
}
}
yield return null;
}
Problem: code stuck inside while loop so Unity freezes. It can never escape the loop but how if Im calling --boost using deltaTime while the loop running.
If you want to use that while loop to check, use it into IEnumerator and wait every frame. Eg:
IEnumerator Use_VehicleBoost(){
...
while (Player.boost >= 3)
{
yield return null;
}
...
}
I've done this by setting a bool in update to flip true when the ability is activated, and flip false once the conditions are no longer met. A method is also called when that bool flips true that holds the desired behavior. I was actually having the same issue as you and didn't realize yield return null was the solution, so this is what I came up with.
if (ultCharge >= pilotUltMin && !pilotUlting && !pilotUltingPressed)
{
if (Input.GetButton(bumperR1))
{
pilotUltingPressed = true;
pilotUlting = true;
curCharge = ultCharge;
pilotUltingPressed = false;
}
}
if (pilotUlting)
{
PilotUlt();
}
}
private void PilotUlt()
{
if (ultCharge > curCharge - pilotUltMax && ultCharge >= 0)
{
rb.AddRelativeForce(Vector2.up * SwitchController.ShipSpeed * 1.5f, ForceMode2D.Impulse);
ultCharge -= 0.7f;
ultExhaust1.SetActive(true);
ultExhaust2.SetActive(true);
}
else
{
pilotUlting = false;
ultExhaust1.SetActive(false);
ultExhaust2.SetActive(false);
}
}
In the code below, I have a cycle for enemies appearing and disappearing.
IEnumerator EnemyCycle() {
while (isRepeating)
{
for (int j = 0; j < enemies.Length; j++) {
canStartUpdatingReset = true;
Enemy currentEnemy = enemies [j];
var _myMaterial = currentEnemy.GetComponent<Renderer>().material;
var _currentFade = StartCoroutine(FadeToForEnemy(_myMaterial, 0f, fTime, currentEnemy.gameObject, false));
coroutinesToStop.Add(_currentFade);
if (currentEnemy.hasWeapon) {
weaponCycleCoroutines.Add(StartCoroutine(FadeToForWeapon(currentEnemy.weapon.GetComponent<Renderer>().material, 0f, fadeTime, currentEnemy.weapon, false)));
}
}
yield return new WaitForSeconds (hideTime);
for (int j = 0; j < enemies.Length; j++) {
Enemy currentEnemy = enemies [j];
var _myMaterial = currentEnemy.GetComponent<Renderer>().material;
var _currentFade = StartCoroutine(FadeToForEnemy(_myMaterial, 1f, fTime, currentEnemy.gameObject, true));
coroutinesToStop.Add(_currentFade);
if (currentEnemy.hasWeapon) {
weaponCycleCoroutines.Add(StartCoroutine(FadeToForWeapon(currentEnemy.weapon.GetComponent<Renderer>().material, 1f, fadeTime, currentEnemy.weapon, true)));
}
yield return new WaitForSeconds (showTime);
}
}
}
I have an enemyCycleDuration float which is
enemyCycleDuration = 60*(hideTime + fadeTime + showTime + fadeTime)
Note that fadeTime comes from FadeToForWeapon and FadeToForEnemy. The idea here is I want the enemyCycleDuration to be run in parallel to EnemyCycle() so it is reset (please see code below) at the same time that
yield return new WaitForSeconds (showTime);
is reached in the EnemyCycle() (end of method).
I am resetting enemyCycleDuration in the following way in the update method but EnemyCycle() seems to be ahead of enemyCycleDuration, always completing before enemyCycleDuration is reset. How can I get both to run parallel to each other in timing and complete at the same time?
if (canStartUpdatingReset) {
timeElapsed +=1;
if(timeElapsed >= enemyCycleDuration) {
timeElapsed = 0;
Debug.Log ("Reset CycleDuration");
}
}
When I need to something similar to what you ask for I don't use coroutines. I use instead something like this:
int TimeBetweenAction1 = 3f;
int TimeBetweenAction2 = 3f;
void Update () {
if(Time.time > NextAction1){
DoAction1();
}
if(Time.time > NextAction2){
DoAction2();
}
}
void DoAction1(){
NextAction1 = Time.time + TimeBetweenActions1;
}
void DoAction2(){
NextAction2 = Time.time + TimeBetweenActions2;
}
The action could be from respawn a type of enemies or shoot a gun. You can have as many as you need in a gameObject to manage all enemies respawn, or in different gameObjects linked to different triggers in a Scene...
I know you asked exactly about coroutines, but that may be an easier approach that will do the trick. Let me know if it is useful for your issue or if I need to clarify anything.