Boat Attack使用了Universal RP的许多新图形功能,可以用于探索 Universal RP 的使用方式和技巧。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

363 行
18 KiB

using System.Collections.Generic;
using System.IO;
using Unity.Collections;
using Unity.Entities;
using Unity.Entities.Serialization;
using Unity.Entities.Streaming;
using Unity.Mathematics;
using Unity.Profiling;
using UnityEditor;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.SceneManagement;
using UnityEditor.VersionControl;
using static Unity.Entities.GameObjectConversionUtility;
using Hash128 = Unity.Entities.Hash128;
using Object = UnityEngine.Object;
namespace Unity.Scenes.Editor
{
//@TODO: Public
public class EditorEntityScenes
{
static readonly ProfilerMarker k_ProfileEntitiesSceneSave = new ProfilerMarker("EntitiesScene.Save");
static readonly ProfilerMarker k_ProfileEntitiesSceneCreatePrefab = new ProfilerMarker("EntitiesScene.CreatePrefab");
static readonly ProfilerMarker k_ProfileEntitiesSceneSaveHeader = new ProfilerMarker("EntitiesScene.WriteHeader");
public static bool IsEntitySubScene(Scene scene)
{
return scene.isSubScene;
}
public static void WriteEntityScene(SubScene scene)
{
Entities.Hash128 guid = new GUID(AssetDatabase.AssetPathToGUID(scene.EditableScenePath));
WriteEntityScene(scene.LoadedScene, guid, 0);
}
public static bool HasEntitySceneCache(Hash128 sceneGUID)
{
string headerPath = EntityScenesPaths.GetPathAndCreateDirectory(sceneGUID, EntityScenesPaths.PathType.EntitiesHeader, "");
return File.Exists(headerPath);
}
static AABB GetBoundsAndDestroy(EntityManager entityManager, EntityQuery query)
{
var bounds = MinMaxAABB.Empty;
using (var allBounds = query.ToComponentDataArray<SceneBoundingVolume>(Allocator.TempJob))
{
foreach(var b in allBounds)
bounds.Encapsulate(b.Value);
}
entityManager.DestroyEntity(query);
return bounds;
}
public static SceneData[] WriteEntityScene(Scene scene, Hash128 sceneGUID, ConversionFlags conversionFlags)
{
var world = new World("ConversionWorld");
var entityManager = world.EntityManager;
ConvertScene(scene, sceneGUID, world, conversionFlags);
EntitySceneOptimization.Optimize(world);
var sceneSections = new List<SceneData>();
var subSectionList = new List<SceneSection>();
entityManager.GetAllUniqueSharedComponentData(subSectionList);
var extRefInfoEntities = new NativeArray<Entity>(subSectionList.Count, Allocator.Temp);
NativeArray<Entity> entitiesInMainSection;
var sectionQuery = entityManager.CreateEntityQuery(
new EntityQueryDesc
{
All = new[] {ComponentType.ReadWrite<SceneSection>()},
Options = EntityQueryOptions.IncludePrefab | EntityQueryOptions.IncludeDisabled
}
);
var sectionBoundsQuery = entityManager.CreateEntityQuery(
new EntityQueryDesc
{
All = new[] {ComponentType.ReadWrite<SceneBoundingVolume>(), ComponentType.ReadWrite<SceneSection>()},
Options = EntityQueryOptions.IncludePrefab | EntityQueryOptions.IncludeDisabled
}
);
{
var section = new SceneSection {SceneGUID = sceneGUID, Section = 0};
sectionQuery.SetFilter(new SceneSection { SceneGUID = sceneGUID, Section = 0 });
sectionBoundsQuery.SetFilter(new SceneSection { SceneGUID = sceneGUID, Section = 0 });
entitiesInMainSection = sectionQuery.ToEntityArray(Allocator.TempJob);
var bounds = GetBoundsAndDestroy(entityManager, sectionBoundsQuery);
// Each section will be serialized in its own world, entities that don't have a section are part of the main scene.
// An entity that holds the array of external references to the main scene is required for each section.
// We need to create them all before we start moving entities to section scenes,
// otherwise they would reuse entities that have been moved and mess up the remapping tables.
for(int sectionIndex = 1; sectionIndex < subSectionList.Count; ++sectionIndex)
{
if (subSectionList[sectionIndex].Section == 0)
// Main section, the only one that doesn't need an external ref array
continue;
var extRefInfoEntity = entityManager.CreateEntity();
entityManager.AddSharedComponentData(extRefInfoEntity, subSectionList[sectionIndex]);
extRefInfoEntities[sectionIndex] = extRefInfoEntity;
}
// Public references array, only on the main section.
var refInfoEntity = entityManager.CreateEntity();
entityManager.AddBuffer<PublicEntityRef>(refInfoEntity);
entityManager.AddSharedComponentData(refInfoEntity, section);
var publicRefs = entityManager.GetBuffer<PublicEntityRef>(refInfoEntity);
// entityManager.Debug.CheckInternalConsistency();
//@TODO do we need to keep this index? doesn't carry any additional info
for (int i = 0; i < entitiesInMainSection.Length; ++i)
{
PublicEntityRef.Add(ref publicRefs,
new PublicEntityRef {entityIndex = i, targetEntity = entitiesInMainSection[i]});
}
Debug.Assert(publicRefs.Length == entitiesInMainSection.Length);
// Save main section
var sectionWorld = new World("SectionWorld");
var sectionManager = sectionWorld.EntityManager;
var entityRemapping = entityManager.CreateEntityRemapArray(Allocator.TempJob);
sectionManager.MoveEntitiesFrom(entityManager, sectionQuery, entityRemapping);
// The section component is only there to break the conversion world into different sections
// We don't want to store that on the disk
//@TODO: Component should be removed but currently leads to corrupt data file. Figure out why.
//sectionManager.RemoveComponent(sectionManager.UniversalQuery, typeof(SceneSection));
var sectionFileSize = WriteEntityScene(sectionManager, sceneGUID, "0");
sceneSections.Add(new SceneData
{
FileSize = sectionFileSize,
SceneGUID = sceneGUID,
SharedComponentCount = sectionManager.GetSharedComponentCount() - 1,
SubSectionIndex = 0,
BoundingVolume = bounds
});
entityRemapping.Dispose();
sectionWorld.Dispose();
}
{
// Index 0 is the default value of the shared component, not an actual section
for(int subSectionIndex = 0; subSectionIndex < subSectionList.Count; ++subSectionIndex)
{
var subSection = subSectionList[subSectionIndex];
if (subSection.Section == 0)
continue;
sectionQuery.SetFilter(subSection);
sectionBoundsQuery.SetFilter(subSection);
var bounds = GetBoundsAndDestroy(entityManager, sectionBoundsQuery);
var entitiesInSection = sectionQuery.ToEntityArray(Allocator.TempJob);
if (entitiesInSection.Length > 0)
{
// Fetch back the external reference entity we created earlier to not disturb the mapping
var refInfoEntity = extRefInfoEntities[subSectionIndex];
entityManager.AddBuffer<ExternalEntityRef>(refInfoEntity);
var externRefs = entityManager.GetBuffer<ExternalEntityRef>(refInfoEntity);
// Store the mapping to everything in the main section
//@TODO maybe we don't need all that? is this worth worrying about?
for (int i = 0; i < entitiesInMainSection.Length; ++i)
{
ExternalEntityRef.Add(ref externRefs, new ExternalEntityRef{entityIndex = i});
}
var entityRemapping = entityManager.CreateEntityRemapArray(Allocator.TempJob);
// Entities will be remapped to a contiguous range in the section world, but they will
// also come with an unpredictable amount of meta entities. We have the guarantee that
// the entities in the main section won't be moved over, so there's a free range of that
// size at the end of the remapping table. So we use that range for external references.
var externEntityIndexStart = entityRemapping.Length - entitiesInMainSection.Length;
entityManager.AddComponentData(refInfoEntity,
new ExternalEntityRefInfo
{
SceneGUID = sceneGUID,
EntityIndexStart = externEntityIndexStart
});
var sectionWorld = new World("SectionWorld");
var sectionManager = sectionWorld.EntityManager;
// Insert mapping for external references, conversion world entity to virtual index in section
for (int i = 0; i < entitiesInMainSection.Length; ++i)
{
EntityRemapUtility.AddEntityRemapping(ref entityRemapping, entitiesInMainSection[i],
new Entity {Index = i + externEntityIndexStart, Version = 1});
}
sectionManager.MoveEntitiesFrom(entityManager, sectionQuery, entityRemapping);
// Now that all the required entities have been moved over, we can get rid of the gap between
// real entities and external references. This allows remapping during load to deal with a
// smaller remap table, containing only useful entries.
int highestEntityIndexInUse = 0;
for (int i = 0; i < externEntityIndexStart; ++i)
{
var targetIndex = entityRemapping[i].Target.Index;
if (targetIndex < externEntityIndexStart && targetIndex > highestEntityIndexInUse)
highestEntityIndexInUse = targetIndex;
}
var oldExternEntityIndexStart = externEntityIndexStart;
externEntityIndexStart = highestEntityIndexInUse + 1;
sectionManager.SetComponentData
(
EntityRemapUtility.RemapEntity(ref entityRemapping, refInfoEntity),
new ExternalEntityRefInfo
{
SceneGUID = sceneGUID,
EntityIndexStart = externEntityIndexStart
}
);
// When writing the scene, references to missing entities are set to Entity.Null by default
// (but only if they have been used, otherwise they remain untouched)
// We obviously don't want that to happen to our external references, so we add explicit mapping
// And at the same time, we put them back at the end of the effective range of real entities.
for (int i = 0; i < entitiesInMainSection.Length; ++i)
{
var src = new Entity {Index = i + oldExternEntityIndexStart, Version = 1};
var dst = new Entity {Index = i + externEntityIndexStart, Version = 1};
EntityRemapUtility.AddEntityRemapping(ref entityRemapping, src, dst);
}
// The section component is only there to break the conversion world into different sections
// We don't want to store that on the disk
//@TODO: Component should be removed but currently leads to corrupt data file. Figure out why.
//sectionManager.RemoveComponent(sectionManager.UniversalQuery, typeof(SceneSection));
var fileSize = WriteEntityScene(sectionManager, sceneGUID, subSection.Section.ToString(), entityRemapping);
sceneSections.Add(new SceneData
{
FileSize = fileSize,
SceneGUID = sceneGUID,
SharedComponentCount = sectionManager.GetSharedComponentCount() - 1,
SubSectionIndex = subSection.Section,
BoundingVolume = bounds
});
entityRemapping.Dispose();
sectionWorld.Dispose();
}
entitiesInSection.Dispose();
}
}
{
var noSectionQuery = entityManager.CreateEntityQuery(
new EntityQueryDesc
{
None = new[] {ComponentType.ReadWrite<SceneSection>()},
Options = EntityQueryOptions.IncludePrefab | EntityQueryOptions.IncludeDisabled
}
);
if (noSectionQuery.CalculateLength() != 0)
Debug.LogWarning($"{noSectionQuery.CalculateLength()} entities in the scene '{scene.path}' had no SceneSection and as a result were not serialized at all.");
}
sectionQuery.Dispose();
sectionBoundsQuery.Dispose();
entitiesInMainSection.Dispose();
world.Dispose();
// Save the new header
var header = ScriptableObject.CreateInstance<SubSceneHeader>();
header.Sections = sceneSections.ToArray();
WriteHeader(sceneGUID, header);
return sceneSections.ToArray();
}
static int WriteEntityScene(EntityManager scene, Entities.Hash128 sceneGUID, string subsection, NativeArray<EntityRemapUtility.EntityRemapInfo> entityRemapInfos = default(NativeArray<EntityRemapUtility.EntityRemapInfo>))
{
k_ProfileEntitiesSceneSave.Begin();
var entitiesBinaryPath = EntityScenesPaths.GetPathAndCreateDirectory(sceneGUID, EntityScenesPaths.PathType.EntitiesBinary, subsection);
var sharedDataPath = EntityScenesPaths.GetPathAndCreateDirectory(sceneGUID, EntityScenesPaths.PathType.EntitiesSharedComponents, subsection);
GameObject sharedComponents;
// We're going to do file writing manually, so make sure to do version control dance if needed
if (Provider.isActive && !AssetDatabase.IsOpenForEdit(entitiesBinaryPath, StatusQueryOptions.UseCachedIfPossible))
{
var task = Provider.Checkout(entitiesBinaryPath, CheckoutMode.Asset);
task.Wait();
if (!task.success)
throw new System.Exception($"Failed to checkout entity cache file {entitiesBinaryPath}");
}
// Write binary entity file
int entitySceneFileSize = 0;
using (var writer = new StreamBinaryWriter(entitiesBinaryPath))
{
if (entityRemapInfos.IsCreated)
SerializeUtilityHybrid.Serialize(scene, writer, out sharedComponents, entityRemapInfos);
else
SerializeUtilityHybrid.Serialize(scene, writer, out sharedComponents);
entitySceneFileSize = (int)writer.Length;
}
// Write shared component data prefab
k_ProfileEntitiesSceneCreatePrefab.Begin();
//var oldPrefab = AssetDatabase.LoadMainAssetAtPath(sharedDataPath);
//if (oldPrefab == null)
// PrefabUtility.CreatePrefab(sharedDataPath, sharedComponents, ReplacePrefabOptions.ReplaceNameBased);
if(sharedComponents != null)
PrefabUtility.SaveAsPrefabAsset(sharedComponents, sharedDataPath);
//else
// PrefabUtility.Save
//PrefabUtility.ReplacePrefab(sharedComponents, oldPrefab, ReplacePrefabOptions.ReplaceNameBased);
Object.DestroyImmediate(sharedComponents);
k_ProfileEntitiesSceneCreatePrefab.End();
k_ProfileEntitiesSceneSave.End();
return entitySceneFileSize;
}
static void WriteHeader(Entities.Hash128 sceneGUID, SubSceneHeader header)
{
k_ProfileEntitiesSceneSaveHeader.Begin();
string headerPath = EntityScenesPaths.GetPathAndCreateDirectory(sceneGUID, EntityScenesPaths.PathType.EntitiesHeader, "");
AssetDatabase.CreateAsset(header, headerPath);
//subscene.CacheSceneInformation();
k_ProfileEntitiesSceneSaveHeader.End();
}
}
}