C# Coroutine repeating a specific part? - c#

I am making a cookie clicker game for a school project and for my achievement system I want to have a simple animation pop up, and text in a separate menu script that already works perfectly fine.
The issue I am having is that the first two achievements work fine, but after the second one pops up, it just repeats with "Test B" and "Test C" repeating infinitely in the console.
I've tried to have values try to make an 'if' statement void but nothing seems to work.
I feel like I am just eating my own tail with all of the Boolean values I created to get this far.
This is my code below, from only a single script in my Unity file.
Hopefully this has enough information needed for guidance.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Achievements : MonoBehaviour
{
[SerializeField] GameObject achievementBanner;
[SerializeField] GameObject achievementText;
[SerializeField] GameObject achievementBannerAnimation;
[SerializeField] GameObject achievementTextAnimation;
[SerializeField] GameObject achiOne;
[SerializeField] GameObject achiTwo;
[SerializeField] GameObject achiThree;
[SerializeField] GameObject achiFour;
[SerializeField] GameObject achiFive;
[SerializeField] GameObject achiSix;
[SerializeField] public static int achiementcount;
bool achievementrec = false;
bool achievementani = false;
bool achidone = false;
// Start is called before the first frame update
void Start()
{
achiOne.SetActive(false);
achiTwo.SetActive(false);
achiThree.SetActive(false);
achiFour.SetActive(false);
achiFive.SetActive(false);
achiSix.SetActive(false);
achievementBanner.SetActive(false);
achievementText.SetActive(false);
achievementText.GetComponent<Text>().text = "Achievement: First cookie!";
}
// Update is called once per frame
void Update()
{
print(achiementcount);
print(achidone);
if (achiementcount == 1)
{
if (!achievementani)
{
if (!achievementrec)
{
print("Test D");
achiOne.SetActive(true);
achievementBanner.SetActive(true);
achievementText.SetActive(true);
StartCoroutine(AchievementGot());
///achidone = true;
}
}
}
if (achidone == true)
{
///print("Test");
///achidone = false;
if (achiementcount == 2)
{
achievementText.GetComponent<Text>().text = "Achievement: First Clicker!";
achievementani = false;
achievementrec = false;
if (!achievementani)
{
if (!achievementrec)
{
print("Test C");
achiTwo.SetActive(true);
achievementBanner.SetActive(true);
achievementText.SetActive(true);
///achidone = true;
StartCoroutine(AchievementGot());
}
}
}
}
///print(achidone);
if (achidone == true)
{
///achidone = false;
if (achiementcount == 3)
{
achievementText.GetComponent<Text>().text = "Achievement: First Upgrade!";
achievementani = false;
achievementrec = false;
if (!achievementani)
{
if (!achievementrec)
{
print("Test A");
achiThree.SetActive(true);
achievementBanner.SetActive(true);
achievementText.SetActive(true);
StartCoroutine(AchievementGot());
}
}
}
}
}
IEnumerator AchievementGot()
{
///achievementrec = true;
achievementBannerAnimation.GetComponent<Animation>().Play("Achievement");
achievementTextAnimation.GetComponent<Animation>().Play("AchiText");
yield return new WaitForSeconds(6);
achidone = true;
print("Test B");
///print(achidone);
achievementBanner.SetActive(false);
achievementText.SetActive(false);
achievementani = true;
achievementrec = true;
///achidone = false;
}
}

