Unity Corountine started but not ending - c#

I have the following coroutine which displays a warning error image when you click on a button and you do not have enough money:
public IEnumerator ShowWarning()
{
if (warningActive)
yield break;
Debug.Log("started...");
warningActive = true;
NotEnoughMoneyImage.SetActive(true);
yield return new WaitForSeconds(1f);
NotEnoughMoneyImage.SetActive(false);
warningActive = false;
Debug.Log("ended...");
yield break;
}
NotEnoughMoneyImage is a public Image GameObject
warningActive is a public bool
It shows the debug line "started" but never shows the debug line "ended". How is that possible? I call it from another script, but I don't think that there is the problem.
Here I've got the first script which is attached to an empty object (the main parent of the shop UI). This script is the main one. At the final of it appears the IEnumerator:
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using UnityEngine;
public class WeaponShop : MonoBehaviour
{
public static WeaponShop shop;
public GameObject itemHolderPrefab;
public Transform grid;
public List<Weapon> weaponList = new List<Weapon>();
private List<GameObject> itemHolderList = new List<GameObject>();
public List<GameObject> buyButtonList = new List<GameObject>();
public GameObject NotEnoughMoneyImage;
public bool warningActive = false;
void Start()
{
NotEnoughMoneyImage.SetActive(false);
shop = this;
FillList();
}
void FillList()
{
for (int i = 0; i < weaponList.Count; ++i)
{
GameObject holder = Instantiate(itemHolderPrefab, grid);
ItemHolder holderScript = holder.GetComponent<ItemHolder>();
holderScript.itemID = weaponList[i].weaponID;
holderScript.itemName.text = weaponList[i].weaponName;
if (weaponList[i].weaponPrice != (int)weaponList[i].weaponPrice)
holderScript.itemPrice.text = weaponList[i].weaponPrice.ToString("N2") + "$";
else
holderScript.itemPrice.text = weaponList[i].weaponPrice.ToString() + "$";
holderScript.itemSprite.sprite = weaponList[i].weaponSprite;
holderScript.buyButton.GetComponent<BuyButton>().weaponID = weaponList[i].weaponID;
itemHolderList.Add(holder);
buyButtonList.Add(holderScript.buyButton);
}
}
public void SoldOutText(int weaponID)
{
for (int i = 0; i < weaponList.Count; ++i)
if (weaponList[i].weaponID == weaponID)
itemHolderList[i].GetComponent<ItemHolder>().itemPrice.text = "SOLD OUT!";
}
public IEnumerator ShowWarning()
{
if (warningActive)
yield break;
Debug.Log("started...");
warningActive = true;
NotEnoughMoneyImage.SetActive(true);
yield return new WaitForSeconds(1f);
NotEnoughMoneyImage.SetActive(false);
warningActive = false;
Debug.Log("ended...");
yield break;
}
}
Now the second script is attached to the "BUY" button of a prefab "Item Holder". The script above (first one) generates a number of prefabs "item holder" and fills them with the info from the inspector (weapon name, price, stats etc). Here it comes the second script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.PlayerLoop;
public class BuyButton : MonoBehaviour
{
public int weaponID;
public TMP_Text buttonText;
public void BuyWeapon()
{
if (weaponID == 0)
{
Debug.Log("Weapon ID is ZERO");
return;
}
for (int i = 0; i < WeaponShop.shop.weaponList.Count; ++i)
{
if (WeaponShop.shop.weaponList[i].weaponID == weaponID && !WeaponShop.shop.weaponList[i].isBought && CurrencyManager.currencyManager.RequestMoney(WeaponShop.shop.weaponList[i].weaponPrice))
{// USING
WeaponShop.shop.weaponList[i].isBought = true;
CurrencyManager.currencyManager.ReduceMoney(WeaponShop.shop.weaponList[i].weaponPrice);
WeaponShop.shop.SoldOutText(weaponID);
UpdateBuyButton();
// change the weapon system and update your weapon, depending on ID / name here
}
else if (WeaponShop.shop.weaponList[i].weaponID == weaponID && !WeaponShop.shop.weaponList[i].isBought && !CurrencyManager.currencyManager.RequestMoney(WeaponShop.shop.weaponList[i].weaponPrice))
{//NOT ENOUGH MONEY. I know that it enters this if statement because of the debug.log
Debug.Log("Corountine...");
StartCoroutine(WeaponShop.shop.ShowWarning());
}
else if (WeaponShop.shop.weaponList[i].weaponID == weaponID && WeaponShop.shop.weaponList[i].isBought)
{
UpdateBuyButton();
// change the weapon system and update your weapon, depending on ID / name here
}
}
}
void UpdateBuyButton()
{
buttonText.text = "USING";
for (int i = 0; i < WeaponShop.shop.buyButtonList.Count; ++i)
{
BuyButton buyButtonScript = WeaponShop.shop.buyButtonList[i].GetComponent<BuyButton>();
for (int j = 0; j < WeaponShop.shop.weaponList.Count; ++j)
{
if (WeaponShop.shop.weaponList[j].weaponID == buyButtonScript.weaponID && WeaponShop.shop.weaponList[j].isBought && WeaponShop.shop.weaponList[j].weaponID != weaponID)
{
buyButtonScript.buttonText.text = "USE";
}
}
}
// change the weapon system and update your weapon, depending on ID / name
}
}
At line 34 is the start coroutine method.

