How to create loading in Unity until load information? - c#

hi,i found a code for loading scene in unity3D, I want to take when data via PHP (through WWW), Loading displayed, please help me.
Also, how to recognize and change the keyboard language for Android Device?
void Update(){
if (loadScene == false)
{
loadScene = true;
loadingText.text = "Is loading your information...";
StartCoroutine(LoadNewScene());
}
if (loadScene == true)
{
loadingText.color = new Color(loadingText.color.r, loadingText.color.g, loadingText.color.b, Mathf.PingPong(Time.time, 1));
}
}
}
void Start(){
StartCoroutine(rankGet());
}
IEnumerator LoadNewScene() {
yield return new WaitForSeconds(3);
AsyncOperation async = Application.LoadLevelAsync(scene);
while (!async.isDone) {
yield return null;
}
}
IEnumerator rankGet()
{
txtUsername.text = PlayerPrefs.GetString("username");
WWW connection = new WWW("http://127.0.0.1/scoregame/userscorerank.php?uss=" + txtUsername.text);
yield return (connection);
if (connection.text == "401")
{
//Debug.Log("username do not exits!");
}
else
{
ranktxt.text = connection.text;
}
}

The easiest way to use Async operation for any other purpose then loading new scene, is to
write your own method that produces an IEnumerable instance.
As described here by msknapp user.
public System.Collections.IEnumerable coroutineFunction()
{
while (!doneYourPersistentProcess())
// ... wait for it ...
yield return "";
// now it is done.
doPostProcessing();
}
public void doPostProcessing()
{
// Loading progressbar here
}
public void Update()
{
if (userStartsPersistentProcess())
StartCoroutine(coroutineFunction());
}
public bool userStartsPersistentProcess()
{
// your code here.
}
public bool doneYourPersistentProcess()
{
// your code here.
}
And the last step is to prepare graphic for progress bar.
The easiest way is to change Fill Amount value in the Image object, with properties as shown below:

Related

Return record key from InsertAsync in Xamarin Android

I'm trying to return the key of the match created / updated in VS2022 using Xamarin on Android.
The below code works perfectly, but returns the amount of rows updated / inserted.
public class GameDatabase
{
readonly SQLiteAsyncConnection database;
public void SaveGame()
{
Task<DBMatch> matchResult = SaveGameAsync(dbMatch);
_match.matchKey = matchResult.Result.matchKey;
}
public Task<int> SaveMatchAsync(DBMatch dbMatch)
{
if (dbMatch.matchKey != 0)
{
// Update an existing Game.
return database.UpdateAsync(dbMatch);
}
else
{
// Save a new Game.
return database.InsertAsync(dbMatch);
}
}
}
How do I update it to return the key? I seem to have gone round the houses about 20 times changing it to async and await as I believe the object is updated with the key, but then it just hangs on await.
e.g.
public async Task<DBMatch> SaveGameAsync(DBMatch dbMatch)
{
if (dbMatch.matchKey != 0)
{
// Update an existing Game.
await database.UpdateAsync(dbMatch);
}
else
{
// Save a new Game.
await database.InsertAsync(dbMatch);
}
return dbMatch;
}
It seems like the answer was to not use async or Tasks at all, and instead just use Wait().
DBMatch matchResult = SaveGameAsync(dbMatch);
_match.matchKey = matchResult.matchKey;
public DBMatch SaveGameAsync(DBMatch dbMatch)
{
if (dbMatch.matchKey != 0)
{
// Update an existing Game.
database.UpdateAsync(dbMatch).Wait();
}
else
{
// Save a new Game.
database.InsertAsync(dbMatch).Wait();
}
return dbMatch;
}

Unity. Can't activate scene instance when it cached after download

