Entitas icon indicating copy to clipboard operation
Entitas copied to clipboard

Save/Load without Blueprints

Open AVAVT opened this issue 7 years ago • 1 comments

I have asked about this problem before, but now that I'm actually implementing it there're several issues. I'm planning to keep this issue opened until I can update a working solution for others who might be searching.

What's not working now

  • [x] Serialize/Hydrate components: I'm using NewtonSoft's JSON.Net together with custom Converter from this SO question
  • [x] Repopulate entities: AddComponent() based on component type
  • [x] ~~Not sure how to save/hydrate ScriptableObject's reference (getting warning for serializing the ScriptableObject itself)~~ actually it's a terrible idea to bind config scriptable object to entity (game update will easily corrupt save data)
  • [ ] Not sure if this way of getting component index is correct:
entity.AddComponent(
  GameComponentsLookup.componentTypes.ToList().IndexOf(component.GetType()),
  component
);

Help is much appreciated!

Here's what I'm doing (an autosave feature) following the idea from this issue. I'm stripping entities from contexts directly because it's a "Save and Exit Game" feature (like Diablo II). For "Save and Continue" I would get entities list from contexts similarly.

EntityData.cs

[System.Serializable]
public class EntityData
{
  public string contextInfo;
  public string components;
  public EntityData(string contextInfo, IEnumerable<IComponent> components)
  {
    this.contextInfo = contextInfo;
    this.components = AltiJsonSerializer.SerializeJson(components);
  }
}

IAutoSaveComponent.cs

public interface IAutoSaveComponent : IComponent { }

GameController.cs

  private static IEnumerable<Type> autoSaveTypes;
  void Awake()
  {
    if (autoSaveTypes == null)
    {
      var type = typeof(IAutoSaveComponent);
      autoSaveTypes = AppDomain.CurrentDomain.GetAssemblies()
          .SelectMany(s => s.GetTypes())
          .Where(p => type.IsAssignableFrom(p));
    }
  }

  void AutoSave()
  {
    StripEntitiesForAutoSave(_contexts);
    SaveContexts(_contexts);
    _contexts.Reset();
  }

  void SaveContexts(Contexts contexts)
  {
    List<EntityData> savedEntities = new List<EntityData>();
    foreach (var context in contexts.allContexts)
    {
      Entity[] entities = (Entity[])context.GetType().GetMethod("GetEntities").Invoke(context, null);
      foreach (GameEntity entity in entities)
      {
        savedEntities.Add(
          new EntityData(
            entity.contextInfo.name,
            entity.GetComponents().Where(component => component is IAutoSaveComponent)
          )
        );
      }
    }

    BinaryFormatter bf = new BinaryFormatter();
    FileStream file = File.Open(Application.persistentDataPath + "/autosave.dat", FileMode.OpenOrCreate);

    bf.Serialize(file, savedEntities);
    file.Close();
  }

  void LoadContexts(Contexts contexts)
  {
    if (File.Exists(Application.persistentDataPath + "/autosave.dat"))
    {
      BinaryFormatter bf = new BinaryFormatter();
      FileStream file = File.Open(Application.persistentDataPath + "/autosave.dat", FileMode.Open);

      List<EntityData> savedEntities = (List<EntityData>)bf.Deserialize(file);
      file.Close();

      Dictionary<string, object> contextMap = new Dictionary<string, object>();
      contextMap.Add(contexts.game.contextInfo.name, contexts.game);

      foreach (EntityData entityData in savedEntities)
      {
        var context = contextMap[entityData.contextInfo];
        Entity entity = (Entity)context.GetType().GetMethod("CreateEntity").Invoke(context, null);

        List<IComponent> components = AltiJsonSerializer.DeserializeJson<List<IComponent>>(
          entityData.components,
          autoSaveTypes
        );

        foreach (IComponent component in components)
        {
          entity.AddComponent(
            GameComponentsLookup.componentTypes.ToList().IndexOf(component.GetType()),
            component
          );
        }
      }
    }
  }

JsonKnownTypeConverter.cs from SO need some change

return typeName.Contains(x.Name + ","); // Instead of "." + x.Name + ","

AVAVT avatar Mar 10 '18 07:03 AVAVT

Posting just to show my support right now. I had a working system using Blueprints for a while, and it worked fine, but with the new Event system it has gotten tied to MonoBehaviours in some cases and I haven't gotten deserialization for those working yet.

ghost avatar Mar 14 '18 20:03 ghost