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
}
Related
I am creating a small game with five levels and a timer to see how fast the player can complete all five levels. I have a script that is supposed to take the finished time from the timer and convert it to a PlayerPref so that it can always be saved to the leaderboard. Eash leaderboard entry is its own separate text object, with its own script.
public class ScoreTimer01 : MonoBehaviour
{
public Text theText;
public void Awake()
{
theText.text = GetComponent<Text>().text;
if (PlayerPrefs.HasKey("time0"))
{
theText.text = PlayerPrefs.GetFloat("time0").ToString("mm':'ss'.'ff");
}
else
{
theText.text = "0";
}
}
}
But the leaderboard texts never change, is there something that I am missing?
Here is the timer script:
public class Timer : MonoBehaviour
{
Text text;
float theTime;
public float speed = 1;
public static bool playing;
static List<float> bestTimes = new List<float>();
static int totalScores = 5;
void Awake()
{
LoadTimes();
}
// Start is called before the first frame update
void Start()
{
text = GetComponent<Text>();
playing = true;
}
static public void EndTimer()
{
playing = false;
CheckTime(FinalTime.finalTime);
}
// Update is called once per frame
void Update()
{
if(playing == true)
{
TimerController.theTime += Time.deltaTime * speed;
int minutes = (int)(TimerController.theTime / 60f) % 60;
int seconds = (int)(TimerController.theTime % 60f);
int milliseconds = (int)(TimerController.theTime * 1000f) % 1000;
text.text = "Time: " + minutes.ToString("D2") + ":" + seconds.ToString("D2") + ":" + milliseconds.ToString("D2");
}
}
static public void LoadTimes()
{
for (int i = 0; i < totalScores; i++)
{
string key = "time" + i;
if (PlayerPrefs.HasKey(key))
{
bestTimes.Add(PlayerPrefs.GetFloat(key));
}
}
}
static public void CheckTime(float time)
{
// if there are not enough scores in the list, go ahead and add it
if (bestTimes.Count < totalScores)
{
bestTimes.Add(time);
// make sure the times are in order from highest to lowest
bestTimes.Sort((a, b) => b.CompareTo(a));
SaveTimes();
}
else
{
for (int i = 0; i < bestTimes.Count; i++)
{
// if the time is smaller, insert it
if (time < bestTimes[i])
{
bestTimes.Insert(i, time);
// remove the last item in the list
bestTimes.RemoveAt(bestTimes.Count - 1);
SaveTimes();
break;
}
}
}
}
static public void SaveTimes()
{
for (int i = 0; i < bestTimes.Count; i++)
{
string key = "time" + i;
PlayerPrefs.SetFloat(key, bestTimes[i]);
}
}
void OnDestroy()
{
PlayerPrefs.Save();
}
}
I can't figure out if the PlayerPrefs are not saving correctly or the script to change the text is incorrect. I am very new to C# and unity as a whole, so any help is appreciated.
I got a quick question for you. I have already everything prepared. I am making a simple launching game, where you need to kill all enemies (not time-based) to pass to the next level. I have 2 methods GoToNextLevel() and AllMonsterDied(); .
Need to make something like player have 3 attempts (3 launchings whenever he wants not based on time).
Then every launch checks if any monster left. If does just show 1 attemps less. After 3 times just restart scene.
Thanks a lot since I am new to both c# and unity that would mean the world to me.
public class LevelController: MonoBehaviour
{
[SerializeField] string _nextLevelName;
Monster[] _monsters;
void OnEnable()
{
_monsters = FindObjectsOfType<Monster>();
}
void Update()
{
int currentLevel = SceneManager.GetActiveScene().buildIndex;
if (currentLevel >= PlayerPrefs.GetInt("levelsUnlocked"))
{
PlayerPrefs.SetInt("levelsUnlocked", currentLevel + 1);
}
if (MonsterAreAllDead() )
{
GoToNextLevel();
}
Debug.Log("Level" + PlayerPrefs.GetInt("levelsUnlocked") + "UNLOCKED");
}
void GoToNextLevel()
{
Debug.Log("Go to next level" + _nextLevelName);
SceneManager.LoadScene(_nextLevelName);
}
bool MonsterAreAllDead()
{
foreach (var monster in _monsters)
{
if (monster.gameObject.activeSelf)
return false;
}
return true;
}
}
Line from Monster.cs
private void OnCollisionEnter2D(Collision2D collision)
{
if (SouldDieFromCollision(collision))
{
tickSource.Play();
StartCoroutine(Die());
}
}
IEnumerator Die()
{
_hasDied =true;
GetComponent<SpriteRenderer>().sprite=_deadsprite;
_particleSystem.Play();
yield return new WaitForSeconds(1);
gameObject.SetActive(false);
}
Let's say you want the next level launch attempt happen when the player hits N key on the keyboard.
When the player hits the key, check if all monsters are dead. If so, call GoToNextLeve(), otherwise take off 1 attempt from the attempts available and restart the current scene.
int attempts;
void OnEnable()
{
attempts = 3;
}
void Update()
{
if (Input.GetKeyUp(KeyCode.H)
{
TryGoToNextLevel();
}
}
void TryGoToNextLevel()
{
if (MonsterAreAllDead() )
{
GoToNextLevel();
}
else
{
attempts--;
}
if (attempts <= 0)
{
SceneManager.LoadScene(
SceneManager.GetActiveScene().name);
}
}
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!).
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.
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;
}