Yes, I figured it out! When I was entering the shop UI, I was pausing the game in another script. So the time scale was 0. All I had to do was to replace in the coroutine this line:
yield return new WaitForSeconds(1f);
to this one:
yield return new WaitForSecondsRealtime(1f);
That was it! Now it works just fine! For everyone which thinks the coroutine does not work well, check your time scale. It's very important (Idk how I missed it!).

Related

Unity's Button.onClick listener requires several clicks to be invoked

The cards with the Button component and an onClick listener require numerous erratic clicks to trigger and fail to invoke after just one click.
The first two cards will always invoke via the first click. Afterwards, the other cards often require multiple clicks (between 2 and 7 clicks) before their listener is invoked, and they are flipped over. This occurs at random as sometimes even the first three pairs will flip after just one click, and then the rest won´t.
If I wait for a set time of five seconds, the next card I click will turn over with just one click. So the issue appears to depend on the interval between clicks, as opposed to the number of clicks. Although this behaviour is inconsistent, as clicking just once on a new card and then waiting those 5 seconds doesn´t always work and the card will stay unflipped; assuming because I clicked too soon.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
public class GameController : MonoBehaviour
{
[SerializeField] private Sprite bgImage;
public Sprite[] puzzles;
public List<Sprite> gamePuzzles = new List<Sprite>();
public List<Button> btns = new List<Button>();
private bool firstGuess, secondGuess;
private int countGuesses;
private int countCorrectGuesses;
private int gameGuesses;
private int firstGuessIndex, secondGuessIndex;
private string firstGuessPuzzle, secondGuessPuzzle;
void Awake()
{
puzzles = Resources.LoadAll<Sprite>("Sprites/Cards");
}
void Start()
{
GetButtons();
AddListeners();
AddGamePuzzles();
Shuffle(gamePuzzles);
gameGuesses = gamePuzzles.Count / 2;
}
void GetButtons()
{
GameObject[] objects = GameObject.FindGameObjectsWithTag("Puzzle Button");
for (int i = 0; i < objects.Length; i++)
{
btns.Add(objects[i].GetComponent<Button>());
btns[i].image.sprite = bgImage;
}
}
void AddGamePuzzles()
{
int looper = btns.Count;
int index = 0;
for (int i = 0; i < looper; i++)
{
if (index == looper / 2)
{
index = 0;
}
gamePuzzles.Add(puzzles[index]);
index++;
}
}
void AddListeners()
{
foreach (Button btn in btns)
{
btn.onClick.AddListener(() => PickAPuzzle());
}
}
public void PickAPuzzle()
{
string name = UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject.name;
Debug.Log("You are clicking a button named" + name);
if (!firstGuess)
{
firstGuess = true;
firstGuessIndex =
int.Parse(UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject.name);
firstGuessPuzzle = gamePuzzles[firstGuessIndex].name;
btns[firstGuessIndex].image.sprite = gamePuzzles[firstGuessIndex];
}
else if (!secondGuess)
{
secondGuess = true;
secondGuessIndex =
int.Parse(UnityEngine.EventSystems.EventSystem.current.currentSelectedGameObject.name);
secondGuessPuzzle = gamePuzzles[secondGuessIndex].name;
btns[secondGuessIndex].image.sprite = gamePuzzles[secondGuessIndex];
countGuesses++;
StartCoroutine(CheckIfThePuzzlesMatch());
}
}
IEnumerator CheckIfThePuzzlesMatch()
{
yield return new WaitForSeconds(2f);
if (firstGuessPuzzle == secondGuessPuzzle && firstGuessIndex != secondGuessIndex)
{
yield return new WaitForSeconds(1f);
btns[firstGuessIndex].interactable = false;
btns[secondGuessIndex].interactable = false;
btns[firstGuessIndex].image.color = new Color(0, 0, 0, 0);
btns[secondGuessIndex].image.color = new Color(0, 0, 0, 0);
CheckIfTheGameIsFinished();
}
else
{
yield return new WaitForSeconds(1f);
btns[firstGuessIndex].image.sprite = bgImage;
btns[secondGuessIndex].image.sprite = bgImage;
}
yield return new WaitForSeconds(1f);
firstGuess = secondGuess = false;
}
void CheckIfTheGameIsFinished()
{
countCorrectGuesses++;
if (countCorrectGuesses == gameGuesses)
{
Debug.Log("Game Finished");
}
}
void Shuffle(List<Sprite> list)
{
for (int i = 0; i < list.Count; i++)
{
Sprite temp = list[i];
int randomIndex = Random.Range(i, list.Count);
list[i] = list[randomIndex];
list[randomIndex] = temp;
}
}
}

