Oh man that was a big facepalm. I was so focused on pulling my hair out with this issue that I didn't even consider that as a possibility...
I'm sure there is a plausible explanation for it (performance perhaps) but Unity really should offer a standard solution to serialize dictionaries, I would guess that it is quite a common thing to do.
Anyway, found this thread that helped a lot. Used the SerializableDictionary class from OP and it worked great, plus since it essentially mimics a default Dictionary it fits nicely with my already implementation of the dict. Keep in mind that OP's solution is not the most efficient (mainly due to the serialization of all boilerplate variables) and there are some free assets that offer a similar solution, in my case, unless I run into some issues, i'll keep it, it's simple to implement and there is no need to refactor my code to accommodate the new class, however if anyone want's to do something similar and is concerned about performance, check the store.
Also had some trouble serializing the Lists as values, turns out that we need to subclass them. This is the base ideia of the implementation.
Code: Select all
[System.Serializable]
public class ContactsDict : SerializableDictionary<string, ContactMessages> { }
[System.Serializable]
public class ContactMessages
{
public List<ConversationMessage.State> msgs;
public ContactMessages()
{
msgs = new List<ConversationMessage.State>();
}
}
And here's my final ConversationUI.cs file:
Code: Select all
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Naninovel;
using Naninovel.UI;
using NaninovelPhone.UI;
using UniRx.Async;
using UnityEngine;
using UnityEngine.UI;
[System.Serializable]
public class ContactsDict : SerializableDictionary<string, ContactMessages> { }
[System.Serializable]
public class ContactMessages
{
public List<ConversationMessage.State> msgs;
public ContactMessages()
{
msgs = new List<ConversationMessage.State>();
}
}
namespace NaninovelPhone.UI
{
public class ConversationUI : CustomUI
{
[System.Serializable]
public new class GameState
{
public ContactsDict Messages;
}
protected bool StripTags => stripTags;
[SerializeField] private RectTransform messagesContainer = default;
[SerializeField] private ScrollRect scrollRect = default;
[SerializeField] private ConversationMessage contactMessagePrefab = default;
[SerializeField] private ConversationMessage playerMessagePrefab = default;
[Tooltip("Whether to strip formatting content (content inside `<` `>` and the angle brackets themselves) from the added messages.")]
[SerializeField] private bool stripTags = true;
private ICharacterManager charManager;
private PhoneManager phoneManager;
private IStateManager stateManager;
private ContactsDict messages;
private static readonly Regex formattingRegex = new Regex(@"<.*?>");
protected override void Awake()
{
base.Awake();
this.AssertRequiredObjects(messagesContainer, scrollRect, contactMessagePrefab, playerMessagePrefab);
charManager = Engine.GetService<ICharacterManager>();
phoneManager = Engine.GetService<PhoneManager>();
stateManager = Engine.GetService<IStateManager>();
messages = new ContactsDict();
}
public void AddMessage(string message, string actorId, string contactId)
{
if (StripTags) message = StripFormatting(message);
var messageInstance = SpawnMessage(message, actorId);
var contactName = charManager.GetDisplayName(contactId);
ContactMessages smsList;
if (!messages.TryGetValue(contactName, out smsList))
{
smsList = new ContactMessages();
messages[contactName] = smsList;
}
smsList.msgs.Add(messageInstance.GetState());
PopulateContainer(contactId);
stateManager.PushRollbackSnapshot();
}
public void Clear()
{
ObjectUtils.DestroyAllChilds(messagesContainer);
messages.Clear();
}
protected virtual ConversationMessage SpawnMessage (string messageText, string authorId)
{
var message = default(ConversationMessage);
var actorNameText = charManager.GetDisplayName(authorId);
var avatarImage = phoneManager.GetMobileAvatar(authorId);
if (authorId == "Player")
{
message = Instantiate(playerMessagePrefab);
}
else
{
message = Instantiate(contactMessagePrefab);
}
message.transform.SetParent(messagesContainer.transform, false);
message.AuthorId = authorId;
message.InitializeMessage(messageText, actorNameText, avatarImage);
return message;
}
public void PopulateContainer(string contactId)
{
ObjectUtils.DestroyAllChilds(messagesContainer);
if(!messages.ContainsKey(contactId)) return;
var contactMessages = messages[contactId].msgs;
foreach (var message in contactMessages)
{
SpawnMessage(message.PrintedText, message.AuthorId);
}
}
protected virtual string StripFormatting (string content) => formattingRegex.Replace(content, string.Empty);
protected override void SerializeState(GameStateMap stateMap)
{
base.SerializeState(stateMap);
var state = new GameState() {
Messages = messages
};
stateMap.SetState(state);
}
protected override async UniTask DeserializeState(GameStateMap stateMap)
{
await base.DeserializeState(stateMap);
Clear();
var state = stateMap.GetState<GameState>();
if (state is null) return;
messages = state.Messages;
}
}
}