How to know when field or property initialization is done? - c#

For example, there's two classes here.
Class One:
using UnityEngine;
using System.Collections.Generic;
public class One:MonoBehavior
{
private List<string> _assetBundleList;
private void Start()
{
InitializeList();
}
private void InitializeList()
{
//Add assetBundleList field some value.
}
public IEnumerator<string> GetEnumerator()
{
return _assetBundleList.GetEnumerator();
}
}
Class two:
public class Two:MonoBehavior
{
public GameObject gameObjectWithScriptOne;
private One _scriptOne;
private void Start()
{
scriptOne = gameObjectWithScriptOne.GetComponent<One>();
DoSomething();
}
private void DoSomething()
{
foreach(var assetBundle in scriptOne)
{
//Load asset
}
}
}
Script one is just like a manager things, I use this for storing asset bundle data, perhaps the list value will change. Script two must wait for initializing done. Is there any way to wait for it except adjusting script order?

It can be handled with easily creating a context that initialize things sequentially. In short you need to block the main thread.
Let's say you have a root or persistent scene that do must things like loading assets etc.
//The only object in the Root Scene.
public class RootContext : MonoBehaviour
{
private const string _playerAddress = "Player";
public void Awake()
{
//Load assets
//wait for assets loading
//instantiate things.
//new instantiated things can also initalize with this way.
var handle = Addressables.LoadAssetAsync<GameObject>(_playerAddress);
var asset = handle.WaitForCompletion();
var go = Instantiate(bla bla bla);
}
}
I think the what you want is a framework that organize things. You can look at Strange. It is MVCS + IoC framework. Addionality what you said about "when field or property initialization is done" with [PostConstruct] attribute with Strange.
[Inject(ContextKeys.CONTEXT)]
public IContext Context { get; set; }
/*Initialization of injections are guaranteed at here. Their initialization
is also quaranteed like this class.
*/
[PostConstruct]
public void Initalize()
{
}

I'd go with refactoring, but if this is not the case C# events might be the solution.
Class One
public class One:MonoBehavior
{
private List<string> _assetBundleList;
public event Func Initialized;
private void Start()
{
InitializeList();
}
private void InitializeList()
{
//Add assetBundleList field some value.
Initialized?.Invoke();
}
public IEnumerator<string> GetEnumerator()
{
return _assetBundleList.GetEnumerator();
}
}
Class Two:
public class Two:MonoBehavior
{
public GameObject gameObjectWithScriptOne;
private One _scriptOne;
private void Start()
{
scriptOne = gameObjectWithScriptOne.GetComponent<One>();
scriptOne.Initialized += DoSomething;
}
private void DoSomething()
{
foreach(var assetBundle in scriptOne)
{
//Load asset
}
}
}
Also, you should unsubscribe from events when disabling an object, but you'll figure it out by yourself.

Related

(UNITY) My JSON file is not loading on start