First of all you should use a List/Array.
Then you probably also want to show the banner only ONCE when the achievement is unlocked the first time so you should also keep track of which one you already showed.
E.g. something like
[Serializable]
public class Achievement
{
// text to display in the banner
public string Label;
// the object to enable
public GameObject GameObject;
// has this been achieved
public bool Unlocked;
// has this achievement been displayed
public bool Displayed;
}
public class Achievements : MonoBehaviour
{
// in general instead of many GetComponent calls use the correct type right away in the Inspector
[SerializeField] private GameObject achievementBanner;
[SerializeField] private Text achievementText;
[SerializeField] private Animation achievementBannerAnimation;
[SerializeField] private Animation achievementTextAnimation;
[SerializeField] private Achievement[] achievements;
// is currently a display routine running already?
private bool currentlyDisplaying;
private void Start()
{
// initially hide all objects
foreach (var achievement in achievements)
{
achievement.GameObject.SetActive(false);
}
achievementBanner.SetActive(false);
achievementText.gameObject.SetActive(false);
}
private void Update()
{
// if currently a display routine is running do nothing
if (currentlyDisplaying) return;
// otherwise go through all achievements
foreach (var achievement in achievements)
{
// check if one is unlocked but hasn't been displayed yet
if (achievement.Unlocked && !achievement.Displayed)
{
// set to displayed and display it
StartCoroutine(DisplayAchievement(achievement));
// break to only handle one at a time
break;
}
}
}
private IEnumerator DisplayAchievement(Achievement achievement)
{
// just in case if other routine is already running do nothing
if (currentlyDisplaying) yield break;
// block other routines from being started
currentlyDisplaying = true;
// set displayed so it is not displayed again
achievement.Displayed = true;
// Enable your objects and set the text accordingly
achievementText.text = achievement.Label;
achievementText.gameObject.SetActive(true);
achievementBanner.SetActive(true);
achievement.GameObject.SetActive(true);
achievementBannerAnimation.Play("Achievement");
achievementTextAnimation.Play("AchiText");
yield return new WaitForSeconds(6);
achievementBanner.SetActive(false);
achievementText.gameObject.SetActive(false);
// allow the next routine to start
currentlyDisplaying = false;
}
}
even better than poll checking within Update would be to rather go for events like e.g.
[Serializable]
public class Achievement
{
// text to display in the banner
public string Label;
// the object to enable
public GameObject GameObject;
// has this been achieved
// private but (de)serializable e.g. for saving via Json etc later on
// and editable via Inspector
[SerializeField] private bool unlocked = false;
// public read-only access
public bool Unlocked => unlocked;
public void Unlock()
{
// is this already unlocked anyway?
if(!unlocked)
{
// if not unlock and invoke event
unlocked = true;
OnUnlocked?.Invoke(this);
}
}
// global event to subscribe to for all achievement unlocks
public static event Action<Achievement> OnUnlocked;
}
and then listen to this
public class Achievements : MonoBehaviour
{
// in general instead of many GetComponent calls use the correct type right away in the Inspector
[SerializeField] private GameObject achievementBanner;
[SerializeField] private Text achievementText;
[SerializeField] private Animation achievementBannerAnimation;
[SerializeField] private Animation achievementTextAnimation;
[SerializeField] private Achievement[] achievements;
// is currently a display routine running already?
private bool currentlyDisplaying;
// First-In first-out collection to work off the unlocked achievements in the UI
private readonly Queue<Achievement> achievementsToDisplay = new ();
private void Start()
{
// initially hide all objects
foreach (var achievement in achievements)
{
achievement.GameObject.SetActive(false);
}
achievementBanner.SetActive(false);
achievementText.gameObject.SetActive(false);
// listen to the event
Achievement.OnUnlocked += OnAchievementUnlocked;
}
private void OnDestroy()
{
// to avoid exceptions unregister the listener from the event
Achievement.OnUnlocked -= OnAchievementUnlocked;
}
// called everytime an achievement is unlocked
private void OnAchievementUnlocked(Achievement achievement)
{
// add achievement to the queue to work
achievementsToDisplay.Enqueue(achievement);
// if there isn't a worker routine running already anyway
if (!currentlyDisplaying)
{
// start one now
StartCoroutine(DisplayAchievements());
}
}
private IEnumerator DisplayAchievement()
{
// just in case if other routine is already running do nothing
if (currentlyDisplaying) yield break;
// block other routines from being started
currentlyDisplaying = true;
// as long as there are new achievements in the queue simply continue directly with the next one
while(achievementsToDisplay.Count > 0)
{
var achievement = achievementsToDisplay.Dequeue();
// Enable your objects and set the text accordingly
achievementText.text = achievement.Label;
achievementText.gameObject.SetActive(true);
achievementBanner.SetActive(true);
achievement.GameObject.SetActive(true);
achievementBannerAnimation.Play("Achievement");
achievementTextAnimation.Play("AchiText");
yield return new WaitForSeconds(6);
achievementBanner.SetActive(false);
achievementText.gameObject.SetActive(false);
}
// when queue is worked off terminate for now
// and allow the next routine to be started later
currentlyDisplaying = false;
}
}

