How to serialize SerializableMap and save to game state

Using Naninovel C# APIs: adding custom actor implementations or commands, overriding engine services, integrating with other systems, etc.
Post Reply
TuTlo
Posts: 13
Joined: 02 Jun 2022 14:40

How to serialize SerializableMap and save to game state

Post by TuTlo »

Hi,

I developed my own Inventory/Shop/Purchase system, but I have trouble serializing a dictionary ( I made use of the SerializableMap in Naninovel)

I have a customized ScritableObject class Item with below code:

Code: Select all

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/New Item", order = 0)]
[Serializable]
public class Item : ScriptableObject 
{
    public string itemName;
    public Sprite itemImage;

[TextArea]
public string itemInfo;

public bool gift;

public int price;
}

In my InventoryManager, I implement the SerializableMap as below

Code: Select all

    
[Serializable] public class InventoryMap: SerializableMap<Item, int> { public InventoryMap(){} public InventoryMap(InventoryMap map){} }

And I did similar job in InventoryManager as in https://naninovel.com/guide/state-manag ... stom-state

Code: Select all

    
[Serializable] public class GameState { public InventoryMap SaveItemDict; } public static InventoryMap itemDict; void Awake() { if (instance != null) { Destroy(this); Debug.Log("singleton destroy..."); } instance = this; itemDict = new InventoryMap(); stateManager = Engine.GetService<IStateManager>(); } private void OnEnable() { stateManager.AddOnGameSerializeTask(SerializeState); stateManager.AddOnGameDeserializeTask(DeserializeState); } private void OnDisable() { stateManager.RemoveOnGameSerializeTask(SerializeState); stateManager.RemoveOnGameDeserializeTask(DeserializeState); } private void SerializeState (GameStateMap stateMap) { var state = new GameState() { SaveItemDict = itemDict // I would suspect is there a copy issue/not just simple equal here }; stateMap.SetState(state); } private UniTask DeserializeState(GameStateMap stateMap) { var state = stateMap.GetState<GameState>(); if (state is null) return UniTask.CompletedTask; itemDict = state.SaveItemDict; // I would suspect is there a copy issue/not just simple equal here return UniTask.CompletedTask; }

The issue could be described as below:

  1. If I add an <Item, int> ( an item, number of Item added) to the bag, and save, it was working fine. I exit play mode in unity editor, restart the game, its working fine as well.
  2. The problem happens when I exit the whole unity editor, when I restart the Unity Editor, it always gives an ArgumentNullException

ArgumentNullException: Value cannot be null.
Parameter name: key
System.Collections.Generic.Dictionary2[TKey,TValue].TryInsert (TKey key, TValue value, System.Collections.Generic.InsertionBehavior behavior) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Collections.Generic.Dictionary
2[TKey,TValue].set_Item (TKey key, TValue value) (at <695d1cc93cca45069c528c15c9fdd749>:0)
Naninovel.SerializableMap2[TKey,TValue].OnAfterDeserialize () (at Assets/Naninovel/Runtime/Common/Collections/SerializableMap.cs:67)
UnityEngine.JsonUtility:FromJson(String, Type)
Naninovel.StateMap:OnAfterDeserialize() (at Assets/Naninovel/Runtime/State/StateMap.cs:33)
Naninovel.GameStateMap:OnAfterDeserialize() (at Assets/Naninovel/Runtime/State/GameStateMap.cs:59)
UnityEngine.JsonUtility:FromJson(String)
Naninovel.<DeserializeDataAsync>d__20:MoveNext() (at Assets/Naninovel/Runtime/Common/SaveSlotManager/IOSaveSlotManager.cs:157)
UniRx.Async.CompilerServices.MoveNextRunner
1:Run() (at Assets/Naninovel/ThirdParty/UniTask/UniRx.Async/CompilerServices/MoveNextRunner.cs:18)
UniRx.Async.UniTaskCompletionSource1:TryInvokeContinuation() (at Assets/Naninovel/ThirdParty/UniTask/UniRx.Async/UniTaskCompletionSource.cs:344)
UniRx.Async.UniTaskCompletionSource
1:TrySetResult(String) (at Assets/Naninovel/ThirdParty/UniTask/UniRx.Async/UniTaskCompletionSource.cs:363)
UniRx.Async.CompilerServices.AsyncUniTaskMethodBuilder1:SetResult(String) (at Assets/Naninovel/ThirdParty/UniTask/UniRx.Async/CompilerServices/AsyncUniTaskMethodBuilder.cs:227)
Naninovel.<UnzipStringAsync>d__28:MoveNext() (at Assets/Naninovel/Runtime/Common/Utilities/StringUtils.cs:395)
UniRx.Async.CompilerServices.MoveNextRunner
1:Run() (at Assets/Naninovel/ThirdParty/UniTask/UniRx.Async/CompilerServices/MoveNextRunner.cs:18)
UnityEngine.UnitySynchronizationContext:ExecuteTasks() (at /Users/bokken/buildslave/unity/build/Runtime/Export/Scripting/UnitySynchronizationContext.cs:107)

I tried debugging, I could see in StateMap, ObjectMap, the dictionary is loaded fine. After this step, it jumps into the ArgumentNullException, I could not figure out what is happening next and I suspected the way I use the SerializableMap to serialize the dictionary might be wrong (since it contains my custom class)

Could you provide any hint on this? Really appreciate your help.

Thanks!

idaot
Posts: 202
Joined: 01 Aug 2020 08:25

Re: How to serialize SerializableMap and save to game state

Post by idaot »

Check how state management of a serializable map is handled in UnlockableManager.cs (under SaveServiceStateAsync and LoadServiceStateAsync specifically).

TuTlo
Posts: 13
Joined: 02 Jun 2022 14:40

Re: How to serialize SerializableMap and save to game state

Post by TuTlo »

idaot wrote: 29 Oct 2022 20:11

Check how state management of a serializable map is handled in UnlockableManager.cs (under SaveServiceStateAsync and LoadServiceStateAsync specifically).

Hi,

Thanks for the instruction.

I implement exactly the same as in SaveServiceState and LoadServiceStateAsync, it seems like that doesn't help, still get the same null error.

My implementation is as follow:

My Map extend from SerializableMap

Code: Select all

    [Serializable]
    public class InventoryMap: SerializableMap<Item, int>
    {
        public InventoryMap () : base() { }
        public InventoryMap(InventoryMap map): base (map){ }
    }

GameState, instead of GlobalState, I would like to save to slot

Code: Select all

    [Serializable]
    public class GameState
    {
        public InventoryMap SaveItemDict = new InventoryMap();
    }

SerializeState and DeserializeState

Code: Select all

    
private void SerializeState (GameStateMap stateMap) { var state = new GameState { SaveItemDict = new InventoryMap(itemDict), }; Debug.Log("SerializeState: " + state.SaveItemDict[0]); stateMap.SetState(state); } private UniTask DeserializeState(GameStateMap stateMap) { var state = stateMap.GetState<GameState>(); if (state is null) return UniTask.CompletedTask; itemDict = new InventoryMap(state.SaveItemDict); Display(); return UniTask.CompletedTask; }

During debugging, the error will arise when the game starts, seems like during SerializaState method is called, still get some ArgumentNullExceptions:
Value cannot be null.
Parameter name: key

idaot
Posts: 202
Joined: 01 Aug 2020 08:25

Re: How to serialize SerializableMap and save to game state

Post by idaot »

It sounds like you are triggering the script in the editor. Make sure the InventoryManager fulfills the engine service requirements (https://naninovel.com/guide/engine-serv ... e-services) and check the Inventory extension for reference (https://naninovel.com/guide/inventory.html#inventory)

TuTlo
Posts: 13
Joined: 02 Jun 2022 14:40

Re: How to serialize SerializableMap and save to game state

Post by TuTlo »

hey,

Thanks for the guide! I think I figure out the root cause for this. I did not put [Serializable] when I created my Scriptable Object Item, the [Serializable] was added afterwards. So it leads to the fact that the previous added Item is not able to serialize and result in Null Key error when it got deserialized.

I will also check the engine service requirements accordingly and the Inventory extension as well!

Thanks!

Post Reply