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: 14
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
support
Posts: 262
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: 14
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
support
Posts: 262
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: 14
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