Old scene still appears for short time when switching scene - c#

So I have a canvas which doesnt get destroyed because it holds and slider whih shows a progressbar. It also has a button which is interactable when the progress of loading is 0.9.. The problem i am having is when I click the button (see activateNewScene()). Basically what happens is when I click on the button is that i load my scene and my disable my canvas. But the problem is that after I disable my canvas the old scene is still shown for about 0.5 sec and than the new scene is loaded. What I want is that after the canvas gets disabled the new scene should show up. without seeing the old scene for short time.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SceneLoaderGameObject : MonoBehaviour {
private SceneLoader sl;
public Canvas cav;
void Start () {
sl = new SceneLoader ();
cav.GetComponent<Canvas> ().enabled = false;
DontDestroyOnLoad (this.gameObject);
DontDestroyOnLoad (cav.transform.parent);
}
public void setNewNameAndLoadScene(string name){
if (cav != null) {
Slider slid = cav.transform.GetChild (1).GetComponent<Slider> ();
Text tx = cav.transform.GetChild (2).GetComponent<Text> ();
Button bttn = cav.transform.GetChild (3).GetComponent<Button> ();
sl.setNewScene (name);
sl.setSliderAndTextToChange (slid, tx);
bttn.onClick.AddListener (() => activateNewScene ());
cav.GetComponent<Canvas> ().enabled = true;
cav.GetComponent<Canvas> ().sortingOrder = 1;
StartCoroutine (sl.LoadAsynchron (name, bttn));
}
}
public void activateNewScene(){
sl.AsgetOP().allowSceneActivation=true;
cav.GetComponent<Canvas> ().sortingOrder = 1;
cav.GetComponent<Canvas> ().enabled = false;
}
}
EDIT: Here is the code which loads the scene:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class SceneLoader {
AsyncOperation operation;
string sceneName;
private Text txx =null;
private Slider slider=null;
public SceneLoader(){
}
public void setSliderAndTextToChange(Slider sl, Text tx){
txx = tx;
slider = sl;
}
public void setNewScene(string sceneName){
if (sceneName.Length == 0) {
throw new UnityException ("Please enter a name");
}
this.sceneName = sceneName;
}
public IEnumerator LoadAsynchron(string myMain, Button bttn){
operation = SceneManager.LoadSceneAsync (myMain);
operation.allowSceneActivation = false;
while (operation.isDone == false) {
float progress = Mathf.Clamp01 (operation.progress / 0.9f);
slider.value = progress;
txx.text = progress * 100f + " %";
if (progress * 100f == 100f) {
bttn.interactable = true;
}
yield return null;
}
}
public AsyncOperation AsgetOP(){
return operation;
}
}

I see. I think its because of the canvas. Try to put the code where you disable the canvas and set the order to 0 in the function OnLevelWasLoaded . So basically this:
void OnLevelWasLoaded(){
cav.GetComponent<Canvas> ().sortingOrder = 0;
cav.GetComponent<Canvas> ().enabled = false;
}
public void activateNewScene(string name){
sl.AsgetOP ().allowSceneActivation = true;
Scene sc = SceneManager.GetSceneByName(name);
if (sc.IsValid ()) {
SceneManager.SetActiveScene(sc);
}
}
I added the scene name to the function to activate it.

When SceneManager was first released, it is known that isDone will only be true in when two conditions are met:
1.When the scene is done loading
2.When scene is activated
I don't know if this is still true but I will assume it is.
Basically, when you set allowSceneActivation to false, condition 2 is not met and you will run into issues. It looks like while (operation.isDone == false) will still be executing causing bttn.interactable = true; to also be executing every frame while you are trying to load new scene. This is my guess.
Replace while (operation.isDone == false) with while (operation.progress < 0.9f) then add bttn.interactable = true; to the end of that function just in-case.
public IEnumerator LoadAsynchron(string myMain, Button bttn)
{
operation = SceneManager.LoadSceneAsync(myMain);
operation.allowSceneActivation = false;
while (operation.progress < 0.9f)
{
float progress = Mathf.Clamp01(operation.progress / 0.9f);
slider.value = progress;
txx.text = progress * 100f + " %";
if (progress * 100f == 100f)
{
bttn.interactable = true;
}
yield return null;
}
if (!bttn.interactable)
bttn.interactable = true;
}
If that does not work, I suggest you use SceneManager.SetActiveScene function. See the enableScene function from this post. I've also seen bugs with this, you may want to file for bug report from the Editor if this is still a problem.