Unity- Load - Unlock levels and check if there are next levels existing

I am working on small game similar to angry birds since I am new to both unity
and C#. I want to make load and unlock next level if all enemies are dead and check if new level exist
(if not) return back to Level selection scene.
This is what I tried:
LevelScript
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelScript : MonoBehaviour
{
[SerializeField] string _nextLevelName;
Monster[] _monsters;
void OnEnable()
{
_monsters = FindObjectsOfType<Monster>();
}
private void Update()
{
int currentLevel = SceneManager.GetActiveScene().buildIndex ;
if (currentLevel >= PlayerPrefs.GetInt("levelsUnlocked"))
{
PlayerPrefs.SetInt("levelsUnlocked", currentLevel );
}
if (MonsterAreAllDead())
{
GoToNextLevel();
}
Debug.Log("Level" + PlayerPrefs.GetInt("levelsUnlocked") + "UNLOCKED");
}
public void Pass()
{
int currentLevel = SceneManager.GetActiveScene().buildIndex;
if (currentLevel >= PlayerPrefs.GetInt("levelsUnlocked") )
{
PlayerPrefs.SetInt("levelsUnlocked", currentLevel + 1);
};
}
bool MonsterAreAllDead()
{
foreach (var monster in _monsters)
{
if (monster.gameObject.activeSelf)
return false;
}
return true;
}
void GoToNextLevel()
{
Debug.Log("Go to next level" + _nextLevelName);
SceneManager.LoadScene(_nextLevelName);
}
}
and Level Manager
public class LevelManager : MonoBehaviour
{
int levelsUnlocked;
public Button[] buttons;
void Start()
{
levelsUnlocked = PlayerPrefs.GetInt("levelsUnlocked", 1);
for (int i = 0; i < buttons.Length; i++)
{
buttons[i].interactable = false;
}
for (int i = 0; i < levelsUnlocked; i++)
{
buttons[i].interactable = true;
}
}
public void LoadLevel(int levelIndex)
{
SceneManager.LoadScene(levelIndex);
}
I got these 2 scripts and I attached both canvas and my buttons and Level script to my levels.
Problem is that every level gets unlocked at begin and after completeing levels automatically
it want to go to next level whic is not exist yet.
Pls help me. Sorry if my question is stupid and for bad english.
You should make an array of scene in your LevelManager that know all your levels (in order)
and for getting next level you can get the position of your actual scene in the array and check the next one.
somethink like
pseudocode :
[Serialized]
Scene[] AllScenes
void GoToNextLevel()
{
int currentLevelPos = AllScenes.IndexOf(currentScene);
if (AllScenes[currentLevelPos + 1] != null)
Load Next Level
else
Go To Level Menu
}

How to stop tile animation in Unity TileSystem?

I'm trying to understand tileSystem build in Unity, and i don't know how to stop animation in AnimatedTiles.
Once animation is started, there is no way i can think of to stop this. I'm working on Unity 2018.3.2f1, but i think that TileSystem is similar in next versions.
Only code in AnimatedTile handling animation is:
public override void GetTileData(Vector3Int location, ITilemap tileMap, ref TileData tileData)
{
tileData.transform = Matrix4x4.identity;
tileData.color = Color.white;
if (m_AnimatedSprites != null && m_AnimatedSprites.Length > 0)
{
tileData.sprite = m_AnimatedSprites[0];
tileData.colliderType = m_TileColliderType;
}
}
public override bool GetTileAnimationData(Vector3Int location, ITilemap tileMap, ref TileAnimationData tileAnimationData)
{
if (m_AnimatedSprites.Length > 0)
{
tileAnimationData.animatedSprites = m_AnimatedSprites;
tileAnimationData.animationSpeed = Random.Range(m_MinSpeed, m_MaxSpeed);
tileAnimationData.animationStartTime = m_AnimationStartTime;
return true;
}
return false;
}
I want to stop animation after some time (like 3 seconds) or after last frame. Any help would be appritiated!
So after some time a got workaround and it looks like this :
public class TileBump : MonoBehaviour
{
public Transform m_GridParent;
public GameObject m_TileMap_Prefab;
public AnimatedTile m_tilePrefabAnimated;
public Tile m_tilePrefabStatic;
private Tilemap map;
void Start()
{
StartCoroutine(EStart());
}
public IEnumerator EStart()
{
GameObject t = Instantiate(m_TileMap_Prefab, m_GridParent);
map = t.GetComponent<Tilemap>();
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
map.SetTile(new Vector3Int(i, j, 0), m_tilePrefabAnimated);
StartCoroutine(Operation(new Vector3Int(i, j, 0)));
yield return new WaitForSeconds(0.3f);
}
}
}
public IEnumerator Operation(Vector3Int x)
{
yield return new WaitForSeconds(m_tilePrefabAnimated.m_AnimatedSprites.Length / m_tilePrefabAnimated.m_AnimationSpeed);
map.SetTile(x, m_tilePrefabStatic);
}
}
BUT. What i understood here is that tiles are not for that. Every tile in TileMap refer to ScriptableObject, so every animation will be same in every frame.
However if someone need this kind of effect, its one way to do it.