I am making a basic shop system includes car name, price and buyable. I am saving data to a text file. When I replay, I can't get the last variables I've changed. But, if I refresh at Editor by CTRL+R and start the game, variables are loading correct. What am I doing wrong? I am new at storage and JSON issues. Thanks for answers...
Here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
public class GameHandler : MonoBehaviour
{
[Serializable]
public class Car
{
public string name;
public int price;
public bool unlocked;
}
[Serializable]
public class CarList{
public Car[] cars;
}
private Car cars = new Car();
public CarList myCars = new CarList();
//-------
public int chosenCar;
//-------
public TextAsset CARS;
//-------
private void Start() {
GetFromJSON();
}
private void Update() {
if(Input.GetKeyDown(KeyCode.L))
GetFromJSON();
if(Input.GetKeyDown(KeyCode.S))
SaveToJSON();
}
public void ChangeCarIndex(int a){
chosenCar = a;
}
//This is a button func.
public void ChangePrice(int a){
myCars.cars[chosenCar].price = a;
SaveToJSON();
}
public void GetCarName(){
Debug.Log(myCars.cars[chosenCar].name);
}
public void GetCarPrice(){
Debug.Log(myCars.cars[chosenCar].price);
}
public void SaveToJSON(){
string jsOutput = JsonUtility.ToJson(myCars);
File.WriteAllText(Application.dataPath + "/CARS.txt", jsOutput);
Debug.Log("SaveToJSON() called");
}
public void GetFromJSON(){
myCars = JsonUtility.FromJson<CarList>(CARS.text);
Debug.Log("GetFromJSON() called");
}
}
When running this in the editor try to add
public void SaveToJSON()
{
string jsOutput = JsonUtility.ToJson(myCars);
File.WriteAllText(Application.dataPath + "/CARS.txt", jsOutput);
Debug.Log("SaveToJSON() called");
#if UNITY_EDITOR
UnityEditor.AssetDatabase.Refresh();
#endif
}
Though actually this isn't really necessary! Afaik you could also simply set the text of the textasset:
public void SaveToJSON()
{
string jsOutput = JsonUtility.ToJson(myCars);
CARS.text = jsOutput;
Debug.Log("SaveToJSON() called");
}
BUT NOTE:
in general note that this makes only sense in the editor itself.
If you target to do this in an actually later built application you would rather go via a file in Application.persistentDataPath. Problem with that though: The data can easily be seen and edited by the user. So if this is anything sensitive you will need to go for a central database server with user login.

How to wait until a scene is fully loaded?

