Unable to save games on mobile(both iOS and Android), works fine in editor. Using Odin Serializer.

I have a system in place for saving the objects in my scene to different save files according to your profile. At the base, I’m using the Into the Dev save system, which utilizes the Odin Serializer. It works perfectly in the editor, and not at all on mobile. It seems like it doesn’t really manage to save files. Below I’ll write down the important parts of the code, and then what I’ve tried so far and how it’s worked out.

Firstly, I’ve modified a bit the “DataSerializer” script from IntoTheDev, so as to write the names of saves always with two digits(Save00, Save01, etc), to make loading easier.

public static void ChangeProfile(int profileIndex)
        {
            if (_currentProfileIndex == profileIndex)
                return;

            if (profileIndex < 10) filePathModifier = "_0";
            else filePathModifier = "_";

            SaveFile();

            _currentProfileIndex = profileIndex;

          

            GeneratePath();
            LoadFile();
        }

“ChangeProfile” is called whenever we change the current save file to be used.

private static void GeneratePath() => _savePath = Path.Combine(Application.persistentDataPath, $"{FILE_NAME}" + filePathModifier + $"{_currentProfileIndex}.data");

Basically I just modify the path name a little bit according to our current save profile.

Below are the saving and loading scripts I use, stripped down a bit to make them more readable:

public void SaveAllPOIs()
    {
        GameManager.savedPOIs.Clear();
        foreach (POI poi in addedPOIs)
        {
            GameManager.savedPOIs.Insert(poi.index, poi);          
        }
        DataSerializer.Save("POI_List", GameManager.savedPOIs);

        GameManager.localizationManager.SendMessageToConsole("console_sauv_OK");
    }
void Start()
    {      
        if (DataSerializer.TryLoad("POI_List", out List<POI> poilist))
        {
            for (int i = 0; i < poilist.Count; i++)
            {
                AddPOIOnLocation(poilist[i].location, poilist[i]);
            }                      
        }          
    }
public void AddPOIOnLocation(Vector2d location, POI poivar)
    {
        var instance = Instantiate(spawner._markerPrefab, markerParent.transform);
        instance.name = "Marker" + (addedPOIs.Count + 1);
        instance.transform.localPosition = _map.GeoToWorldPosition(location, true);
        instance.transform.localScale = new Vector3(spawner._spawnScale, spawner._spawnScale, spawner._spawnScale);

        poivar.marker = instance;

        //Spawner variables

        spawner.IncreaseMarkers(instance);
        spawner.IncreaseLocations(poivar.location);      

        addedPOIs.Add(poivar);

        DrawConnections();
        LabelMagic();
    }

This is inside the same file as the above, if that matters:

[System.Serializable]
public class POI
{
    public int index;
    public GameObject marker;
    public Vector2d location;
}

As I said, everything works perfectly fine in the editor(I can’t test in a Windows build because I don’t have a system to check for geolocalization on desktop computers, which breaks the game in that case. I use the Mapbox SDK by the way). I’ve had some pointers, which all lead to a dead end:

  • I thought it was a read/write issue on mobile. For Android, I’ve tried manually adding storage permissions on the manifest, as well as allowing External Sotrage in the player settings. Then I’ve tried accessing different folders other than the persistent data path, which worked. I just couldn’t save.

  • The very same thing happens on iOs, as wel as several annoying errors on compilation.

  • “Cannot initialize a parameter of type ‘id _Nonnull’ with an rvalue of type ‘Class’”. If I remember correctly, this stopped the build from finishing. I’ve followed the instructions here and it worked.

  • "MissingMethodException: Default constructor not found for type ToolBox.Serialization.OdinSerializer.Int32Serializer". This one I believe came up when trying to save. For this I added ```
    public Int32Serializer() : base() {}

- __**"AOT formatter support was missing for type ‘System.Collections.Generic.List<Vector3>’"**__. This error showed up when I clicked on the save button. Now, this pointed me in another direction. I thought the problem might be AOT code stripping. So I've tried disabling code stripping altogether. Hasn't made a difference on Android, still haven't had the chance to retest on iOS. I've messed around with other build settings on Unity to no avail.

I would appreciate it if anyone had some insight onto what might be happening here. I've wasted wayy too much time already debugging this, so I can't really just keep testing different builds on different platforms while putting the project on hold. Please let me know if any information is missing.

**Unity Version:** 2020.3.33f1
**Development platform:** Windows 10, Mac for iOS building.

You’re probably better off asking the questions related to the package to the package developers, and the Odin related questions to the Odin devs.

Do you know if the project is even designed for mobile build targets?

But I know with Odin, at least with the paid package, that it needs to run an AOT scan to produce a .dll that provides a library for built project to use when serialising and deserialising Odin types. Not sure how this works with the standalone serialiser, but there might be a .dll not where it should be?

Side note, this is the problem with pre-made solutions. They work up until they don’t, and if you’re relying on them without completely understanding them then it puts your entire operation on hold. IMHO you’d be better off building your own save system appropriate to your project. You’ll learn a lot more that way too.

Same Issue. I am using the same package. Let me know if you have found the answer please. It also doesn’t load

As already noted above:

… along with a pile of other info.

Did you read all that information?

Otherwise, if you’re itching to fix it yourself (Remember, we don’t have your code), then here is how to get started:

Time to start debugging! Here is how you can begin your exciting new debugging adventures:

You must find a way to get the information you need in order to reason about what the problem is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the names of the GameObjects or Components involved?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as Debug.Log("Problem!",this);

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

Visit Google for how to see console output from builds. If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer for iOS: https://discussions.unity.com/t/700551 or this answer for Android: https://discussions.unity.com/t/699654

If you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

If your problem is with OnCollision-type functions, print the name of what is passed in!

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

https://discussions.unity.com/t/839300/3

“When in doubt, print it out!™” - Kurt Dekker (and many others)

Note: the print() function is an alias for Debug.Log() provided by the MonoBehaviour class.


Load/Save steps:

https://discussions.unity.com/t/799896/4

An excellent discussion of loading/saving in Unity3D by Xarbrough:

https://discussions.unity.com/t/870022/6

Loading/Saving ScriptableObjects by a proxy identifier such as name:

When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. The reason is they are hybrid C# and native engine objects, and when the JSON package calls new to make one, it cannot make the native engine portion of the object.

Instead you must first create the MonoBehaviour using AddComponent() on a GameObject instance, or use ScriptableObject.CreateInstance() to make your SO, then use the appropriate JSON “populate object” call to fill in its public fields.

If you want to use PlayerPrefs to save your game, it’s always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:

Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.