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.
Related
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
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.
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.
I made an app that has a login form which gets the data from a SQL database on a server , i want to add a remember me check box so the user can automatically login , how can i do this or how can i save a data to text file then retrieve it back ?
thanks
unity, to addition to writing to a file, also provides local storing in PlayerPrefs, which work for all platforms.
Please read more about it here
https://docs.unity3d.com/ScriptReference/PlayerPrefs.html
You can store login information there and check it afterward to see if player has login credentials.
This script will create txt file in your project folder and write to it single row with login string. In order to use this you have to put this scrip anywhere you like then drag in editor your INPUT FIELD and toggle. Finaly assign OnClick event on your login button to execute loginClick() method.
using UnityEngine;
using UnityEngine.UI;
using System.IO;
public class InputFieldTest : MonoBehaviour {
//reference for InputField, needs to be assigned in editor
public InputField login;
string loginText;
//reference for Toggle, needs to be assigned in editor
public Toggle rememberToggle;
bool remeberIsOn;
// Use this for initialization
void Start ()
{
loginText = "";
FindLogin();
}
public void FindLogin()
{
if (File.Exists("userSetting.txt"))
{
using (TextReader reader = File.OpenText("userSetting.txt"))
{
string str;
if ((str = reader.ReadLine()) != null) login.text = str;
}
//Debug.Log(loginText);
}
}
public void remember()
{
using (TextWriter writer = File.CreateText("userSetting.txt"))
{
writer.WriteLine(login.text);
}
}
public void loginClick()
{
remeberIsOn = rememberToggle.isOn;
loginText = login.text;
if (remeberIsOn)
{
remember();
}
else
{
using (TextWriter writer = File.CreateText("userSetting.txt"))
{
writer.WriteLine("");
}
}
}
}
Unity3D has a Screen class with an orientation property that allows you to force orientation in code, which lets you have different scenes with different orientations (useful in mini-games).
Setting this works fine for Android but crashes on iOS. What is the fix?
The problem is the file UnityViewControllerBaseiOS.mm that gets generated during the build for iOS has an assert in it which inadvertently prevents this property from being used. It is possible to create a post-build class that runs after the iOS build files have been generated that can alter the generated code before you compile it in XCode.
Just create a C# script named iOSScreenOrientationFix.cs and paste in the following code - adapted from this Unity3D forum post. Note that this file must be placed in a folder named Editor, or in one of its subfolders.
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using System.IO;
namespace Holovis
{
public class iOSScreenOrientationFix : MonoBehaviour
{
#if UNITY_CLOUD_BUILD
// This method is added in the Advanced Features Settings on UCB
// PostBuildProcessor.OnPostprocessBuildiOS
public static void OnPostprocessBuildiOS (string exportPath)
{
Debug.Log("OnPostprocessBuildiOS");
ProcessPostBuild(BuildTarget.iPhone,exportPath);
}
#endif
[PostProcessBuild]
public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
{
#if !UNITY_CLOUD_BUILD
ProcessPostBuild(buildTarget, path);
#endif
}
private static void ProcessPostBuild(BuildTarget buildTarget, string path)
{
if (buildTarget == BuildTarget.iOS)
{
#if !UNITY_CLOUD_BUILD
Debug.Log("Patching iOS to allow setting orientation");
#endif
string filePath = Path.Combine(path, "Classes");
filePath = Path.Combine(filePath, "UI");
filePath = Path.Combine(filePath, "UnityViewControllerBaseiOS.mm");
Debug.Log("File Path for View Controller Class: " + filePath);
string classFile = File.ReadAllText(filePath);
string newClassFile = classFile.Replace("NSAssert(UnityShouldAutorotate()", "//NSAssert(UnityShouldAutorotate()");
File.WriteAllText(filePath, newClassFile);
}
}
}
}
You can set it in a scene by attaching the following MonoBehaviour to a game object
using UnityEngine;
namespace Holovis
{
public class SetDeviceOrientation : MonoBehaviour
{
public ScreenOrientation orientation = ScreenOrientation.AutoRotation;
void Awake()
{
Screen.orientation = orientation;
}
}
}
NOTE: Setting Screen.orientation has no effect when running on desktop, in the Unity editor, or when testing using Unity Remote.