Related

Object reference not set to an instance of an object (Cannot Loot Object) C# Unity

Hello i have 2 scripts one is for Flashlight "Battery UI script" Storing how many batteries i have left
for reload a flashlight max 5 that script is working totally fine but when i try to loot a battery it occur a error
and i dont understand why because i setup input "Use" in Project Settings correctly because another key "Grab" is working fine on Key "Q" but "Use" on Key "F" not working and i getting this error what i do wrong?!
i will paste code bellow with screenshots as well
" Get Error in this line (see screenshot) "
Click for code screenshot error
battery GUI on top right corner 3/5 collected batteries
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class BatteryPickup : MonoBehaviour {
private Transform myTransform;
private GameObject MessageLabel;
private GameObject BatteryUIScript;
public bool EnableMessageMax = true;
public bool Enabled;
public float BatteryAdd = 0.01f;
public AudioClip pickupSound;//sound to playe when picking up this item
public string MaxBatteryText = "You have Max Batteries";
public Color MaxBatteryTextColor = Color.white;
public bool PickupMessage;
public string PickupTEXT = "Battery +1";
public Color PickupTextColor = Color.white;
void Start () {
myTransform = transform;//manually set transform for efficiency
}
public void UseObject (){
BatteryUIScript = GameObject.Find("Flashlight");
BatteryUI BatteryComponent = BatteryUIScript.GetComponent<BatteryUI>();
if (BatteryComponent.EnableBattery == true)
{
Enabled = true;
}
if(BatteryComponent.EnableBattery == false){
Enabled = false;
if(EnableMessageMax){StartCoroutine(MaxBatteries());}
}
if(Enabled){
StartCoroutine(SendMessage());
BatteryComponent.Batteries += BatteryAdd;
if(pickupSound){AudioSource.PlayClipAtPoint(pickupSound, myTransform.position, 0.75f);}
this.GetComponent<Renderer>().enabled = false;
this.GetComponent<Collider>().enabled = false;
}
}
public IEnumerator SendMessage (){
MessageLabel = GameObject.Find("UI_MessageLabel");
Text Message = MessageLabel.GetComponent<Text>();
/* Message Line */
EnableMessageMax = false;
Message.enabled = true;
Message.color = PickupTextColor;
Message.text = PickupTEXT;
yield return new WaitForSeconds(2);
Message.enabled = false;
EnableMessageMax = true;
}
public IEnumerator MaxBatteries (){
MessageLabel = GameObject.Find("UI_MessageLabel");
Text Message = MessageLabel.GetComponent<Text>();
/* Message Line */
if(!Enabled){
EnableMessageMax = false;
Message.enabled = true;
Message.color = MaxBatteryTextColor;
Message.text = MaxBatteryText;
yield return new WaitForSeconds(3);
Message.CrossFadeAlpha(0f, 2.0f, false);
yield return new WaitForSeconds(4);
Message.enabled = false;
EnableMessageMax = true;
}
}
}

Unity Corountine started but not ending

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!).

OnPointerEnter is not being called