I need to wait until a scene is fully loaded in order to move a gameObject to it from DontDestroyOnLoad scene. If I do it too soon (just after calling SceneManager.LoadScene()) then the gameObject disappears. Based on this post I implemented a scene loading class to solve this issue:
public static class CustomSceneManager
{
public delegate void SceneChange(string sceneName);
public static event SceneChange LoadScene;
public static event SceneChange UnloadScene;
private static IEnumerator LoadLevel (string sceneName){
var asyncLoadLevel = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
while (!asyncLoadLevel.isDone){
Debug.Log("Loading the Scene");
yield return null;
}
}
public static void OnLoadScene(string newSceneName)
{
OnUnloadScene(newSceneName);
LoadLevel(newSceneName);
LoadScene?.Invoke(newSceneName);
}
private static void OnUnloadScene(string newSceneName)
{
UnloadScene?.Invoke(newSceneName);
}
}
I'm calling two events from it (LoadScene and UnloadScene). However the LoadLevel(newSceneName) doesn't work - it simply doesn't load a scene. What am I doing wrong here?
EDIT:
Now I'm passing the MonoBehavior reference of the script calling OnLoadScene methid like this:
public static void OnLoadScene(MonoBehaviour loader, string newSceneName)
{
UnloadScene?.Invoke(newSceneName);
loader.StartCoroutine(LoadLevel(newSceneName));
Debug.Log(SceneManager.GetActiveScene().name); // this line returns previous scene
LoadScene?.Invoke(newSceneName);
}
Now the scene loads, but when I check what scene is currently loaded, it returns the previous scene name.
EDIT 2:
To be more precise I replaced Debug.Log(SceneManager.GetActiveScene().name); with Debug.Log(SceneManager.GetSceneByName(newSceneName).isLoaded); and it returns False.
You have to run coroutienes using StartCoroutine.
You either would need to pass in a reference of a MonoBehaviour that will execute the coroutine or simply make your class a Singleton that is never destroyed
Than actually you will invoke your event too early when it is not yet loaded but you just started to load it so rather do e.g.
public class CustomSceneManager : MonoBehaviour
{
public delegate void SceneChange(string sceneName);
public static event SceneChange LoadScene;
public static event SceneChange UnloadScene;
private CustomNetworkManager singleton;
private void Awake ()
{
if(singleton && singleton != this)
{
Destroy(gameObject);
}
singleton = this;
DontDestroyOnLoad (gameObject);
}
private static IEnumerator LoadLevel (string sceneName){
var asyncLoadLevel = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
while (!asyncLoadLevel.isDone){
Debug.Log("Loading the Scene");
yield return null;
}
LoadScene?.Invoke(newSceneName);
}
public static void OnLoadScene(string newSceneName)
{
if(! singleton)
{
singleton = new GameObject("CustomNetworkManager").AddComponent<CustomNetworkManager>();
}
OnUnloadScene(newSceneName);
singleton.StartCoroutine(LoadLevel(newSceneName));
}
Another way you can do this is with an async method.
The catch is that in order to await an AsyncOperation you need a custom awaiter class. There are a few libraries that make it possible, and my favorite one is UniTask.
using Cysharp.Threading.Tasks;
using UnityEngine.SceneManagement;
public static class CustomSceneManager
{
public delegate void SceneChange(string sceneName);
public static event SceneChange LoadScene;
public static event SceneChange UnloadScene;
private static async UniTask LoadLevelAsync(string sceneName)
{
await SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
}
public static async UniTask OnLoadSceneAsync(string newSceneName)
{
OnUnloadScene(newSceneName);
await LoadLevelAsync(newSceneName);
LoadScene?.Invoke(newSceneName);
}
private static void OnUnloadScene(string newSceneName)
{
UnloadScene?.Invoke(newSceneName);
}
}

Prevent static property being accessed before async function initializes it

I have a function that asynchronously loads a xml file, parses it, and adds certain values to a list. I'm using async and await for this. The issue I've run into is that after calling await the program moves on to executing code that accesses that list before the async function has finished adding all items.
My static class with async function:
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Xml.Linq;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.AddressableAssets;
namespace Drok.Localization
{
public static class Localization
{
/// <summary>
/// The currently available languages.
/// </summary>
public static List<string> Available { get; private set; } = new List<string>();
/// <summary>
/// The currently selected language.
/// </summary>
public static string Current { get; private set; } = null;
public static async Task Initialize()
{
await LoadMetaData();
}
private static async Task LoadMetaData()
{
AsyncOperationHandle<TextAsset> handle = Addressables.LoadAssetAsync<TextAsset>("Localization/meta.xml");
TextAsset metaDataFile = await handle.Task;
XDocument metaXMLData = XDocument.Parse(metaDataFile.text);
IEnumerable<XElement> elements = metaXMLData.Element("LangMeta").Elements();
foreach (XElement e in elements)
{
string lang = e.Attribute("lang").Value;
int id = Int32.Parse(e.Attribute("id").Value);
Debug.LogFormat("Language {0} is availible with id {1}.", lang, id);
Available.Add(lang);
}
}
public static void LoadLanguage(string lang)
{
Current = lang;
throw new NotImplementedException();
}
public static string GetString(string key)
{
return key;
}
}
}
The class that initializes it and accesses the list:
using Drok.Localization;
using UnityEngine;
namespace Spellbound.Menu
{
public class LanguageMenu : MonoBehaviour
{
private async void Awake()
{
await Localization.Initialize();
}
private void Start()
{
Debug.Log(Localization.Available.Count);
}
private void Update()
{
}
}
}
I have no idea how to prevent access to that list until after all items have been added. The code I posted just collects info on what languages are available so that only the one language being used can be loaded later.
A Task<T> represents some value (of type T) that will be determined in the future. If you make your property this type, then it will force all callers to await for it to be loaded:
public static class Localization
{
public static Task<List<string>> Available { get; private set; }
static Localization() => Available = LoadMetaDataAsync();
private static async Task<List<string>> LoadMetaDataAsync()
{
var results = new List<string>();
...
results.Add(lang);
return results;
}
}
Usage:
private async Task StartAsync()
{
var languages = await Localization.Available;
Debug.Log(languages.Available.Count);
}
One possibility might be to add some logic to wait for the metadata to be loaded when returning the list from the get accessor.
One way to do this is to have a bool field that is set to true when the list is ready, and then we either return a private backing List<string> or null, depending on the value of our bool field:
public static class Localization
{
private static bool metadataLoaded = false;
private static List<string> available = new List<string>();
// The 'Available' property returns null until the private list is ready
public static List<string> Available => metadataLoaded ? available : null;
private static async Task LoadMetaData()
{
// Add items to private 'available' list here
// When the list is ready, set our field to 'true'
metadataLoaded = true;
}
}
The Awake method is async void, so there is no way for the caller to guarantee that it finishes before moving on to something else.
However, you could preserve the task and await it in the Start method to ensure that it is completed. Awaiting it twice does not harm anything.
public class LanguageMenu : MonoBehaviour
{
private Task _task;
private async void Awake()
{
_task = Localization.Initialize();
await _task;
}
private async void Start()
{
await _task;
Debug.Log(Localization.Available.Count);
}
private void Update()
{
}
}
Expanding on Rufus' comment:
Declare a bool property that's initialized to false. And in your list's getter, return the list only if the said bool property is true, and return maybe null (this depends on your requirements) if false.
public static bool IsAvailable { get; set; } = false;
private static List<string> _available;
public static List<string> Available
{
get
{
if (IsAvailable)
return _available;
else
return null;
}
set { _available = value; }
}
Finally, in your async function, when the work is done set the above property to true.
Latest when there is an Update method involved that should also wait with its execution using async and await might not be enough anyway.
Usually there is always one big alternative to using async for the Unity messages: an event system like e.g.
public static class Localization
{
public static event Action OnLocalizationReady;
public static async void Initialize()
{
await LoadMetaData();
OnLocalizationReady?.Invoke();
}
...
}
And wait for that event in any class using it like e.g.
public class LanguageMenu : MonoBehaviour
{
private bool locaIsReady;
private void Awake()
{
Localization.OnLocalizationReady -= OnLocalizationReady;
Localization.OnLocalizationReady += OnLocalizationReady;
Localization.Initialize();
}
private void OnDestroy ()
{
Localization.OnLocalizationReady -= OnLocalizationReady;
}
// This now replaces whatever you wanted to do in Start originally
private void OnLocalizationReady ()
{
locaIsReady = true;
Debug.Log(Localization.Available.Count);
}
private void Update()
{
// Block execution until locaIsReady
if(!locaIsReady) return;
...
}
}
Or for minimal better performance you could also set enabled = false in Awake and set it to true in OnLocalizationReady then you could get rid of the locaIsReady flag.
No async and await needed.
If you would move the Localization.Initialize(); instead to Start you would give other classes the chance to also add some callbacks before to Localization.OnLocalizationReady in Awake ;)
And you can extend this in multiple ways! You could e.g. together with firering the event directly also pass in the reference to Availables so listeners can directly use it like e.g.
public static class Localization
{
public static event Action<List<string>> OnLocalizationReady;
...
}
and then in LanguageMenu change the signiture of OnLocalizationReady to
public class LanguageMenu : MonoBehaviour
{
...
// This now replaces whatever you wanted to do in Start originally
private void OnLocalizationReady(List<string> available)
{
locaIsReady = true;
Debug.Log(available.Count);
}
}
If anyway LanguageMenu will be the only listener then you could even pass the callback directly as parameter to Initialize like
public static class Localization
{
public static async void Initialize(Action<List<string>> onSuccess)
{
await LoadMetaData();
onSuccess?.Invoke();
}
...
}
and then use it like
private void Awake()
{
Localization.Initialize(OnLocalizationReady);
}
private void OnLocalizationReady(List<string>> available)
{
locaIsReady = true;
Debug.Log(available.Count);
}
or as lambda expression
private void Awake()
{
Localization.Initialize(available =>
{
locaIsReady = true;
Debug.Log(available .Count);
}
}
Update
As to your question about later Initialization: Yes there is a simple fix as well
public static class Localization
{
public static event Action OnLocalizationReady;
public static bool isInitialized;
public static async void Initialize()
{
await LoadMetaData();
isInitialized = true;
OnLocalizationReady?.Invoke();
}
...
}
Then in other classes you can do it conditional either use callbacks or Initialize right away:
private void Awake()
{
if(Localization.isInitialized)
{
OnLocaInitialized();
}
else
{
Localization.OnInitialized -= OnLocaInitialized;
Localization.OnInitialized += OnLocaInitialized;
}
}
private void OnDestroy ()
{
Localization.OnInitialized -= OnLocaInitialized;
}
private void OnLocaInitialized()
{
var available = Localization.Available;
...
}
private void Update()
{
if(!Localization.isInitialized) return;
...
}

How to implement event sender - event receiver game objects in unity?

I have class "A", which will send event "a". Classes that are subscribing to event "a" will react to this event. Other classes can subscribe to event "a" without changing anything in class "A";
Now, what is the most reasonable way to do this in unity? Is there some messaging system that can already do that?
If not, should I make something like EventManager class that will store subscriber classes in array and call their methods?
There are probably many ways to do this.
Public static List
public class A : MonoBehaviour
{
public static List<A> AvailableAs = new List<A>();
private void Awake()
{
if(!AvailableAs.Contains(this)) AvailableAs.Add(this);
}
private void OnDestroy()
{
if(AvailableAs.Contains(this)) AvailableAs.Remove(this);
}
public void SomePublicMethod()
{
// magic
}
}
and use it e.g. like
public class B : MonoBehaviour
{
// run the method on all currently registered A instances
public void DoIt()
{
foreach(var a in A.AvailableAs)
{
a.SomePublicMethod();
}
}
}
Global EventHandler
Or if you rather want to go for encapsulation have as you mentioned a global event handler for all A's like
namespace ANamespace
{
public static class AEventHandler
{
internal static event Action OnInvokeA;
public static void InvokeAEvent()
{
if(OnInvokeA != null) OnInvokeA.Invoke();
}
}
}
and in A have
namespace ANamespace
{
public class A : MonoBehaviour {
private void Awake()
{
// it is save to remove a callback first even
// if it was not added yet. This makes sure it is
// added only once always
AEventHandler.OnIvokeA -= SomePrivateMethod;
AEventHandler.OnIvokeA += SomePrivateMethod;
}
private void OnDestroy()
{
AEventHandler.OnIvokeA -= SomePrivateMethod;
}
private void SomePrivateMethod()
{
// magic
}
}
}
Now in B you would rather simply do
// invoke the event and whoever is added as listener will do
// whatever he registered
// in this case all A instances execute their method
AEventHandler.InvokeAEvent();
Unity Event
If you have however only one class A which throws an event and you want others to react to it simply use a UnityEvent like
public class A : MonoBehaviour
{
public UnityEvent OnSomethingHappened = new UnityEvent();
private void SomeMethodIWillRun()
{
//...
OnSomethingHappened.Invoke();
}
}
Now you cann easily add callbacks to that event in the Unity Inspector by dragging in GameObjects/Components and select the method to call. (Exactly the same thing is used for the onClick event of the Button component btw. ;) )
And you could still add callbacks via script on runtime like
public class B : MonoBehaviour
{
public A referenceToA;
private void Start()
{
referenceToA.OnSomethingHappened.AddCallback(OnASomethingHappened);
}
private void OnDestroy()
{
referenceToA.OnSomethingHappened.RemoveCallback(OnASomethingHappened)
}
private void OnASomethingHappened()
{
//
}
}
Note: Typed on smartphone so no warranty but I hope the idea gets clear.