Unity and Visual Studio - Issues with a button not working

So I downloaded the assets file of the final version of the Sloan Kelly Card Game Project from YouTube Link to the video where you can download it in the description and I was playing around with the game it was all working fine.
I then re-opened the game the project the next day and the 'Stick Button' is no longer working. It appears on screen and appears intractable but it will not press.
I made a couple of changes to the code to make it easier to read but didn't think i had changed anything to do with this button. The rest of the code runs fine just this one button.
I'm unsure how to link my project assets package on this sight, but I can show you the code for the button and was just wondering if anyone can see the problem straight away / could you tell me how to link the whole file so you can get a better look at what is happening.
Thanks in advance
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class GameController : MonoBehaviour
{
int dealersFirstCard = -1;
public CardStack player;
public CardStack dealer;
public CardStack deck;
public Button hitButton;
public Button stickButton;
public Button playAgainButton;
public Text winnerText;
/*
* Cards dealt to each player
* First player hits/sticks/bust
* Dealer's turn unyil minimum of 17 value in hand
* Dealers cards: first card is hidden, other cards are facing
*/
#region Hit
public void Hit()
{
player.Push(deck.Pop());
if (player.HandValue() > 21)
{
hitButton.interactable = false;
stickButton.interactable = false;
StartCoroutine(DealersTurn());
}
}
#endregion
#region Stick
public void Stick()
{
hitButton.interactable = false;
stickButton.interactable = false;
StartCoroutine(DealersTurn());
}
#endregion
#region Play Again
public void PlayAgain()
{
playAgainButton.interactable = false;
player.GetComponent<CardStackView>().Clear();
dealer.GetComponent<CardStackView>().Clear();
deck.GetComponent<CardStackView>().Clear();
deck.CreateDeck();
winnerText.text = "";
hitButton.interactable = true;
stickButton.interactable = true;
dealersFirstCard = -1;
StartGame();
}
#endregion
void Start()
{
StartGame();
}
void StartGame()
{
for (int i = 0; i < 2; i++)
{
player.Push(deck.Pop());
HitDealer();
}
}
void HitDealer()
{
int card = deck.Pop();
if (dealersFirstCard < 0)
{
dealersFirstCard = card;
}
dealer.Push(card);
if (dealer.CardCount >= 2)
{
CardStackView view = dealer.GetComponent<CardStackView>();
view.Toggle(card, true);
}
}
#region Dealers turn
IEnumerator DealersTurn()
{
hitButton.interactable = false;
stickButton.interactable = false;
CardStackView view = dealer.GetComponent<CardStackView>();
view.Toggle(dealersFirstCard, true);
view.ShowCards();
yield return new WaitForSeconds(1f);
while (dealer.HandValue() < 17)
{
HitDealer();
yield return new WaitForSeconds(1f);
}
if (player.HandValue() > 21 || (dealer.HandValue() >= player.HandValue() && dealer.HandValue() <= 21))
{
winnerText.text = "You Lose!";
}
else if (dealer.HandValue() > 21 || (player.HandValue() <= 21 && player.HandValue() > dealer.HandValue()))
{
winnerText.text = "You Win!";
}
else
{
winnerText.text = "The house wins!";
}
yield return new WaitForSeconds(1f);
playAgainButton.interactable = true;
}
#endregion
}