I'm trying to animate the button while it is being gazed on. I have got the following code in which I Raycast from a sphere to find the button that it hits.
var eventDataCurrentPosition = new PointerEventData(EventSystem.current);
eventDataCurrentPosition.position = screenPosition;
var results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
foreach (var result in results)
{
Debug.Log(result.gameObject.name);
}
In order to animate the button, I'm adding the following code to the button. Unfortunately, the OnPointerEnter or ``OnPointerExit is never being called.
[RequireComponent(typeof(Button))]
public class InteractiveItem : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
public Image progressImage;
public bool isEntered = false;
RectTransform rt;
Button _button;
float timeElapsed;
Image cursor;
float GazeActivationTime = 5;
// Use this for initialization
void Awake()
{
_button = GetComponent<Button>();
rt = GetComponent<RectTransform>();
}
void Update()
{
if (isEntered)
{
timeElapsed += Time.deltaTime;
progressImage.fillAmount = Mathf.Clamp(timeElapsed / GazeActivationTime, 0, 1);
if (timeElapsed >= GazeActivationTime)
{
timeElapsed = 0;
_button.onClick.Invoke();
progressImage.fillAmount = 0;
isEntered = false;
}
}
else
{
timeElapsed = 0;
}
}
#region IPointerEnterHandler implementation
public void OnPointerEnter(PointerEventData eventData)
{
CodelabUtils._ShowAndroidToastMessage("entered");
isEntered = true;
}
#endregion
#region IPointerExitHandler implementation
public void OnPointerExit(PointerEventData eventData)
{
CodelabUtils._ShowAndroidToastMessage("exit");
isEntered = false;
progressImage.fillAmount = 0;
}
#endregion
}
am I missing something ? or is there any other way of achieving this?
I guess your Raycast supposed to trigger the OnPointerEnter on all results.
You will need to use ExecuteEvents.Execute like e.g.
ExecuteEvents.Execute(result.gameObject, eventDataCurrentPosition, ExecuteEvents.pointerEnterHandler);
And will also at some point have to invoke pointerExitHandler.
I would therefore store a HashSet like
using System.Linq;
private HashSet<GameObject> previousEnters = new HashSet<GameObject>();
...
foreach (var result in results.Select(r => r.gameObject))
{
Debug.Log(result.name);
ExecuteEvents.Execute(result, eventDataCurrentPosition, ExecuteEvents.pointerEnterHandler);
// Store the item so you can later invoke exit on them
if(!previousEnters.Contains(result)) previousEnters.Add(result);
}
// This uses Linq in order to get only those entries from previousEnters that are not in the results
var exits = previousEnters.Except(results);
foreach(var item in exits)
{
if(item) ExecuteEvents.Execute(item, eventDataCurrentPosition, ExecuteEvents.pointerExitHandler);
}
You might actually want to implement your own custom PointerInputModule.
Alternative/Example
As a starter you could also use my answer to Using Raycast instead of Gaze Pointer from a while ago where I created a script based on Steam's VR Laserpointer which allows to interact with 3D objects and UI elements.
Note: Typed on smartphone but I hope the idea gets clear

Unity IsPointerOverGameObject Issue

I know it's a common problem but none worked. Maybe the button setup is wrong. I have a Panel with no image, and inside a settings button on the top left that once clicked opens another scene. I tried using those 3 methods but none works. It always detects it as a Game object.
The bool isGameStarted is checking if the player should move or no.
Please guide me through
Have tried
if (Swipe.Instance.Tap && !isGameStarted)
{
if (EventSystem.current.IsPointerOverGameObject() )
{
isGameStarted = false;
}
else
{
isGameStarted = true;
motor.StartRunning();
gameCanvas.SetTrigger("Show");
}
}
Also tried using trigger but it goes through the UI.
This is the original code.
if (Swipe.Instance.Tap && !isGameStarted)
{
isGameStarted = true;
motor.StartRunning();
gameCanvas.SetTrigger("Show");
}
Once you tap on the screen the player starts moving. I need it NOT to move or start the game if clicked on the settings button.
I had the same problem until I figured out that IsPointerOverGameObject seems to return true for any GameObject (possibly with a collider) and not only UI objects.
So I wrote a custom static class to check for UI objects only. You will need to set the layer of each panel, image, button, etc. to UI for it to work.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public static class MouseOverUILayerObject
{
public static bool IsPointerOverUIObject()
{
PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
eventDataCurrentPosition.position = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
for (int i = 0; i < results.Count; i++)
{
if (results[i].gameObject.layer == 5) //5 = UI layer
{
return true;
}
}
return false;
}
}
Use it like this:
private void OnMouseDown()
{
if (!MouseOverUILayerObject.IsPointerOverUIObject())
HandleClick();
}
The overload of IsPointerOverGameObject takes a parameter
int pointerId Pointer (touch / mouse) ID.
and
If you use IsPointerOverGameObject() without a parameter, it points to the "left mouse button" (pointerId = -1);
therefore when you use IsPointerOverGameObject for touch, you should consider passing a pointerId to it.
So from the example
if (Swipe.Instance.Tap && !isGameStarted)
{
if (EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId))
{
isGameStarted = false;
return;
}
isGameStarted = true;
motor.StartRunning();
gameCanvas.SetTrigger("Show");
}
otherwise if you have multiple possible touches you could also do the check for all of them like
if (Swipe.Instance.Tap && !isGameStarted)
{
foreach(var touch in Input.touches)
{
if (EventSystem.current.IsPointerOverGameObject(touch.fingerId))
{
isGameStarted = false;
return;
}
}
isGameStarted = true;
motor.StartRunning();
gameCanvas.SetTrigger("Show");
}
also look at this thread

