Reputation: 71
I looked for answers, but still at loss here. I have some character generator, which creates then saves player character variables. They are saved into a fresh script "PlayerCharacterData", which is a component of object CharacterData. This object has DontDestroyOnLoad, so it persists to other scenes. After generating character, game loads character data into that script, and when I switch scenes, data is properly loaded from binary files (serialized earlier).
But now I have this core and I need my numerous UI objects to load data from "PlayerCharacterData" class, so UI can fill all fields, etc. Later whole game will be depending on variable values from "PlayerCharacterData".
This is part of my PlayerCharacterData script:
public class PlayerCharacterData : MonoBehaviour {
void Awake(){
DontDestroyOnLoad (this); // keeps the object when changing scenes
}
// below we have data stored in variables. It was loaded by another script.
public string characterName;
public int characterSpeed;
public List<Weapon>characterInvWeapon;
// etc, more variables x50-70
}
Now here's example of my public class UIPlayerCharacterData. I added a tag to object CharacterData in Unity Editor to make "Find" faster:
public class UIPlayerCharacterData : PlayerCharacterData {
public void NameToCharacterDataUI() {
// this would be required to make it work: PlayerCharacterData playerCharacterData = GameObject.FindGameObjectWithTag("CharacterData").GetComponent<PlayerCharacterData>();
Text_ch_sh_char_name_string.text = playerCharacterData.characterName;
}
// etc, more functions like that x50-70
void Awake () {
PlayerCharacterData playerCharacterData = GameObject.FindGameObjectWithTag("CharacterData").GetComponent<PlayerCharacterData>();
NameToCharacterDataUI();
// etc, calling more functions x50-70
}
}
Problem is, those classes are on different objects. First is component of CharacterData object which persists from 1st scene, second is component of major UI panel in 2nd scene. Second class has LOTS of UI fields to fill, just showed you one of those. Each takes data from first class (component on CharacterData object). So there's like 50-70 variables for UI to pull data from.
And that's just a start, because whole game will need to get and modify data in PlayerCharacterData script.
Now, I've found out the function NameToCharacterData() isn't working, because it has no reference in playerCharacterSheet variable: "Object reference not set to an instance of an object". Thought it was sorted out in Awake() - I was wrong.
So seems we'd need to set
PlayerCharacterData playerCharacterData = GameObject.Find("CharacterData").GetComponent<PlayerCharacterData>();
...in every of 50-70 functions filling the fields in UI. Not to mention every other system in game would need to do that too. Plus, everytime player opens the UI those UI fields need to be re-generated.
Is there some faster, more convenient way to do it? I was thinking about making all PlayerCharacterData variables static - this would be fast as lightning, but I'd prefer to make our engine ready for multiplayer (between 1 and 200 players). So not sure.
Also, wouldn't it be better to save character data variables into some script without Monobehaviour, so not connected to a gameObject, and maybe then we could just use PlayerCharacterData playerCharacterData = new PlayerCharacterData();
to get values from that class? I simply need those values accesible really fast and from everywhere.
Really considering the use of static variables at this point, but if there's a better solution, or if there's a better way create static variables for a multiplayer game, let me know.
Upvotes: 0
Views: 2122
Reputation: 436
Use a static class to store data. I prefer to use static class instead of Singleton "anti-pattern" if it is possible.
public static class Cache
{
/// <summary>
/// Actual cache storage for data.
/// </summary>
private static Dictionary<string, object> _cache = new Dictionary<string, object>();
/// <summary>
/// Adds/changes an object to the cache.
/// </summary>
/// <param name="key">The identifier of stored object to access it.</param>
/// <param name="obj">The object to store</param>
public static void Set(string key, object obj)
{
if (string.IsNullOrEmpty(key))
throw new Exception("key must be a non empty string");
if (_cache.ContainsKey(key))
_cache[key] = obj;
else
_cache.Add(key, obj);
}
/// <summary>
/// Get the cached object by key.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public static T Get<T>(string key)
{
var obj = _cache.ContainsKey(key) ? _cache[key] : null;
return (T)obj;
}
/// <summary>
/// Removes an object.
/// </summary>
/// <param name="key">The identifier of stored object</param>
public static void Remove(string key)
{
if (string.IsNullOrEmpty(key))
throw new Exception("key must be a non empty string");
if (_cache.ContainsKey(key))
_cache.Remove(key);
}
}
Upvotes: 1
Reputation: 389
With Singleton pattern, wich is a design pattern that restricts the instantiation of a class to one object, this way you only make one global Instance and accses it from diferent scripts.
MyClass Script:
public class MyClass : MonoBehaviour {
void Awake () {
Debug.Log(Manager.Instance.myGlobalVar);
}
}
Global Script:
public class Manager : Singleton<Manager> {
protected Manager () {} // guarantee this will be always a singleton only - can't use the constructor!
public string myGlobalVar = "whatever";
}
SingletonImplementation:
using UnityEngine;
/// <summary>
/// Be aware this will not prevent a non singleton constructor
/// such as `T myT = new T();`
/// To prevent that, add `protected T () {}` to your singleton class.
///
/// As a note, this is made as MonoBehaviour because we need Coroutines.
/// </summary>
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance;
private static object _lock = new object();
public static T Instance
{
get
{
if (applicationIsQuitting) {
Debug.LogWarning("[Singleton] Instance '"+ typeof(T) +
"' already destroyed on application quit." +
" Won't create again - returning null.");
return null;
}
lock(_lock)
{
if (_instance == null)
{
_instance = (T) FindObjectOfType(typeof(T));
if ( FindObjectsOfType(typeof(T)).Length > 1 )
{
Debug.LogError("[Singleton] Something went really wrong " +
" - there should never be more than 1 singleton!" +
" Reopening the scene might fix it.");
return _instance;
}
if (_instance == null)
{
GameObject singleton = new GameObject();
_instance = singleton.AddComponent<T>();
singleton.name = "(singleton) "+ typeof(T).ToString();
DontDestroyOnLoad(singleton);
Debug.Log("[Singleton] An instance of " + typeof(T) +
" is needed in the scene, so '" + singleton +
"' was created with DontDestroyOnLoad.");
} else {
Debug.Log("[Singleton] Using instance already created: " +
_instance.gameObject.name);
}
}
return _instance;
}
}
}
private static bool applicationIsQuitting = false;
/// <summary>
/// When Unity quits, it destroys objects in a random order.
/// In principle, a Singleton is only destroyed when application quits.
/// If any script calls Instance after it have been destroyed,
/// it will create a buggy ghost object that will stay on the Editor scene
/// even after stopping playing the Application. Really bad!
/// So, this was made to be sure we're not creating that buggy ghost object.
/// </summary>
public void OnDestroy () {
applicationIsQuitting = true;
}
}
Requirement: MonoBehaviourExtended.cs (from GetOrAddComponent)
static public class MethodExtensionForMonoBehaviourTransform {
/// <summary>
/// Gets or add a component. Usage example:
/// BoxCollider boxCollider = transform.GetOrAddComponent<BoxCollider>();
/// </summary>
static public T GetOrAddComponent<T> (this Component child) where T: Component {
T result = child.GetComponent<T>();
if (result == null) {
result = child.gameObject.AddComponent<T>();
}
return result;
}
}
Upvotes: 1
Reputation: 201
It's not clear to me where you're trying to store your PlayerCharacterData reference, but since you declared it locally inside Awake(), that won't be available outside that method. It's also not clear to me how Awake() isn't sufficient to store this reference, but even in that case, you could wrap all access to it in a "safe" method to avoid the null reference. Maybe something like this?
public class UIPlayerCharacterData : PlayerCharacterData {
PlayerCharacterData playerCharacterData;
public void NameToCharacterDataUI() {
Text_ch_sh_char_name_string.text = GetPlayerCharacterData().characterName;
}
// etc, more functions like that x50-70
void Awake () {
PlayerCharacterData playerCharacterData = GetCharacterData();
NameToCharacterDataUI();
// etc, calling more functions x50-70
}
PlayerCharacterData GetPlayerCharacterData() {
if (playerCharacterData == null) {
playerCharacterData = GameObject.FindGameObjectWithTag("CharacterData").GetComponent<PlayerCharacterData>();
}
return playerCharacterData;
}
}
Upvotes: 1