Created
July 8, 2021 16:21
-
-
Save ScottJDaley/52f937c266247f221341d3fd4244e737 to your computer and use it in GitHub Desktop.
Save and Load for Unity ECS using SerializeUtility
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Serializable] | |
public struct SaveTag : IComponentData { } | |
[Serializable] | |
public struct MissingRenderMeshTag : IComponentData { } | |
[Serializable] | |
public struct MissingPhysicsColliderTag : IComponentData { } | |
[Serializable] | |
public struct MyBlobComponent : IComponentData | |
{ | |
public struct MyBlobData | |
{ | |
public int Data; | |
} | |
public BlobAssetReference<MyBlobData> Value; | |
} | |
[Serializable] | |
public struct MissingMyBlobComponentTag : IComponentData { } | |
// Each prefab entity will have a unique ID stored in a PrefabID component. All entities instantiated from a given | |
// prefab entity will have the same PrefabID component, but will be missing the Prefab tag component. | |
[Serializable] | |
public struct PrefabID : IComponentData | |
{ | |
public FixedString32 ID; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Stores all prefabs that contain a PrefabID in a hash map keyed by ID that is accessible by other systems. | |
*/ | |
[UpdateAfter(typeof(ConvertToEntitySystem))] | |
[UpdateInGroup(typeof(InitializationSystemGroup))] | |
public class PrefabSystem : SystemBase | |
{ | |
public NativeHashMap<FixedString32, Entity> PrefabsByID; | |
private EntityCommandBufferSystem _commandBufferSystem; | |
private struct PrefabSystemState : ISystemStateComponentData { } | |
protected override void OnCreate() | |
{ | |
PrefabsByID = new NativeHashMap<FixedString32, Entity>(2, Allocator.Persistent); | |
_commandBufferSystem = World.GetOrCreateSystem<EndInitializationEntityCommandBufferSystem>(); | |
} | |
protected override void OnDestroy() | |
{ | |
PrefabsByID.Dispose(); | |
} | |
protected override void OnUpdate() | |
{ | |
NativeHashMap<FixedString32, Entity> prefabs = PrefabsByID; | |
EntityCommandBuffer commandBuffer = _commandBufferSystem.CreateCommandBuffer(); | |
// Add all new prefabs to the prefab hash map. | |
Entities | |
.WithAll<Prefab>() | |
.WithNone<PrefabSystemState>() | |
.ForEach( | |
(Entity entity, in PrefabID prefabID) => | |
{ | |
prefabs.Add(prefabID.ID, entity); | |
commandBuffer.AddComponent<PrefabSystemState>(entity); | |
}).Schedule(); | |
_commandBufferSystem.AddJobHandleForProducer(Dependency); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Looks for any entities that contain the "Missing" component tags and replaces them with the actual components from | |
* the associated prefab for that entity. | |
*/ | |
[UpdateInGroup(typeof(InitializationSystemGroup))] | |
public class RestoreAfterLoadSystem : SystemBase | |
{ | |
private EntityCommandBufferSystem _commandBufferSystem; | |
private PrefabSystem _prefabSystem; | |
protected override void OnCreate() | |
{ | |
_commandBufferSystem = World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>(); | |
_prefabSystem = World.GetOrCreateSystem<PrefabSystem>(); | |
} | |
protected override void OnUpdate() | |
{ | |
EntityCommandBuffer commandBuffer = _commandBufferSystem.CreateCommandBuffer(); | |
NativeHashMap<FixedString32, Entity> prefabs = _prefabSystem.PrefabsByID; | |
Entities | |
.WithAll<MissingRenderMeshTag>() | |
.WithoutBurst() | |
.ForEach( | |
(Entity entity, in PrefabID prefabID) => | |
{ | |
if (!prefabs.TryGetValue(prefabID.ID, out Entity prefab)) | |
{ | |
Debug.LogError($"Could not find prefab with id {prefabID.ID}"); | |
return; | |
} | |
var renderMesh = EntityManager.GetSharedComponentData<RenderMesh>(prefab); | |
var description = new RenderMeshDescription(renderMesh.mesh, renderMesh.material); | |
RenderMeshUtility.AddComponents(entity, commandBuffer, description); | |
commandBuffer.RemoveComponent<MissingRenderMeshTag>(entity); | |
} | |
).Run(); | |
Entities | |
.WithAll<MissingPhysicsColliderTag>() | |
.ForEach( | |
(Entity entity, in PrefabID prefabID) => | |
{ | |
if (!prefabs.TryGetValue(prefabID.ID, out Entity prefab)) | |
{ | |
Debug.LogError($"Could not find prefab with id {prefabID.ID}"); | |
return; | |
} | |
commandBuffer.AddComponent(entity, GetComponent<PhysicsCollider>(prefab)); | |
commandBuffer.RemoveComponent<MissingPhysicsColliderTag>(entity); | |
} | |
).Schedule(); | |
Entities | |
.WithAll<MissingMyBlobComponentTag>() | |
.ForEach( | |
(Entity entity, in PrefabID prefabID) => | |
{ | |
if (!prefabs.TryGetValue(prefabID.ID, out Entity prefab)) | |
{ | |
Debug.LogError($"Could not find prefab with id {prefabID.ID}"); | |
return; | |
} | |
commandBuffer.AddComponent(entity, GetComponent<MyBlobComponent>(prefab)); | |
commandBuffer.RemoveComponent<MissingMyBlobComponentTag>(entity); | |
} | |
).Schedule(); | |
_commandBufferSystem.AddJobHandleForProducer(Dependency); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SaveManager : MonoBehaviour | |
{ | |
private EntityQuery _entitiesToSave; | |
private void Start() | |
{ | |
// Cache a query that gathers all of the entities that should be saved. | |
// NOTE: You don't have to use a special tag component for all entities you want to save. You could instead just | |
// save, for example, anything with a Translation component which would exclude things like Singletons entities. | |
// It is important to note that prefabs (anything with a Prefab tag component) are automatically excluded from | |
// an EntityQuery unless EntityQueryOptions.IncludePrefab is set. | |
var savableEntities = new EntityQueryDesc | |
{ | |
Any = new ComponentType[] | |
{ | |
typeof(SaveTag), | |
}, | |
Options = EntityQueryOptions.Default | |
}; | |
EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; | |
_entitiesToSave = entityManager.CreateEntityQuery(savableEntities); | |
} | |
// Looks for and removes a set of components and then adds a different set of components to the same set | |
// of entities. | |
private void ReplaceComponents( | |
ComponentType[] typesToRemove, | |
ComponentType[] typesToAdd, | |
EntityManager entityManager) | |
{ | |
EntityQuery query = entityManager.CreateEntityQuery( | |
new EntityQueryDesc { Any = typesToRemove, Options = EntityQueryOptions.Default } | |
); | |
NativeArray<Entity> entities = query.ToEntityArray(Allocator.Temp); | |
foreach (ComponentType removeType in typesToRemove) | |
{ | |
entityManager.RemoveComponent(entities, removeType); | |
} | |
foreach (ComponentType addType in typesToAdd) | |
{ | |
entityManager.AddComponent(entities, addType); | |
} | |
} | |
public void Save(string filepath) | |
{ | |
/* | |
* 1. Create a new world. | |
* 2. Copy over the entities we want to serialize to the new world. | |
* 3. Remove all shared components, components containing blob asset references, and components containing | |
* external entity references. | |
* 4. Serialize the new world to a save file. | |
*/ | |
EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; | |
using (var serializeWorld = new World("Serialization World")) | |
{ | |
EntityManager serializeEntityManager = serializeWorld.EntityManager; | |
serializeEntityManager.CopyEntitiesFrom(entityManager, _entitiesToSave.ToEntityArray(Allocator.Temp)); | |
// Remove RenderMesh and related components | |
ReplaceComponents( | |
new ComponentType[] | |
{ | |
typeof(RenderMesh), | |
typeof(EditorRenderData), | |
typeof(WorldRenderBounds), | |
typeof(ChunkWorldRenderBounds), | |
typeof(HybridChunkInfo), | |
typeof(RenderBounds) | |
}, | |
new ComponentType[] { typeof(MissingRenderMeshTag) }, | |
serializeEntityManager | |
); | |
// Remove physics colliders | |
ReplaceComponents( | |
new ComponentType[] | |
{ | |
typeof(PhysicsCollider), | |
}, | |
new ComponentType[] { typeof(MissingPhysicsColliderTag) }, | |
serializeEntityManager | |
); | |
// Remove blob assets. | |
ReplaceComponents( | |
new ComponentType[] | |
{ | |
typeof(MyBlobComponent), | |
}, | |
new ComponentType[] { typeof(MissingMyBlobComponentTag) }, | |
serializeEntityManager | |
); | |
// Need to remove the SceneTag shared component from all entities because it contains an entity reference | |
// that exists outside the subscene which isn't allowed for SerializeUtility. This breaks the link from the | |
// entity to the subscene, but otherwise doesn't seem to cause any problems. | |
serializeEntityManager.RemoveComponent<SceneTag>(serializeEntityManager.UniversalQuery); | |
serializeEntityManager.RemoveComponent<SceneSection>(serializeEntityManager.UniversalQuery); | |
// Save | |
using (var writer = new StreamBinaryWriter(filepath)) | |
{ | |
SerializeUtility.SerializeWorld(serializeEntityManager, writer); | |
} | |
} | |
} | |
public void Load(string filepath) | |
{ | |
EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; | |
entityManager.DestroyEntity(_entitiesToSave); | |
using (var deserializeWorld = new World("Deserialization World")) | |
{ | |
ExclusiveEntityTransaction transaction = deserializeWorld.EntityManager.BeginExclusiveEntityTransaction(); | |
using (var reader = new StreamBinaryReader(filepath)) | |
{ | |
SerializeUtility.DeserializeWorld(transaction, reader); | |
} | |
deserializeWorld.EntityManager.EndExclusiveEntityTransaction(); | |
entityManager.MoveEntitiesFrom(deserializeWorld.EntityManager); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment