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.
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'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.
I'm working on a small program that can modify the animation at run time(Such as when you run faster the animation not only play faster but also with larger movement). So i need to get the existing animation, change its value, then send it back.
I found it is interesting that i can set a new curve to the animation, but i can't get access to what i already have. So I either write a file to store my animation curve (as text file for example), or i find someway to read the animation on start up.
I tried to use
AnimationUtility.GetCurveBindings(AnimationCurve);
It worked in my testing, but in some page it says this is a "Editor code", that if i build the project into a standalone program it will not work anymore. Is that true? If so, is there any way to get the curve at run time?
Thanks to the clearify from Benjamin Zach and suggestion from TehMightyPotato
I'd like to keep the idea about modifying the animation at runtime. Because it could adapt to more situations imo.
My idea for now is to write a piece of editor code that can read from the curve in Editor and output all necesseary information about the curve (keyframes) into a text file. Then read that file at runtime and create new curve to overwrite the existing one. I will leave this question open for a few days and check it to see if anyone has a better idea about it.
As said already AnimationUtility belongs to the UnityEditor namespace. This entire namespace is completely stripped of in a build and nothing in it will be available in the final app but only within the Unity Editor.
Store AnimationCurves to file
In order to store all needed information to a file you could have a script for once serializing your specific animation curve(s) in the editor before building using e.g. BinaryFormatter.Serialize. Then later on runtime you can use BinaryFormatter.Deserialize for returning the info list again.
If you wanted it more editable you could as well use e.g. JSON or XML of course
UPDATE: In general Stop using BinaryFormatter!
In the newest Unity versions the Newtonsoft Json.NET package comes already preinstalled so simply rather use JSON
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Unity.Plastic.Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
public class AnimationCurveManager : MonoBehaviour
{
[Serializable]
public sealed class ClipInfo
{
public int ClipInstanceID;
public List<CurveInfo> CurveInfos = new List<CurveInfo>();
// default constructor is sometimes required for (de)serialization
public ClipInfo() { }
public ClipInfo(Object clip, List<CurveInfo> curveInfos)
{
ClipInstanceID = clip.GetInstanceID();
CurveInfos = curveInfos;
}
}
[Serializable]
public sealed class CurveInfo
{
public string PathKey;
public List<KeyFrameInfo> Keys = new List<KeyFrameInfo>();
public WrapMode PreWrapMode;
public WrapMode PostWrapMode;
// default constructor is sometimes required for (de)serialization
public CurveInfo() { }
public CurveInfo(string pathKey, AnimationCurve curve)
{
PathKey = pathKey;
foreach (var keyframe in curve.keys)
{
Keys.Add(new KeyFrameInfo(keyframe));
}
PreWrapMode = curve.preWrapMode;
PostWrapMode = curve.postWrapMode;
}
}
[Serializable]
public sealed class KeyFrameInfo
{
public float Value;
public float InTangent;
public float InWeight;
public float OutTangent;
public float OutWeight;
public float Time;
public WeightedMode WeightedMode;
// default constructor is sometimes required for (de)serialization
public KeyFrameInfo() { }
public KeyFrameInfo(Keyframe keyframe)
{
Value = keyframe.value;
InTangent = keyframe.inTangent;
InWeight = keyframe.inWeight;
OutTangent = keyframe.outTangent;
OutWeight = keyframe.outWeight;
Time = keyframe.time;
WeightedMode = keyframe.weightedMode;
}
}
// I know ... singleton .. but what choices do we have? ;)
private static AnimationCurveManager _instance;
public static AnimationCurveManager Instance
{
get
{
// lazy initialization/instantiation
if (_instance) return _instance;
_instance = FindObjectOfType<AnimationCurveManager>();
if (_instance) return _instance;
_instance = new GameObject("AnimationCurveManager").AddComponent<AnimationCurveManager>();
return _instance;
}
}
// Clips to manage e.g. reference these via the Inspector
public List<AnimationClip> clips = new List<AnimationClip>();
// every animation curve belongs to a specific clip and
// a specific property of a specific component on a specific object
// for making this easier lets simply use a combined string as key
private string CurveKey(string pathToObject, Type type, string propertyName)
{
return $"{pathToObject}:{type.FullName}:{propertyName}";
}
public List<ClipInfo> ClipCurves = new List<ClipInfo>();
private string filePath = Path.Combine(Application.streamingAssetsPath, "AnimationCurves.dat");
private void Awake()
{
if (_instance && _instance != this)
{
Debug.LogWarning("Multiple Instances of AnimationCurveManager! Will ignore this one!", this);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
// load infos on runtime
LoadClipCurves();
}
#if UNITY_EDITOR
// Call this from the ContextMenu (or later via editor script)
[ContextMenu("Save Animation Curves")]
private void SaveAnimationCurves()
{
ClipCurves.Clear();
foreach (var clip in clips)
{
var curveInfos = new List<CurveInfo>();
ClipCurves.Add(new ClipInfo(clip, curveInfos));
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
{
var key = CurveKey(binding.path, binding.type, binding.propertyName);
var curve = AnimationUtility.GetEditorCurve(clip, binding);
curveInfos.Add(new CurveInfo(key, curve));
}
}
// create the StreamingAssets folder if it does not exist
try
{
if (!Directory.Exists(Application.streamingAssetsPath))
{
Directory.CreateDirectory(Application.streamingAssetsPath);
}
}
catch (IOException ex)
{
Debug.LogError(ex.Message);
}
// create a new file e.g. AnimationCurves.dat in the StreamingAssets folder
var json = JsonConvert.SerializeObject(ClipCurves);
File.WriteAllText(filePath, json);
AssetDatabase.Refresh();
}
#endif
private void LoadClipCurves()
{
if (!File.Exists(filePath))
{
Debug.LogErrorFormat(this, "File \"{0}\" not found!", filePath);
return;
}
var fileStream = new FileStream(filePath, FileMode.Open);
var json = File.ReadAllText(filePath);
ClipCurves = JsonConvert.DeserializeObject<List<ClipInfo>>(json);
}
// now for getting a specific clip's curves
public AnimationCurve GetCurve(AnimationClip clip, string pathToObject, Type type, string propertyName)
{
// either not loaded yet or error -> try again
if (ClipCurves == null || ClipCurves.Count == 0) LoadClipCurves();
// still null? -> error
if (ClipCurves == null || ClipCurves.Count == 0)
{
Debug.LogError("Apparantly no clipCurves loaded!");
return null;
}
var clipInfo = ClipCurves.FirstOrDefault(ci => ci.ClipInstanceID == clip.GetInstanceID());
// does this clip exist in the dictionary?
if (clipInfo == null)
{
Debug.LogErrorFormat(this, "The clip \"{0}\" was not found in clipCurves!", clip.name);
return null;
}
var key = CurveKey(pathToObject, type, propertyName);
var curveInfo = clipInfo.CurveInfos.FirstOrDefault(c => string.Equals(c.PathKey, key));
// does the curve key exist for the clip?
if (curveInfo == null)
{
Debug.LogErrorFormat(this, "The key \"{0}\" was not found for clip \"{1}\"", key, clip.name);
return null;
}
var keyframes = new Keyframe[curveInfo.Keys.Count];
for (var i = 0; i < curveInfo.Keys.Count; i++)
{
var keyframe = curveInfo.Keys[i];
keyframes[i] = new Keyframe(keyframe.Time, keyframe.Value, keyframe.InTangent, keyframe.OutTangent, keyframe.InWeight, keyframe.OutWeight)
{
weightedMode = keyframe.WeightedMode
};
}
var curve = new AnimationCurve(keyframes)
{
postWrapMode = curveInfo.PostWrapMode,
preWrapMode = curveInfo.PreWrapMode
};
// otherwise finally return the AnimationCurve
return curve;
}
}
Then you can do something like e.e.
AnimationCurve originalCurve = AnimationCurvesManager.Instance.GetCurve(
clip,
"some/relative/GameObject",
typeof<SomeComponnet>,
"somePropertyName"
);
the second parameter pathToObject is an empty string if the property/component is attached to the root object itself. Otherwise it is given in the hierachy path as usual for Unity like e.g. "ChildName/FurtherChildName".
Now you can change the values and assign a new curve on runtime.
Assigning new curve on runtime
On runtime you can use animator.runtimeanimatorController in order to retrieve a RuntimeAnimatorController reference.
It has a property animationClips which returns all AnimationClips assigned to this controller.
You could then use e.g. Linq FirstOrDefault in order to find a specific AnimationClip by name and finally use AnimationClip.SetCurve to assign a new animation curve to a certain component and property.
E.g. something like
// you need those of course
string clipName;
AnimationCurve originalCurve = AnimationCurvesManager.Instance.GetCurve(
clip,
"some/relative/GameObject",
typeof<SomeComponnet>,
"somePropertyName"
);
// TODO
AnimationCurve newCurve = SomeMagic(originalCurve);
// get the animator reference
var animator = animatorObject.GetComponent<Animator>();
// get the runtime Animation controller
var controller = animator.runtimeAnimatorController;
// get all clips
var clips = controller.animationClips;
// find the specific clip by name
// alternatively you could also get this as before using a field and
// reference the according script via the Inspector
var someClip = clips.FirstOrDefault(clip => string.Equals(clipName, clip.name));
// was found?
if(!someClip)
{
Debug.LogWarningFormat(this, "There is no clip called {0}!", clipName);
return;
}
// assign a new curve
someClip.SetCurve("relative/path/to/some/GameObject", typeof(SomeComponnet), "somePropertyName", newCurve);
Note: Typed on smartphone so no warranty! But I hope the idea gets clear...
Also checkout the example in AnimationClip.SetCurve → You might want to use the Animation component instead of an Animator in your specific use case.
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 have recently been creating a game with tutorials. Unfortunately, they didn't cover a save score feature. Thanks to another user, I was able to figure out that I needed to use playerprefs. I watched tutorials online, but none of them were helpful. If you can, please help me!
Gold Per Sec Script:
using UnityEngine;
using System.Collections;
public class GoldPerSec : MonoBehaviour {
public UnityEngine.UI.Text gpsDisplay;
public Click click;
public ItemManager[] items;
void Start () {
StartCoroutine(AutoTick ());
}
void Update () {
gpsDisplay.text = GetGoldPerSec() + " Money Per Sec";
}
public float GetGoldPerSec() {
float tick = 0;
foreach (ItemManager item in items) {
tick += item.count * item.tickValue;
}
return tick;
}
public void AutoGoldPerSec() {
click.gold += GetGoldPerSec() / 10;
}
IEnumerator AutoTick() {
while (true) {
AutoGoldPerSec();
yield return new WaitForSeconds(0.10f);
}
}
}
Gold Per Click script:
using UnityEngine;
using System.Collections;
public class Click : MonoBehaviour {
public UnityEngine.UI.Text gpc;
public UnityEngine.UI.Text goldDisplay;
public float gold = 0.00f;
public int goldperclick = 1;
void Update () {
goldDisplay.text = "" + gold.ToString("F0");
gpc.text = "Money Per Click: " + goldperclick;
}
public void Clicked(){
gold += goldperclick;
}
}
My idea was for the game to save when the game is quit, and load as soon as you load the game back up. I am a complete beginner, if anyone can tell me how to do this, please tell me! Thanks! :D
You can use unity's existing functions to achieve this.
For saving data use unity's OnApplicationQuit function like this
void OnApplicationQuit() {
PlayerPrefs.SetFloat("key", value);
}
And for Restoring the values use unity's Awake function like this
void Awake(){
value = PlayerPrefs.GetFloat("key");
}
Please note that PlayerPrefs is an easy way to save data but also an very unsafe way. The player can easily manipulate his "goldValue" since it's just stored as an integer in some file on his device. PlayerPrefs should usually just be used for values the player can changed any way within in game, like volume setting etc.
EXAMPLE CODE
void Save()
{
string filename = "/filename.dat";
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.persistentDataPath+filename);
bf.Serialize(file, goldValue); //Use can easily use e.g. a List if you want to store more date
file.Close();
}
bool Load()
{
string filename = "/filename.dat";
if (File.Exists(Application.persistentDataPath + filename))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + filename, FileMode.Open);
goldValue=(int) bf.Deserialize(file);
file.Close();
return true;
}
return false;
}
Add the following code to Click class:
void Awake()
{
LoadData();
}
void OnApplicationQuit()
{
SaveData();
}
void SaveData()
{
PlayerPrefs.SetFloat("gold",gold);
}
void LoadData()
{
gold = PlayerPrefs.GetFloat("gold",0f);
}