Related

How to call a method before another from 2 different scripts?

I am trying to call a method from the script Dice and after that method is executed the value of the variable diceValue will change. This is the value that I want to take and use in the method from the script Levizja.
Levizja.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Levizja : MonoBehaviour
{
public Transform[] lojtaret;
public GameObject zari;
private int numer = 0;
public Dice vleraEzarit;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
GetComponent<Rigidbody>().AddForceAtPosition(new Vector3(Random.Range(0, 500), Random.Range(0, 500) * 10, Random.Range(0, 500)), new Vector3(0, 0, 0), ForceMode.Force);
Debug.Log("U hodh zari me numer: " + Dice.getValue());
}
}
}
Dice.cs
using System.Collections.Generic;
using UnityEngine;
public class Dice : MonoBehaviour
{
Rigidbody rb;
bool hasLanded, thrown;
Vector3 initPosition;
[SerializeField] private static int diceValue;
private static int numriLojtareve;
//public int diceValue;
public DiceSides[] diceSides;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Reset();
RollDice();
}
if (rb.IsSleeping() && !hasLanded && thrown)
{
hasLanded = true;
rb.useGravity = false;
rb.isKinematic = true;
SideValueCheck();
}
else if (rb.IsSleeping() && hasLanded && diceValue == 0)
RollAgain();
}
void SideValueCheck()
{
diceValue = 0;
foreach(DiceSides side in diceSides)
{
if (side.OnGround())
{
diceValue = side.getValue();
Debug.Log(diceValue + " has been rolled!");
}
}
}
public static int getValue()
{
return diceValue;
}
}
Some of the methods are not included just to address only the issue.
I want to execute the Update method in Dice.cs which will call SideValueCheck method. This way the variable diceValue will be updated. After that I want the Update method in Levizja.cs to execute this way the new value will be stored there.
What happens is the first time I get the value 0 and the next run I get the last value that dice had. So if first time it landed 3 it shows 0. Next time it lands 2 it shows 3 and so on.
You could adjust this in the Script Execution Order and force a certain order of the executions of the same event message type (Update in this case).
However, this won't be enough. You rather want to wait until the dice has a result.
So before/instead of touching the execution order I would rather rethink the code structure and do something else. E.g. why do both your scripts need to check the user input individually?
Rather have one script call the methods of the other one event based. There is also no reason to have things static here as you already have a reference to an instance of a Dice anyway in vleraEzarit
public class Dice : MonoBehaviour
{
[Header("References")]
[SerializeField] private Rigidbody _rigidbody;
[SerializeField] private DiceSides[] diceSides;
[Header("Debug")]
[SerializeField] private int diceValue;
private bool hasLanded, thrown;
private Vector3 initPosition;
private int numriLojtareve;
// if you still also want to also provide a read-only access
// to the current value
public int DiceValue => diceValue;
// Event to be invoked everytime there is a new valid dice result
public event Action<int> OnHasResult;
private void Awake()
{
if(!_rigidbody)
{
_rigidbody = GetComponent<Rigidbody>();
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Reset();
RollDice();
}
if (_rigidbody.IsSleeping() && !hasLanded && thrown)
{
hasLanded = true;
_rigidbody.useGravity = false;
_rigidbody.isKinematic = true;
SideValueCheck();
}
else if (_rigidbody.IsSleeping() && hasLanded && diceValue == 0)
{
RollAgain();
}
}
void SideValueCheck()
{
foreach(DiceSides side in diceSides)
{
if (side.OnGround())
{
diceValue = side.getValue();
Debug.Log(diceValue + " has been rolled!");
// Invoke the event and whoever is listening to it will be informed
OnHasResult?.Invoke(diceValue);
// also I would return here since i makes no sense to continue
// checking the other sides if you already have a result
return;
}
}
// I would move this here
// In my eyes it is clearer now that this is the fallback case
// and only happening if nothing in the loop matches
diceValue = 0;
}
}
And then make your Levizja listen to this event and only act once it is invoked like e.g.
public class Levizja : MonoBehaviour
{
[Header("References")]
[SerializeField] private Rigidbody _rigidbody;
[SerializeField] private Transform[] lojtaret;
[SerializeField] private GameObject zari;
[SerializeField] private Dice vleraEzarit;
private int numer = 0;
private void Awake()
{
if(!_rigidbody)
{
_rigidbody = GetComponent<Rigidbody>();
}
// Attach a callback/listener to the event
// just out of a habit I usually remove it first to make sure it can definitely only be added once
vleraEzarit.OnHasResult -= HandleDiceResult;
vleraEzarit.OnHasResult += HandleDiceResult;
}
private void OnDestroy()
{
// make sure to remove callbacks once not needed anymore
// to avoid exceptions
vleraEzarit.OnHasResult -= HandleDiceResult;
}
// This is called ONCE everytime the dice has found a new result
private void HandleDiceResult(int diceValue)
{
_rigidbody.AddForceAtPosition(new Vector3(Random.Range(0, 500), Random.Range(0, 500) * 10, Random.Range(0, 500)), new Vector3(0, 0, 0), ForceMode.Force);
Debug.Log("U hodh zari me numer: " + Dice.getValue());
}
}
Ideally the diceValue should be returned by the method and Dice should not have a state. If you absolutely need Dice to have a state then it should not be static.

