Unity deserializing an object in another project - class

I am creating a tool that helps the artist in my studio design their UI easily, and then give them the ability to export the UI to the developers to use in their projects.
Long story short, I followed this tutorial: What is the best way to save game state?
and it worked correctly when I wrote everything in the same script.
However when I opened another unity project to import the data, it stopped working correctly. It would read the file, but then the spawnObject variable which is supposed to be instantiated stays null.
The class I use here is called UIdata, and it has only one gameobject variable which is inputObject, and the class is exactly the same as the one I am exporting from the other project
I am not quite sure why it is not working, can deserialization work if its importing something from another project?
Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Runtime.Serialization;
using System.Xml.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Xml;
using System;
public class DataSaver
{
public static void saveData<T>(T dataToSave, string dataFileName)
{
string tempPath = Application.streamingAssetsPath + "/newUICorrect.txt";
string jsonData = JsonUtility.ToJson(dataToSave, true);
byte[] jsonByte = Encoding.ASCII.GetBytes(jsonData);
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
}
try
{
File.WriteAllBytes(tempPath, jsonByte);
Debug.Log("Saved Data to: " + tempPath.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To PlayerInfo Data to: " + tempPath.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
}
}
public static T loadData<T>(string dataFileName)
{
string tempPath = Application.streamingAssetsPath + "/newUICorrect.txt";
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Debug.LogWarning("Directory does not exist");
return default(T);
}
if (!File.Exists(tempPath))
{
Debug.Log("File does not exist");
return default(T);
}
else
{
Debug.Log("found file");
}
byte[] jsonByte = null;
try
{
jsonByte = File.ReadAllBytes(tempPath);
Debug.Log("Loaded Data from: " + tempPath.Replace("/", "\\"));
}
catch (Exception e)
{
Debug.LogWarning("Failed To Load Data from: " + tempPath.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
}
string jsonData = Encoding.ASCII.GetString(jsonByte);
object resultValue = JsonUtility.FromJson<T>(jsonData);
return (T)Convert.ChangeType(resultValue, typeof(T));
}
}
[System.Serializable]
public class UIData
{
public GameObject inputObject;
}
public class LoadingScript : MonoBehaviour
{
private GameObject objectToSpawn;
void Start()
{
}
void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
importObject();
}
}
public void importObject()
{
UIData loadedData = new UIData();
loadedData = DataSaver.loadData<UIData>("UI");
objectToSpawn = loadedData.inputObject;
if (loadedData == null)
{
return;
}
print(objectToSpawn);
}
}

#Programmer is correct, you cannot serialize engine types. There are several ways to go about resolving your issue. The first is to rebuild your gameobject by serializing its MonoBehaviour components in the following manner (pseudocode):
public GameObject TestSaveAndLoadComponent(MyMonoBehaviour myComponent, GameObject objectToLoadComponentOnto){
// convert component to Json
string jsonString = JsonUtility.ToJson(myComponent);
// Pretend to save the json string to File
string directory = "TestDirectory";
string file = "objectFile";
SaveLoadData.SaveString(directory,file,objectJson);
// load string from file
string loadString = SaveLoadData.LoadString(directory,file);
// add a MonoBehaviour component to the object so that it can be
// overwritten by the JsonUtility
MyMonoBehaviour componentToOverwrite = objectToLoadComponentOnto.AddComponent<MyMonoBehaviour>();
JsonUtility.FromJsonOverwrite(loadString, componentToOverwrite);
return newObject;
}
JsonUtility may not provide a deep copy of your your monoBehaviour class, I don't use it much. It's pretty bare bones, and if it doesn't meet your needs, there's always Json.Net for unity.
All that being said, you shouldn't need to serialize gameObjects or monobehaviours if you structure your code in the proper way, using an Model-view-controller pattern. In such a case you would only need to serialize very simple classes that define the Model for the UI, and then the your view classes would rebuild the ui in a separate scene or project from the given model.

Related

How to duplicate, reuse, and dispose of dynamically generated addressable objects