Only allow certain classes to edit certain properties

I have a class with a PictureBox created as followed:
public class Tile
{
public PictureBox tilePB = new PictureBox(); //properties don't matter in this case
}
I also have a class GameManager. This is like a referee.
I want to make it so the BackColor of Tile.tilePB can only be edited by Gamemanager and nothing else, and no other class.
I currently have a public PictureBox for Gamemanager (to edit) and a public get function for other classes, but I want to actually make this a valid system instead of what I have right now.
Is this even possible? Please include explenation for the required code.
EDIT: I ran into an issue that I hadn't thought off: class Gamemanager is a static class. I do everything in that class via public static functions. Is this still possible? Since this doesn't work.
You can't do this at compile time, but it can be done at runtime:
public class PictureBox
{
private Color _backColor;
public void SetBackColor(Color color)
{
//getting class type that called this method
var stackTrace = new StackTrace();
var stackFrames = stackTrace.GetFrames();
var callingFrame = stackFrames[1];
var method = callingFrame.GetMethod();
//checking if the class type is GameManager
if (!method.DeclaringType.IsAssignableFrom(typeof(GameManager)))
{
throw new FieldAccessException("Only GameManager can set the background color of a PictureBox!");
}
_backColor = color;
}
public Color BackColor => _backColor;
}
public class Tile
{
public PictureBox tilePB { get; set; }
}
//example GameManager class
public class GameManager
{
public void SetBackground()
{
var someTile = new Tile()
{
tilePB = new PictureBox()
};
var someColor = new Color();
someTile.tilePB.SetBackColor(someColor);
}
}
//example class that may want to set picturebox background color
public class MaliciousClass
{
public void SetBackground()
{
var someTile = new Tile()
{
tilePB = new PictureBox()
};
var someColor = new Color();
someTile.tilePB.SetBackColor(someColor);
}
}
Then somewhere:
var gm = new GameManager();
var mc = new MaliciousClass();
gm.SetBackground(); //this is fine
mc.SetBackground(); //this will throw an exception
If you don't want to throw an exception or you want to do something different when "not authorized" class is trying to access the SetBackColor method then just replace throw new FieldAccessException() with return or whatever you want.
Bare in mind the approach presented here is inefficent and it just presents that in can be done at runtime and nothing more than that.
Not sure if this is exactly what you are looking for, but I made this quick test and it seems to be able to differentiate the calling class:
class Program
{
static void Main(string[] args)
{
Type1 something1 = new Type1();
Type2 something2 = new Type2();
something1.runTest();
something2.runTest();
Console.ReadKey();
}
public class Type1
{
public void runTest()
{
Testing.edit(this);
}
}
public class Type2
{
public void runTest()
{
Testing.edit(this);
}
}
public static class Testing
{
public static void edit(object obj)
{
// This is where you test the calling class to make sure
// it is allowed to edit.
Console.WriteLine(obj.GetType().ToString());
}
}
}
The only way I can think of where you enforce this at compile time, end up being a bit complicated. I don't think you'll want to do this.
You can create an interface with properties/methods for everything that only the GameManager is allowed to do. You can implement this interface in a private inner class below Tile, and make sure the only way this object is created is by passing in a GameManager that receives it. Now, the only way the access can 'leak' is if the GameManager 'gives away' the object.
public class GameManager {
public void AddTile(Tile t, Tile.IManagerHook m) {
m.SomeProperty = "set from manager";
}
}
public class Tile
{
public object SomeProperty { get; private set; }
public Tile(GameManager manager) {
manager.AddTile(this, new ManagerHook(this));
}
public interface IManagerHook {
object SomeProperty {get; set;}
}
private class ManagerHook : IManagerHook {
private Tile _tile;
public ManagerHook(Tile t) {
_tile = t;
}
public object SomeProperty {
get{ return _tile.SomeProperty;}
set { _tile.SomeProperty = value; }
}
}
}
(seems) Simply not possible
After asking several programmers, the way I have coded everything and what I want seems to be simply impossible without immensely complicated code - to the point you are better off refacturing everything. Since class Gamemanager is a static class, there will be no instances of it so you can not check if the 'object' that called it is of class Gamemanager. this also doesn't work since Gamemanager is, agian, static.

Categories