Why when making start a new game when it's loading the game scene over again it also remember one flag state as true?

The content is a bit long but all the three scripts parts are connected.
In one script if in the game the player enters the correct code a public static flag m_hasOpened becomes true:
private void HandleInputCode(int inputCode)
{
if (inputCode == _targetCode)
{
Debug.Log("Code correct!", this);
anim.Play("Crate_Open");
m_hasOpened = true;
And in the same script I return this f lag from a method :
public bool HasOpened()
{
return m_hasOpened;
}
Then in another script I'm checking if this flag is true. If it's opened means the m_hasOpened is true then set another public static flag to true:
if (allDetectedItems.Count == 2)
{
objectsNames = allDetectedItems.Select(item => item.transform.name).ToList();
if (objectsNames.Contains("NAVI") && objectsNames.Contains("Security Keypad"))
{
InteractableItem navi = allDetectedItems.Single(item => item.transform.name == "NAVI");
InteractableItem securitykeypad = allDetectedItems.Single(item => item.transform.name == "Security Keypad");
var crate = GameObject.Find("Crate_0_0");
var test = crate.gameObject.GetComponent<UnlockCrate>().HasOpened();
if (crate.gameObject.GetComponent<UnlockCrate>().HasOpened())
{
primaryTarget = navi;
var rig_f_middle = GameObject.Find("rig_f_middle.02.R");
startMovingNAVI = true;
}
Now startMovingNAVI is true.
In a third script I'm start moving the NAVI is startMovingNAVI is true:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveNavi : MonoBehaviour
{
public GameObject rig_f_middle;
public float speed;
public float distanceFromTarget;
public static bool naviChildOfHand = false;
public GameObject naviParent;
public bool startedFade = false;
public FadeInOut fadeInOut;
private void Start()
{
}
private void Update()
{
if (IKControl.startMovingNAVI == true && startedFade == false)
{
var v = rig_f_middle.transform.position - transform.position;
if (v.magnitude < distanceFromTarget)
{
startedFade = true;
StartCoroutine(fadeInOut.Fade(FadeInOut.FadeDirection.Out));
StartCoroutine(fadeInOut.Fade(FadeInOut.FadeDirection.In));
naviChildOfHand = true;
return;
}
Vector3 moveDir = v.normalized;
transform.position += moveDir * speed * Time.deltaTime;
}
}
public void ChangeChild()
{
var parent = GameObject.Find("Navi Parent");
transform.parent = parent.transform;
transform.localPosition = parent.transform.localPosition;
transform.localRotation = Quaternion.identity;
transform.localScale = new Vector3(0.001f, 0.001f, 0.001f);
}
}
Now I press once the escape key back to the main menu so it's loading the main menu scene and removing the game scene. In the main menu I click on start a new game button :
public void ClickNewGameDialog(string ButtonType)
{
if (ButtonType == "Yes")
{
loading = false;
newGameDialog.SetActive(false);
StartCoroutine(sceneFader.FadeAndLoadScene(SceneFader.FadeDirection.In, _newGameButtonLevel));
}
if (ButtonType == "No")
{
GoBackToMainMenu();
}
}
And last in the SceneFader script :
public IEnumerator FadeAndLoadScene(FadeDirection fadeDirection, string sceneToLoad)
{
yield return Fade(fadeDirection);
SceneManager.LoadScene(sceneToLoad);
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
}
private void SceneManager_sceneLoaded(Scene arg0, LoadSceneMode arg1)
{
if (MenuController.loading == true)
{
var saveLoad = GameObject.Find("Save System").GetComponent<SaveLoad>();
saveLoad.Load();
MenuController.loading = false;
}
}
When it's a new game it's just loading the game scene the flag loading is false, but when it's loading the game scene as a new game for some reason in the MoveNavi script startMovingNavi is true:
if (IKControl.startMovingNAVI == true && startedFade == false)
Why is it true? If I started a new game without loading anything it should be false.
When I quit the game app in the editor not escape to the main menu just quit the app it self and then run over the game again and make a new game it's false and fine but in the game while the game is running if I make escape key then new game he remember this flag as true.
And I used a breakpoint it's never setting it to true again it's just true already in the MoveNavi script. and it happens only when making a new game while the game is running.
Strange because it's not remembering for example the flag startedFade as true but the flag startMovingNAVI he remember it as true.
if (IKControl.startMovingNAVI == true && startedFade == false)
You've told us that IKControl.startMovingNAVI is a static field, while startFade is not static.
And therein lies your issue. IKControl.startMovingNAVI will remain set to the last value you give it for the lifetime of your app. Unity will generally unload the app when it has to do a domain reload (for instance recompiling any changed scripts) but static variables do need more care when developing inside the Unity Editor.
From what I can tell of your code, you don't need IKControl.startMovingNAVI to be a static field. You could have a reference to your IKControl instance on your MoveNavi script. Then simply drag your IKControl component into the corresponding object field.
[SerializeField] private IKControl ikControl;
And in you IKControl script, make a few modifications to your code.
public bool startMovingNavi { get; private set; }
Now use ikControl.startMovingNAVI wherever you've been using IKControl.startMovingNAVI.
I'm also unsure if in your first (unnamed) script you need m_hasOpened to be static either. You could probably do away with that in the same fashion.
public bool hasOpened { get; private set; }
After doing this, and removing the static variables, you should find that your code won't be retaining the values from previous runs of the game.

How can I register to the event/s if the parent object is first not active when starting the game?

The script :
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SecurityKeypadSystem : MonoBehaviour
{
[Header("References")]
// rather let this class control the display text
[SerializeField] private TextMesh _text;
[Header("Settings")]
// also rather let this class control the length of a code
[SerializeField] private int _codeLength = 8;
[Header("Debugging")]
[SerializeField] private GameObject[] _keyPadNumbers;
[SerializeField] private List<int> _code = new List<int>();
// This will be invoked once the code length has reached the target length
public event Action<int> OnCodeComplete;
// Start is called before the first frame update
private void Start()
{
_keyPadNumbers = GameObject.FindGameObjectsWithTag("Keypad");
// register a callback to each key that handles the numbers
foreach (var keyPadNumber in _keyPadNumbers)
{
// It is save to remove an event even if it hasn't been added yet
// this makes sure it is only added exactly once
// only adding this here for the case you later have to move this again to Update for some reason ;)
var securityKeypadKeys = keyPadNumber.GetComponent<SecurityKeypadKeys>();
securityKeypadKeys.onKeyPressed -= HandleKeyPressed;
securityKeypadKeys.onKeyPressed += HandleKeyPressed;
securityKeypadKeys.onKeyPressed += SecurityKeypadKeys_onKeyPressed;
}
}
private void SecurityKeypadKeys_onKeyPressed(int value)
{
string gethere = "";
}
private void OnDestroy()
{
// just for completeness you should always remove callbacks as soon as they are not needed anymore
// in order to avoid any exceptions
foreach (var keyPadNumber in _keyPadNumbers)
{
var securityKeypadKeys = keyPadNumber.GetComponent<SecurityKeypadKeys>();
securityKeypadKeys.onKeyPressed -= HandleKeyPressed;
}
}
// this is called when a keypad key was pressed
private void HandleKeyPressed(int value)
{
// add the value to the list
_code.Add(value);
_text.text += value.ToString();
// Check if the code has reached the target length
// if not do nothing
if (_code.Count < _codeLength) return;
// if it reached the length combine all numbers into one int
var exponent = _code.Count;
float finalCode = 0;
foreach (var digit in _code)
{
finalCode =digit * Mathf.Pow(10, exponent);
exponent--;
}
// invoke the callback event
OnCodeComplete?.Invoke((int)finalCode);
// and reset the code
ResetCode();
}
// Maybe you later want an option to clear the code field from the outside as well
public void ResetCode()
{
_code.Clear();
_text.text = "";
}
// also clear the input if this gets disabled
private void OnDisable()
{
ResetCode();
}
}
Inside the Start I'm making this line :
_keyPadNumbers = GameObject.FindGameObjectsWithTag("Keypad");
but this return 0 because when I'm running the game the GameObjects that have the tag "keypad" are not active yet they are getting active later in the game.
Here is screenshot of the GameObject with this script attached to it and the Key Cubes gameobjects that are not active :

Everything breaks after using PlayOneShot

In a game I am working on, the player uses a cube to open doors. When the player interacts with the cube while near the door, the door slides open. I am now trying to make it so that while it slides open, it makes a sound as doors should but I kinda ran into some issues. When I tried to add in the sound for the door, it ignored the part where the door should open altogether.
Here is what I did:
I added an AudioSource to the Cube's child object, CORE because the Cube object already contains an AudioSource which will play at the same time as this sound and assigned it in my Cube's script...
public AudioSource Woosh;
void Start()
{
//Debug.Log("Script initialized!");
Hm = gameObject.GetComponent<AudioSource>();
anim = GameObject.Find("TheFirstDoor").GetComponent<Animator>();
//Door = GameObject.Find("TheFirstDoor").GetComponent<AudioSource>();
Woosh = GameObject.Find("CORE").GetComponent<AudioSource>();
}
Here's the interactive part of the script, it runs a check but for some reason, it is pretending CanBeKey is false...
public void OnActivate()
{
if(HasPlayed == false) //Have I played? Has the Time out passed?
{
HasPlayedFirstTime = true;
Hm.Play();
HasPlayed = true;
PlayTimeOut = 30.0f;
if(CanBeKey == true)
{
anim.Play("Door_Open");
//Door.Play();
StartCoroutine(PlayDaWoosh());
Debug.Log("OPENING!");
}
}
}
Now here is the IEnumerator part of the script, PlayDaWoosh()
IEnumerator PlayDaWoosh()
{
Woosh.PlayOneShot(Woosh.clip);
yield return new WaitForSeconds(Woosh.clip.length);
}
I am aware that the last code snippet is a bit messy but it was the best thing I can think of.
Here is the full script in case you are that curious.....
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CUBE_CORE : MonoBehaviour
{
public AudioSource Hm; // HM!!!
private bool HasPlayed = false;
public float PlayTimeOut = 0.0f;
public static bool CanBeKey = false;
public Animator anim;
public static bool HasPlayedFirstTime = false;
public static AudioSource Door;
public AudioSource Woosh;
// Start is called before the first frame update
void Start()
{
//Debug.Log("Script initialized!");
Hm = gameObject.GetComponent<AudioSource>();
anim = GameObject.Find("TheFirstDoor").GetComponent<Animator>();
//Door = GameObject.Find("TheFirstDoor").GetComponent<AudioSource>();
Woosh = GameObject.Find("CORE").GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
if(HasPlayed == true)
{
if (PlayTimeOut < 0.0f)
{
HasPlayed = false;
}
PlayTimeOut -= Time.smoothDeltaTime;
}
}
public void OnActivate()
{
if(HasPlayed == false) //Have I played? Has the Time out passed?
{
HasPlayedFirstTime = true;
Hm.Play();
HasPlayed = true;
PlayTimeOut = 30.0f;
if(CanBeKey == true)
{
anim.Play("Door_Open");
//Door.Play();
StartCoroutine(PlayDaWoosh());
Debug.Log("OPENING!");
}
}
}
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.name == "SecondDoor")
{
anim = GameObject.Find("DoorSecond").GetComponent<Animator>();
}
if(other.gameObject.name == "ThirdDoor")
{
anim = GameObject.Find("TheThirdDoor").GetComponent<Animator>();
}
if(other.gameObject.name == "FourthDoor")
{
anim = GameObject.Find("TheFourthDoor").GetComponent<Animator>();
}
}
IEnumerator PlayDaWoosh()
{
Woosh.PlayOneShot(Woosh.clip);
yield return new WaitForSeconds(Woosh.clip.length);
}
}
Expected result: Door opening and sound playing
Actual result: Neither happening unless I remove anything that has to do with the Woosh
I apologize ahead of time if I wasn't specific enough or if the question was answered somewhere else. I am relatively new here.

Instantiate Object Only once on game start Unity 3D

I have a game with several levels, each level has 6 scenes, the game start directly without any menu scene, and when the player open the game he can continue from the last scene that he already reached.
I want to instantiate some elements only on game opening (like Best score, Tap to play etc...), I mean that they should be instantiated only once on the start of the game (on the level he reached).
I tried this code in GameManager but it instantiate the elements in every scene:
public GameObject PlayButton;
bool GameHasEnded = false;
public float RestartDelay = 2f;
public float NextLevelDelay = 5f;
public int level_index;
private static bool loaded = false;
private void Start()
{
if (!loaded)
{
loaded = true;
level_index = PlayerPrefs.GetInt("Last_Level");
SceneManager.LoadScene(level_index);
}
GameObject canvas = GameObject.Find("Canvas");
GameObject play = Instantiate(PlayButton, canvas.transform.position, Quaternion.identity);
play.transform.SetParent(canvas.transform, false);
}
public void CompleteLevel()
{
Invoke("NextLevel", NextLevelDelay);
}
public void EndGame()
{
if (GameHasEnded == false)
{
GameHasEnded = true;
Invoke("Restart", RestartDelay);
}
}
void NextLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex +1);
level_index = SceneManager.GetActiveScene().buildIndex + 1;
PlayerPrefs.SetInt("Last_Level", level_index);
PlayerPrefs.Save();
}
void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().path);
}
You already have an if block there with the static flag loaded
Since you there load another scene you need a similar second flag e.g.
private static bool loadedPrefab = false;
private void Start()
{
if (!loaded)
{
loaded = true;
level_index = PlayerPrefs.GetInt("Last_Level");
SceneManager.LoadScene(level_index);
// Return because you don't want to execute the rest yet
// but instead in the new loaded scene
return;
}
// The same way skip if the prefab was already loaded before
if(!loadedPrefab)
{
loadedPrefab = true;
GameObject canvas = GameObject.Find("Canvas");
GameObject play = Instantiate(PlayButton, canvas.transform.position, Quaternion.identity);
play.transform.SetParent(canvas.transform, false);
}
}

Categories