How do I load an addressable AssetReference dynamically, like a crate or something, via LoadAssetAsync and then reuse it throughout my game without calling LoadAssetAsync again?
I am generating a series of square platforms that are addressable objects. When each platform is spawned, it generates its own set of addressable objects to populate the platform. The objects for each platform are assigned to an array of AssetReferences via the inspector. Using the code below, I am able to loop through the array and generate the objects for the platform. My problem is that when the next platform tries to generate its objects via LoadAssetAsync I get the error
Attempting to load AssetReference that has already been loaded.
I get that the objects have already been loaded, so doing it again makes no sense. How can I access the addressables, test that they are in the scene, and duplicate them as prefabs for future platforms?
I think this is probably simple, and I am just missing something basic, but I can't figure it out.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class SquareObjectsDistributor : MonoBehaviour
{
public SquareObject[] SquareObjectArray;
[System.Serializable]
public class SquareObject {
public string sqObject;
public AssetReference sqObjectReference;
public Vector3 sqObjectPosition;
public Vector3 sqObjectRotation;
}
public void DistributeObjects(int whichSquare) {
for(int i = 0; i < SquareObjectArray.Length; i++)
{
InstantiateSquareObjects(whichSquare, i);
}
}
void InstantiateSquareObjects(int whichSquare, int whichObject) {
Debug.Log("Object_" + whichSquare + "_" + whichObject);
SquareObjectArray[whichObject].sqObjectReference.LoadAssetAsync<GameObject>().Completed +=
(asyncOperationHandle) => {
if(asyncOperationHandle.Status == AsyncOperationStatus.Succeeded) {
GameObject newObject = Instantiate(asyncOperationHandle.Result, SquareObjectArray[whichObject].sqObjectPosition, Quaternion.Euler(SquareObjectArray[whichObject].sqObjectRotation)) as GameObject;
newObject.name = "Object_" + whichSquare + "_" + whichObject;
} else {
Debug.Log("Failed to load");
}
};
}
}```

(Unity) How do I save/load a party's stats?

I'm stumped in trying to save that. I had previously managed to save stats normally like this:
However I'm not sure how to handle it for when there are multiple characters (or when I have to save stuff like inventory data or other miscellaneous flags, for that matter). I'm thinking of using, say, lists, but then I don't know how to load them to each character, since I'm thinking of adding party members as the game goes on.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DataJson
{
public int wp;
public float spe;
public Vector3 pos;
public DataJson(Player play)
{
wp = play.WP;
spe = play.spe;
pos = new Vector3();
pos.x = play.transform.position.x;
pos.y = play.transform.position.y;
pos.z = play.transform.position.z;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
public static class SaveJson
{
// Start is called before the first frame update
public static void Save(Player player)
{
DataJson data = new DataJson(player);
string json = JsonUtility.ToJson(data);
File.WriteAllText(Application.dataPath + "/save.txt", json);
Debug.Log("Saved");
Debug.Log(json);
}
// Update is called once per frame
public static void Load(Player player)
{
string json = File.ReadAllText(Application.dataPath + "/save.txt");
DataJson data = JsonUtility.FromJson<DataJson>(json);
Debug.Log("loaded from: "+json);
player.WP = data.wp;
player.spe = data.spe;
player.position = data.pos;
player.transform.position = player.position;
}
public static void SaveTemp(Player player)
{
DataTemp data = new DataTemp(player);
string json = JsonUtility.ToJson(data);
File.WriteAllText(Application.dataPath + "/temp.txt", json);
Debug.Log("Saved");
Debug.Log(json);
}
// Update is called once per frame
public static void LoadTemp(Player player)
{
string path = Application.dataPath + "/temp.txt";
if (File.Exists(path))
{
string json = File.ReadAllText(Application.dataPath + "/temp.txt");
DataTemp data = JsonUtility.FromJson<DataTemp>(json);
player.WP = data.wp;
player.spe = data.spe;
Debug.Log("Data WP: " + data.wp + "Data Spe: " + data.spe);
File.Delete(Application.dataPath + "/temp.txt");
}
else
{
Debug.Log("nothing");
}
}
}
How I implement save / load in my RPG is that I assign an ID to each object i'm going to save.
So if I want to save some random crates throughout the level, one might have the ID of 'crate1', another as 'crate2' etc. Naming doesn't really matter, as long as they are unique. Then I save this id into the individual save data, and I have many individual save datas in an array.
Now whenever I load a save file, I just compare ID's to see who the data belongs to.
Psuedocode:
for each savedata in savedataArray
Find character with the same ID
Load Data onto the character

Unity3D changes not showing in build

I am currently working on a basic 2D Android quiz game in Unity 3D. The issue is that certain changes are not passing through to the build even though they are visible and working in the editor. I am using Unity 2019.4.1f1(64bit) on Windows 10(x64). What I have tried so far:
Closed down Unity and stopped the background service, removed previous instances of the build, restarted Unity and rebuilt;
Deleted the Library folder and retried the build;
On the mobile I am testing the app, I have done factory reset and reinstalled the app.
I have restarted the Windows 10 Laptop between each test.
Any other suggestions as to why the build is not being created with the most up to date scripts?
EDIT
I have kind of got to the bottom of the issue. The cause seems to be my Level Select Scene. I am using a json file to hold player data including:
Player Name;
Last completed Level(for activation/deactivation of level buttons;
Level Score;
Level completion status as a boolean.
I am using a separate function in a script called Player.cs(LoadLastLevel) to obtain the last completed level number. Data extraction from the json is achieved using SimpleJSON, here is the code for the two areas affected:
LevelSelectController.cs
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using SimpleJSON;
public class LevelSelectController : MonoBehaviour
{
static public int currentLevel;
public Font kb_Font;
public List<Button> buttonsList = new List<Button>();
public int lastCompletedLevel;
public Player player;
// Start is called before the first frame update
void Start()
{
int l = player.LoadLastLevel();
for (int i = 0; i < buttonsList.Count; i++)
{
int levelNum = i;
buttonsList[i].onClick.AddListener(() => {currentLevel = levelNum;});
buttonsList[i].GetComponentInChildren<Text>().fontSize = 48;
buttonsList[i].GetComponentInChildren<Text>().font = kb_Font;
buttonsList[i].GetComponent<Image>().color = Color.grey;
if (i > l)
{
buttonsList[i].interactable = false;
}
}
}
public void StartLevel()
{
SceneManager.LoadScene ("Game");
}
public void ReturnToMenu()
{
SceneManager.LoadScene ("MenuScreen");
}
}
Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using SimpleJSON;
using System.IO;
public class Player : MonoBehaviour
{
public string playerName;
public int lastCompletedLevel;
public int Load()
{
string filePath = Application.dataPath + "/StreamingAssets/data.json";
string jsonString = File.ReadAllText(filePath);
JSONObject playerJson = (JSONObject)JSON.Parse(jsonString);
playerName = playerJson["player"]["playerName"];
lastCompletedLevel = playerJson["player"]["lastCompletedLevel"];
return lastCompletedLevel;
}
public int LoadLastLevel()
{
string filePath = Application.dataPath + "/StreamingAssets/data.json";
string jsonString = File.ReadAllText(filePath);
JSONObject playerJson = (JSONObject)JSON.Parse(jsonString);
lastCompletedLevel = playerJson["player"]["lastCompletedLevel"];
return lastCompletedLevel;
}
public void SavePlayerData(int completedLevel, int levelScore)
{
string filePath = Application.dataPath + "/StreamingAssets/data.json";
string jsonString = File.ReadAllText(filePath);
JSONObject playerJson = (JSONObject)JSON.Parse(jsonString);
playerJson["player"]["lastCompletedLevel"] = completedLevel;
playerJson["player"]["levels"][completedLevel][2] = levelScore;
playerJson["player"]["levels"][completedLevel][1] = true;
string myJsonString = (playerJson.ToString());
File.WriteAllText(filePath, myJsonString);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
I manually set the value of 'l' in the LevelSelectController.cs script, it worked. I then set the value of 'l' manually in the Player.cs to rule out an issue with the JSON portion of the script, it failed. So I believe the root cause to be that the build is not bringing in the Player.cs script and therefore the call to player.LoadLastLevel() in the LevelSelectController.cs script is failing.
I suspect that the issue is with how I am calling player.LoadLastLevel(), should I be calling the GameObject the script is attached to?
EDIT TWO
I think I have the cause of the issue, after using adb to inspect the logcat on the device as the application runs, I am getting an DirectoryNotFoundException when looking for my JSON file in the StreamingAssets folder so I believe that by problem line is here:
string filePath = Application.dataPath + "/StreamingAssets/data.json";
Any thoughts on how to resolve would be most welcomed?
EDIT THREE
I jumped the gun by setting an Answer to this question. Whilst the code provided below happily reads from data.json, it will not write to it:
public int LoadLastLevel()
{
string filePath = Path.Combine(Application.streamingAssetsPath, "data.json");
string jsonString;
if (Application.platform == RuntimePlatform.Android)
{
UnityWebRequest www = UnityWebRequest.Get(filePath);
www.SendWebRequest();
while (!www.isDone) ;
jsonString = www.downloadHandler.text;
}
else
{
jsonString = File.ReadAllText(filePath);
}
JSONObject playerJson = (JSONObject)JSON.Parse(jsonString);
lastCompletedLevel = playerJson["player"]["lastCompletedLevel"];
return lastCompletedLevel;
}
I have been able to create a *.json file in the persistentDataPath instead using the same code and simply changing the target of filePath to persistentDataPath. The issue is that I am not able to subsequently read or edit the file. Reading returns null.
Credit for getting me this far goes to Triple_Why over at the Unity Forum for his response to the message at
https://forum.unity.com/threads/cant-use-json-file-from-streamingassets-on-android-and-ios.472164/
Any further guidance on how to read from/write to a file that is in the persistentDataPath on Android would be welcomed?
In the end it was a frustratingly simple solution, after examining the logcat of the device and seen curl errors, this pointed me to the cause being that the intent of UnityWebRequest is the querying of the streamingAssetsPath, I am no longer using this so I just had to remove reference to UnityWebRequest and go back to reading the data using File.ReadAllText and voila, just like that it started working.
public int LoadLastLevel()
{
string filePath = Application.persistentDataPath + "/playerdata.json";
string jsonString;
jsonString = File.ReadAllText(filePath);
JSONObject playerJson = (JSONObject)JSON.Parse(jsonString);
lastCompletedLevel = playerJson["player"]["lastCompletedLevel"];
Debug.Log("Player name is " + playerName + " and the last completed level is " + lastCompletedLevel);
Debug.Log("The filePath to PlayerData.JSON is " + filePath);
return lastCompletedLevel;
}
Hope this helps anyone else facing a similar issue.

How to instantiate objects in a specified place in unity?

I have an AR app that displays objects when it detects a QR code. The way I do it is with an empty object called model caller that has an script that instantiates a model, this is the script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Android;
using UnityEngine.Networking;
using UnityEngine.UI;
public class Load_Models: MonoBehaviour
{
// Start is called before the first frame update
[Serializable]
public class PlaceModel
{
public string Clave = "";
}
public GameObject[] model;
public string[] clave_modelos;
public string URL;
public GameObject ModeloUI;
public PlaceModel placeModel;
Dictionary<string, GameObject> strGO = new Dictionary<string, GameObject>();
public void Start()
{
StartCoroutine(GetRequest(URL));
for (int i = 0; i < model.Length; i++)
{
strGO.Add(clave_modelos[i], model[i]);
}
}
IEnumerator GetRequest(string uri)
{
using (UnityWebRequest webRequest = UnityWebRequest.Get(uri))
{
// Request and wait for the desired page.
yield return webRequest.SendWebRequest();
string jsonForm = uri;
if (webRequest.isNetworkError)
{
Debug.Log("Error loading");
}
else
{
try
{
PlaceModel model_1 = JsonUtility.FromJson<PlaceModel>(webRequest.downloadHandler.text);
Instantiate(strGO[model_1.Clave], new Vector3(0, 0, 0), Quaternion.identity, transform); //instantiates the model
Debug.Log("Loaded");
}
catch
{
Debug.Log("Error in connection");
}
}
}
}
}
and this is what happens when I detect more than 1 QR (it also happens with only one, just without the models "fusing" with each other):
Explanation: It should display 3 models, 1 simple street (the white block) and 2 "no model" 3d texts, but, the idea is for the models to appear "attached" (I don't know how to word it) to the QR code. And I tried to do it by having model caller as a child of the ImageTarget and with model caller being in the dead center of the imagetarget, also with new Vector3(0, 0, 0).
Is there a way to do this?
I know that I can do it by simply using the prefab by itself instead of a script, but I need for the models to change depending on a website (which I already did).
I'm using EasyAR 3 for this
If I got you right, try to change Instantiate line this way:
Instantiate(strGO[model_1.Clave], transform.position, Quaternion.identity, transform);
Also, maybe, you dont need to set parent transform, it depends from your implementation.

why is ma json data read on PC but not in android?

I'm trying to save the name of the last level my player gets in and I wanted to use Json and the streaming assets folder, so I get a JSON file in a folder in my unity project
└───Assets
└───[...] (all my other unity project folder)
└───StreamingAssets
lastlevel.json
I created an object ProgressSavior with and attached script to save in this JSON file the name of the level and also add a function to get the level name, so that in my main menu when I press play my last scene played is loaded
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ProgressSavior : MonoBehaviour
{
string chemin = Application.streamingAssetsPath + "/lastlevel.json";
Regex levelRegex = new Regex(#"^level_[1-9][0-9]*$");
string Json;
string levelname;
Level Saved;
// Start is called before the first frame update
void Start()
{
levelname = SceneManager.GetActiveScene().name;
Json = File.ReadAllText(chemin);
Saved = JsonUtility.FromJson<Level>(Json);
if (levelRegex.IsMatch(levelname)) {
save();
}
}
bool ShouldWeSave(string s)
{
Debug.Log(s);
Debug.Log(Saved.name);
return int.Parse(s.Split('_')[1]) > int.Parse(Saved.name.Split('_')[1]);
}
public void save()
{
if (ShouldWeSave(levelname))
{
Saved.name = levelname;
Json = JsonUtility.ToJson(Saved);
File.WriteAllText(chemin, Json);
} else
{
Debug.LogWarning("saving was Useless");
}
}
public string load()
{
return Saved.name;
}
}
public class Level
{
public string name;
}
And on PC it work wonderfully but when I build the project on android it won't work and I don't understand why,
On Android, the path will be different, as mentioned in the official documentation here.
Use "jar:file://" + Application.dataPath + "!/assets" instead.