I have Main menu provider:
class MainMenuProvider
{
private static SceneInstance _cachedScene;
private static bool _isLoaded;
public async Task<SceneInstance> Load()
{
if (!_isLoaded)
{
var mainMenuLoadHandle =
Addressables.LoadSceneAsync("Assets/Bundles/UI/Scenes/MainMenu.unity", LoadSceneMode.Single, false);
await mainMenuLoadHandle.Task;
_cachedScene = mainMenuLoadHandle.Result;
_isLoaded = true;
}
return _cachedScene;
}
}
When I firstly invoke Load and then scene.ActivateSync it works perfect, but when I invoke Load and ActivateAsync the second time and my scene is cached, nothing happens.
_cachedScene.m_Operation.isDone == true
You set _isLoaded static so this whole thing is definitely only executed exactly once the entire application session lifecycle.
However, if you then ever happen to use LoadSceneMode.Single for any other scene at any time, then this _cachedScene is unloaded and doesn't exist anymore.
=> _cachedScene will then contain an invalid scene which isn't loaded anymore and will never be loaded again.
You would need to decouple the loading of the addressable and the actual loading of the scene. The Addressables.LoadSceneAsync already contains directly also the call to SceneManagement.SceneManager.LoadSceneAsync. You want to split that and only use e.g. only Addressables.LoadAssetAsync for your caching.
And then only load and unload this scene via the mentioned SceneManagement.SceneManager.LoadSceneAsync somewhat like e.g.
class MainMenuProvider
{
private static AsyncOperationHandle<Scene>? _sceneHandle;
private static Scene? _loadedScene;
public async Task<Scene> Load()
{
if(TryGetLoadedScene(out var scene))
{
return scene;
}
if (_sceneHandle == null)
{
_sceneHandle = Addressables.LoadAssetAsync<Scene>("Assets/Bundles/UI/Scenes/MainMenu.unity");
}
await _sceneHandle.Task;
if(TryGetLoadedScene(out var scene))
{
return scene;
}
var path = _sceneHandle.Result.path;
await SceneManager.LoadSceneAsync(path, LoadSceneMode.Single, false);
_loadedScene = SceneManager.GetSceneByPath(path);
return _loadedScene;
}
private static bool TryGetLoadedScene(out Scene? loadedScene)
{
if(_loadedScene == null)
{
loadedScene = null;
return false;
}
loadedScene = SceneManager.GetSceneByPath(_loadedScene.path);
if(scene.isValid)
{
_loadedScene = loadedScene;
return true;
}
return false;
}
}
This would to certain extent reduce the advantage of the addressable of course since one of them is less memory usage by unloading the scene while it isn't used.

UnityEngine.UI.Text is not updating inside async method