Unity Crashing on Play, most likely infinite While loop, but cannot locate issue

thanks for reading.
I'm currently building a small memory card game in Unity using C#. I have the main portion of code finished but when I press the play button on a certain scene Unity freezes.
I believe it is due to an infinite While loop, but I can not find the issue. I would really appreciate any help anyone can offer. I will leave my code below. Thanks in advance.
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using UnityEngine;
public class Pairs : MonoBehaviour {
public Sprite[] face; //array of card faces
public Sprite back;
public GameObject[] deck; //array of deck
public Text pairsCount;
private bool deckSetUp = false;
private int pairsLeft = 13;
// Update is called once per frame
void Update () {
if (!deckSetUp)
{
SetUpDeck();
}
if (Input.GetMouseButtonUp(0)) //detects left click
{
CheckDeck();
}
}//Update
void SetUpDeck()
{
for (int ix = 0; ix < 2; ix++)
{
for(int i = 1; i < 14; i++)//sets up card value (2-10 JQKA)
{
bool test = false;
int val = 0;
while (!test)
{
val = Random.Range(0, deck.Length);
test = !(deck[val].GetComponent<Card>().SetUp);
}//while
//sets up cards
deck[val].GetComponent<Card>().Number = i;
deck[val].GetComponent<Card>().SetUp = true;
}//nested for
}//for
foreach (GameObject crd in deck)
{
crd.GetComponent<Card>().setUpArt();
}
if (!deckSetUp)
{
deckSetUp = true;
}
}//SetUpDeck
public Sprite getBack()
{
return back;
}//getBack
public Sprite getFace(int i)
{
return face[i - 1];
}//getFace
void CheckDeck()
{
List < int > crd = new List<int>();
for(int i = 0; i < deck.Length; i++)
{
if(deck[i].GetComponent<Card>().State == 1)
{
crd.Add(i);
}
}
if(crd.Count == 2)
{
CompareCards(crd);
}
}//CheckDeck
void CompareCards(List<int> crd)
{
Card.NO_TURN = true; //stops cards turning
int x = 0;
if(deck[crd[0]].GetComponent<Card>().Number ==
deck[crd[1]].GetComponent<Card>().Number)
{
x = 2;
pairsLeft--;
pairsCount.text = "PAIRS REMAINING: " + pairsLeft;
if(pairsLeft == 0) // goes to home screen when game has been won
{
SceneManager.LoadScene("Home");
}
}
for(int j = 0; j < crd.Count; j++)
{
deck[crd[j]].GetComponent<Card>().State = x;
deck[crd[j]].GetComponent<Card>().PairCheck();
}
}//CompareCards
}
I believe the issue lies in the while(!test) but i do not know why test never become true.
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
public class Card : MonoBehaviour {
public static bool NO_TURN = false;
[SerializeField]
private int cardState; //state of card
[SerializeField]
private int cardNumber; //Card value (1-13)
[SerializeField]
private bool _setUp = false;
private Sprite back; //card back (Green square)
private Sprite face; //card face (1-10 JQKA)
private GameObject pairsManager;
void Begin()
{
cardState = 1; //cards face down
pairsManager = GameObject.FindGameObjectWithTag("PairsManager");
}
public void setUpArt()
{
back = pairsManager.GetComponent<Pairs>().getBack();
face = pairsManager.GetComponent<Pairs>().getFace(cardNumber);
turnCard();//turns the card
}
public void turnCard() //handles turning of card
{
if (cardState == 0)
{
cardState = 1;
}
else if(cardState == 1)
{
cardState = 0;
}
if (cardState == 0 && !NO_TURN)
{
GetComponent<Image>().sprite = back; // shows card back
}
else if (cardState == 1 && !NO_TURN)
{
GetComponent<Image>().sprite = face; // shows card front
}
}
//setters and getters
public int Number
{
get {return cardNumber;}
set { cardNumber = value;}
}
public int State
{
get { return cardState; }
set { cardState = value; }
}
public bool SetUp
{
get { return _setUp; }
set { _setUp = value; }
}
public void PairCheck()
{
StartCoroutine(pause ());
}
IEnumerator pause()
{
yield return new WaitForSeconds(1);
if (cardState == 0)
{
GetComponent<Image>().sprite = back;
}
else if (cardState == 1)
{
GetComponent<Image>().sprite = face;
}
}
}
Thank you for reading, I will post a link to the github repository if that helps.
github repository
Your deck array has at least one card in it that has _setUp set to true which would make it go in a infinite loop.
The reason it goes in a infinite loop is because it will have set all available _setUp to true and it would keep looking for _setUp that are set to false and it will never find any.
The reason you need at least 26 object that have _setUp to false is because in the nested for loop you loop 13 times and then you do that twice which gives a total of 26 loops. So you need at least 26 objects.
What you can do to make sure that they're all false is to set them all to false before entering the for loop
for(int i = 0; i < deck.Length; i++)
{
deck[i].GetComponent<Card>().SetUp = false;
}

Categories

Resources