Unity 3d Animation

I m a newbie to Unity3d and was just playing with unity animation, i was trying to implement 2 Main UI Button such as when either of them is pressed it should check the condition whether a animation is already played before by 2nd button available if yes then remove those gameobject from scene using reverse animation else play the default animation attach to the particular button.
Problem is out of 6 case only 4 are getting operational 2 of them are not executing (marked in the code as Case Not Operational)
Animation anim;
private bool isOpen = false;
private bool open = false;
// Start is called before the first frame update
void Start()
{
anim = GetComponent<Animation>();
//isOpen = false;
//open = false;
}
// Update is called once per frame
void Update()
{
}
void OnGUI()
{
isOpen = GUI.Toggle(new Rect(50, 50, 200, 100), isOpen, "IsOpen");
open = GUI.Toggle(new Rect(65, 65, 200, 100), open, "Open");
}
public void ButtonControl()
{
string name = EventSystem.current.currentSelectedGameObject.name;
if (name == "Main Button 1")
{
if (isOpen == false && open == false)
{ Debug.Log("False False 1");
Anim1();
isOpen = true;
}
else if (isOpen == false && open == true) // case not operational
{ Debug.Log("False True 2");
ReverseAnim_2();
open = false;
Anim1();
isOpen = true;
}
else if(isOpen == true)
{
Debug.Log("True 3");
ReverseAnim_1();
isOpen = false;
}
}
else if (name == "Main Button 2")
{
if (open == false && isOpen == false)
{ Debug.Log("False False 4");
Anim2();
open = true;
}
else if(open == false && isOpen == true) // case not operational
{ Debug.Log("False True 5");
ReverseAnim_1();
isOpen = false;
Anim2();
open = true;
}
else if(open == true)
{
Debug.Log("True 6");
ReverseAnim_2();
open = false;
}
}
}
void ReverseAnim_1()
{
anim["1"].speed = -1;
anim["1"].time = anim["1"].length;
anim.Play();
}
void Anim1()
{
anim["1"].speed = 1;
anim.Play();
}
void Anim2()
{
anim["2"].speed = 1;
anim.Play();
}
void ReverseAnim_2()
{
anim["2"].speed = -1;
anim["2"].time = anim["2"].length;
anim.Play();
}
The problem is that you have two Main_B2 scripts and they are each tracking their own values for the isOpen and open fields.
One solution for this could be to make the fields static so that they are in common across all instances of Main_B2.
Animation anim;
private static bool isOpen = false;
private static bool open = false;
If you do this, your code should work as intended - each instance of Main_B2 would then be referring to the same fields whenever they reference isOpen or open.
With that said, static fields can get kind of sloppy if you ever want to try and re-use code so a better option might be to have only one instance of Main_B2 somewhere else such as the Canvas, instead of one on each button.
Then you could have in it public GameObject fields that you can drag the button objects into, and private Animation fields for the animations.:
public GameObject button1
public GameObject button2
private Animation anim1
private Animation anim2
Then in Start:
anim1 = button1.GetComponent<Animation>();
anim2 = button2.GetComponent<Animation>();
And then wherever you referred to anim you would refer to anim1 or anim2 instead:
void ReverseAnim_1()
{
anim1.["1"].speed = -1;
anim1.["1"].time = anim1["1"].length;
anim1.Play();
}
void Anim1()
{
anim1["1"].speed = 1;
anim1.Play();
}
void Anim2()
{
anim2["2"].speed = 1;
anim2.Play();
}
void ReverseAnim_2()
{
anim2["2"].speed = -1;
anim2["2"].time = anim2["2"].length;
anim2.Play();
}
Your scripts 'anim' variable is an Animation. Normally to adjust animation parameters you set them on an Animator, not Animation. But perhaps your solution can work.
For clarity. An animator organizes how animations play out, when, for how long and how they transition.
I would personally advise you to use an animator with behaviour patterns and transitionings between animations. Instead of the current approach.
Either that or use one of the many Tweening asset packs from the asset store. They are specifically made to create UI effects like what you describe.
At least consider these solutions if you keep being stuck :-)

Categories

Resources