i have a script MenuManager.cs amd i this script i get tow doc in the firebase firestone, and this works well but when i will set the result in a text, dont work
using Firebase.Firestore;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Json;
using UnityEngine;
public class MenuManager : MonoBehaviour
{
private Usuario usuario;
private Rank rank;
private CollectionReference usuarioCollection;
private CollectionReference rankCollection;
public GameObject rankNomeLabel;
public bool infoCarregadas;
public GameObject botaoInfo;
void Start()
{
this.rank = new Rank();
this.botaoInfo.GetComponent<UnityEngine.UI.Button>().onClick.AddListener(() => this.CarregaInfos());
//this.infoCarregadas = false;
}
void Update()
{
/*if (db != null && infoCarregadas == false)
{
this.CarregaInfos();
} else
{
if (infoCarregadas == false)
{
db = FirebaseAuthenticator.instance.fsDB;
}
}*/
}
private void CarregaInfos()
{
try
{
FirebaseFirestore db = FirebaseAuthenticator.instance.fsDB;
var usuarioJson = PlayerPrefs.GetString("usuario");
this.usuario = usuarioJson != "" ? JsonUtility.FromJson<Usuario>(usuarioJson) : null;
if (this.usuario != null && this.usuario.UID != null)
{
this.usuarioCollection = db.Collection("usuario");
DocumentReference docRef = this.usuarioCollection.Document(usuario.UID);
docRef.GetSnapshotAsync().ContinueWith(task =>
{
DocumentSnapshot snapshot = task.Result;
if (snapshot.Exists)
{
Dictionary<string, object> usuario = snapshot.ToDictionary();
foreach (KeyValuePair<string, object> pair in usuario)
{
if (pair.Key == "rank")
{
this.rankCollection = db.Collection("rank");
DocumentReference docRef = this.rankCollection.Document(pair.Value.ToString());
docRef.GetSnapshotAsync().ContinueWith(task =>
{
DocumentSnapshot snapshot = task.Result;
if (snapshot.Exists)
{
Dictionary<string, object> rank = snapshot.ToDictionary();
foreach (KeyValuePair<string, object> pair in rank)
{
if (pair.Key == "nome")
{
this.rankNomeLabel.GetComponent<UnityEngine.UI.Text>().text = pair.Value.ToString();
}
}
}
else
{
Debug.Log("Erro ao carregar o rank: " + snapshot.Id);
}
});
}
}
}
else
{
Debug.Log("Erro ao carregar o usuário: " + snapshot.Id);
}
});
}
else
{
Debug.Log("Erro ao pegar usuário do PlayerPrefs");
}
}
catch (Exception e)
{
Debug.LogError("Erro: " + e);
}
}
}
the text is linked to the variable (print bellow)
this.rankNomeLabel.GetComponent<UnityEngine.UI.Text>().text = pair.Value.ToString(); in this line, the reference is null
think the problem is in setting the value within the async method but I don't know how to fix it
does anyone know what's going on?
Most of the Unity API can only be used on the Unity main thread (the exception are pure structs since they are only data containers and don't immediately rely on or influence the actual scene).
ContinueWith is not guaranteed to be executed in the main thread.
Therefore Firebase provides the Extension method ContinueWithOnMainThread.
Replacing both or at least the inner ContinueWith by ContinueWithOnMainThreae should solve your issue or at least make it clearer where the actual issue is.
Instead of using GetComponent over and over again make your live easier and directly use the correct types
using UnityEngine.UI;
...
public Text rankNomeLabel;
public Button botaoInfo;
this also makes sure you can only drag in objects that actually have the according component attached and is less error prone on runtime since it makes already sure it isn't null - and if it is (e.g. if the object was destroyed) you can also see it immediately in the Inspector
I solved a similar issue by checking a boolean in an update method. It's not fancy but it works.
public class MyClass : MonoBehaviour
{
private bool updateUI = false;
private async Task function() {
updateUI = true;
}
private void Update() {
if (updateUI) {
// update your ui here
...
updateUI = false;
}
}
}

Json Serialization in Unity - NullReferenceException : Object reference not set to an instance of an object

I created several custom classes to read a json file in Unity.
Fruits.cs
[System.Serializable]
public class Fruits
{
public Apple[] apples;
}
Apple.cs
[System.Serializable]
public class Apple
{
public string appleName;
}
MyClass.cs
public class MyClass : MonoBehaviour{
private Fruits fruits;
public void classSetup(){
StartCoroutine(JsonReader());
String appleName = fruits.apples[0].appleName; -------> this is the line trigger the exception.
Debug.Log(appleName);
}
Ienumerator JsonReader(){
.........unrelated codes hidden...............
if (Url.Contains(":/") || Url.Contains("://") || Url.Contains(":///"))
{
UnityWebRequest www = UnityWebRequest.Get(Url);
yield return www.SendWebRequest();
JsonText = www.downloadHandler.text;
}
fruits = JsonUtility.FromJson<Fruits>(JsonText);
}
}
The JsonReader (IEnumerator I created) works fine as the Debug.Log print out the appleName I expect but the line "String appleName ....." trigger an exception (NullReferenceException : Object reference not set to an instance of an object.)
I read posts about how to fix null exception, but was not able to find help on how to do that for a custom serialization class. Simply doing fruits = new Fruits() does not work. I suspect that's because I didn't instantiate the fields. But what if I have a lot of fields in Fruits class? And what if I can't just do "fruits.apples = new Apple[5]" because the length depends on json input and may not be 5?
Any help will be appreciated.
I am not entirely sure about that one. But I think the problem is, that Unity reaches the string apple too early because you starting a coroutine and while you creating the json object Unity is going on and tries to get a value, that is non existent yet. So what you can try is a custom event.
//Create a delegate and and event to fire at a certain point of time
public delegate void FruitsLoadedEvent FruitsLoadedEvent();
public static event FruitsLoadedEvent OnFruitsLoaded;
public void classSetup(){
//Subscribe to the event
OnFruitsLoaded += CreateApple;
StartCoroutine(JsonReader());
}
Ienumerator JsonReader(){
.........unrelated codes hidden...............
if (Url.Contains(":/") || Url.Contains("://") || Url.Contains(":///"))
{
UnityWebRequest www = UnityWebRequest.Get(Url);
yield return www.SendWebRequest();
JsonText = www.downloadHandler.text;
}
fruits = JsonUtility.FromJson<Fruits>(JsonText);
//Invoke the event after checking, if anyone is subscribed
OnFruitsLoaded?.Invoke();
}
private void CreateApple() {
String appleName = fruits.apples[0].appleName;
}
I really hope that helps you!
Starting a Coroutine does not delay the method that starts it itself. Wouldn't make sense if it would because you would lose the entire reason why making it a Coroutine.
What you would want to do instead is either move your code that shall be executed after the result is received into the routine like
public class MyClass : MonoBehaviour
{
public void classSetup()
{
StartCoroutine(JsonReader());
}
IEnumerator JsonReader()
{
// .........unrelated codes hidden...............
if (Url.Contains(":/") || Url.Contains("://") || Url.Contains(":///"))
{
UnityWebRequest www = UnityWebRequest.Get(Url);
yield return www.SendWebRequest();
JsonText = www.downloadHandler.text;
}
var fruits = JsonUtility.FromJson<Fruits>(JsonText);
String appleName = fruits.apples[0].appleName; -------> this is the line trigger the exception.
Debug.Log(appleName);
}
}
Or use a callback
public class MyClass : MonoBehaviour
{
public void classSetup()
{
StartCoroutine(JsonReader(HandleResult));
// or the same as lambda expression
//StartCoroutine(JsonReader(fruits =>
//{
// String appleName = fruits.apples[0].appleName; -------> this is the line trigger the exception.
// Debug.Log(appleName);
//}));
}
IEnumerator JsonReader(Action<Fruits> onResult)
{
// .........unrelated codes hidden...............
if (Url.Contains(":/") || Url.Contains("://") || Url.Contains(":///"))
{
UnityWebRequest www = UnityWebRequest.Get(Url);
yield return www.SendWebRequest();
JsonText = www.downloadHandler.text;
}
var fruits = JsonUtility.FromJson<Fruits>(JsonText);
onResult?.Invoke(fruits);
}
private void HandleResult(Fruits fruits)
{
String appleName = fruits.apples[0].appleName; -------> this is the line trigger the exception.
Debug.Log(appleName);
}
}

Loading the method body from a string?

For very special circumstances, I'd like to be able to store C# code in a configuration entry and fill in an empty function with this code at runtime. For example, let's say on initial run I start out with a method such as this:
bool Evaluate(int number)
{
return false;
}
I have a configuration entry that looks like this:
<add key="EvaluateCode" value="if (number > 5) { return true; } else { return false; }"/>
After loading the EvaluateCode configuration entry I'd like to replace the function body of Evaluate so that it looks like this:
bool Evaluate(int number)
{
if (number > 5) { return true; } else { return false; }
}
After this 'replacement' is made, the Evaluate function should behave as the code dictates, just as it would as if the code had not been loaded dynamically.
How could I acheive this in C#?
Bonus: What would be the risks of implementing such a feature? How can I mitigate those risks?
Essentially you are asking for the ability to compile c# code at run time, which is possible, and is described here
This sounded like fun.. so I decided to try it.
No need to upvote.. just popping this here so I can reference it in future :)
Given the below class:
class DynamicMethodTest {
private MethodInfo _methodToCall;
private object _obj;
public void PerformInjection(string newBody) {
using (var codeProvider =
new Microsoft.CSharp.CSharpCodeProvider()) {
var res = codeProvider.CompileAssemblyFromSource(
new System.CodeDom.Compiler.CompilerParameters() {
GenerateInMemory = true
},
"public class StubClass { public bool Evaluate(int number) { " + newBody + " }}"
);
var type = res.CompiledAssembly.GetType("StubClass");
_obj = Activator.CreateInstance(type);
_methodToCall = _obj.GetType().GetMethod("Evaluate");
}
}
public bool Evaluate(int number) {
if (_methodToCall != null)
return (bool)_methodToCall.Invoke(_obj, new object[] { number });
return false;
}
}
We can do this:
public class Program {
public static void Main() {
var dynamicTest = new DynamicMethodTest();
Console.WriteLine(dynamicTest.Evaluate(15)); // False
dynamicTest.PerformInjection("if (number > 5) { return true; } else { return false; }");
Console.WriteLine(dynamicTest.Evaluate(15)); // True
Console.Read();
}
}
This results in:
False
True
As output. Basically, before the "Injection" (its not really injection.. its more of a fascade) the method returns false. After "Injection" it returns true (as expected).

Categories