mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-02-01 18:21:10 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
1051 lines
42 KiB
C#
1051 lines
42 KiB
C#
using Editor;
|
|
using Sandbox;
|
|
using Sandbox.Internal;
|
|
using System;
|
|
|
|
namespace GameObjects;
|
|
|
|
/// <summary>
|
|
/// Various prefab instance tests. No specific category.
|
|
/// </summary>
|
|
[TestClass]
|
|
public partial class PrefabInstances
|
|
{
|
|
[TestMethod]
|
|
public void WriteBackInstanceToPrefab()
|
|
{
|
|
var saveLocation = "___writeback.prefab";
|
|
|
|
using var prefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( saveLocation, _basicPrefabSource );
|
|
|
|
var pfile = ResourceLibrary.Get<PrefabFile>( saveLocation );
|
|
|
|
var prefabScene = SceneUtility.GetPrefabScene( pfile );
|
|
|
|
Assert.IsTrue( prefabScene.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// spawn in scene
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
Assert.IsNull( scene.Camera );
|
|
Assert.AreEqual( scene.Directory.Count, 0 );
|
|
|
|
var instance = prefabScene.Clone( Vector3.Right * -100 );
|
|
Assert.AreEqual( scene.Directory.Count, 1 );
|
|
Assert.IsTrue( instance.Components.Get<ModelRenderer>() is not null );
|
|
|
|
instance.AddComponent<SkinnedModelRenderer>();
|
|
Assert.IsTrue( instance.Components.Get<SkinnedModelRenderer>() is not null );
|
|
|
|
instance.AddComponent<ModelHitboxes>();
|
|
Assert.IsTrue( instance.Components.Get<ModelHitboxes>() is not null );
|
|
instance.Components.Get<ModelHitboxes>().Renderer = instance.Components.Get<SkinnedModelRenderer>();
|
|
|
|
EditorUtility.Prefabs.WriteInstanceToPrefab( instance, true );
|
|
|
|
Assert.AreEqual( 3, prefabScene.Components.Count );
|
|
Assert.IsTrue( prefabScene.Components.Get<SkinnedModelRenderer>() is not null );
|
|
Assert.AreNotEqual( instance.Components.Get<ModelRenderer>().Id, prefabScene.Components.Get<ModelRenderer>().Id );
|
|
// Added component should also not share an id with the prefab to avoid conflicts
|
|
Assert.AreNotEqual( instance.Components.Get<SkinnedModelRenderer>().Id, prefabScene.Components.Get<SkinnedModelRenderer>().Id );
|
|
|
|
// Reference to added objects should be maintaned, when writing back to prefab
|
|
Assert.AreEqual( instance.Components.Get<ModelHitboxes>().Renderer.Id, instance.Components.Get<SkinnedModelRenderer>().Id );
|
|
Assert.AreNotEqual( instance.Components.Get<ModelHitboxes>().Renderer.Id, prefabScene.Components.Get<SkinnedModelRenderer>().Id );
|
|
Assert.AreEqual( prefabScene.Components.Get<ModelHitboxes>().Renderer.Id, prefabScene.Components.Get<SkinnedModelRenderer>().Id );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void UpstreamChangesFromPrefabToInstance()
|
|
{
|
|
var saveLocation = "___upstream_changes.prefab";
|
|
|
|
// Create our base prefab
|
|
using var prefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( saveLocation, _basicPrefabSource );
|
|
var pfile = ResourceLibrary.Get<PrefabFile>( saveLocation );
|
|
var prefabScene = SceneUtility.GetPrefabScene( pfile );
|
|
|
|
Assert.IsTrue( prefabScene.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// Create an instance in a scene
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
var instance = prefabScene.Clone( Vector3.Zero );
|
|
|
|
// Verify the instance has the expected component from prefab
|
|
Assert.IsTrue( instance.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// Make a local modification to the instance (this should be preserved)
|
|
instance.Components.Get<ModelRenderer>().Tint = Color.Blue;
|
|
// The editor would run this after a modification
|
|
instance.PrefabInstance.RefreshPatch();
|
|
|
|
// Now modify the original prefab by adding a new component
|
|
prefabScene.AddComponent<BoxCollider>();
|
|
Assert.IsTrue( prefabScene.Components.Get<BoxCollider>() is not null );
|
|
|
|
// Save the changes to the prefab file
|
|
prefabScene.ToPrefabFile();
|
|
|
|
// Update the instance from the modified prefab
|
|
instance.UpdateFromPrefab();
|
|
|
|
// Verify that:
|
|
// 1. The instance now has the new component from the prefab
|
|
Assert.IsTrue( instance.Components.Get<BoxCollider>() is not null );
|
|
|
|
// 2. The instance's local modifications are preserved
|
|
Assert.AreEqual( Color.Blue, instance.Components.Get<ModelRenderer>().Tint );
|
|
|
|
// 3. The original prefab's component values remain unchanged
|
|
prefabScene.Load( pfile ); // Refresh to be sure we have the latest data
|
|
Assert.AreEqual( new Color( 1, 0, 0, 1 ), prefabScene.Components.Get<ModelRenderer>().Tint );
|
|
}
|
|
|
|
static readonly string _basicPrefabSource = """"
|
|
{
|
|
"__guid": "fab370f8-2e2c-48cf-a523-e4be49723490",
|
|
"Name": "Object",
|
|
"Position": "788.8395,-1793.604,-1218.092",
|
|
"Scale": "10, 10, 10",
|
|
"Enabled": true,
|
|
"Components": [
|
|
{
|
|
"__type": "ModelRenderer",
|
|
"__guid": "230b45c1-a446-42b4-af39-f7195135e31f",
|
|
"BodyGroups": 18446744073709551615,
|
|
"MaterialGroup": null,
|
|
"MaterialOverride": null,
|
|
"Model": null,
|
|
"RenderType": "On",
|
|
"Tint": "1,0,0,1"
|
|
}
|
|
],
|
|
"Children": []
|
|
}
|
|
|
|
"""";
|
|
|
|
[TestMethod]
|
|
public void UpstreamChangesWithHierarchiesThatContainTheSamePrefabMoreThanOnce_Nested()
|
|
{
|
|
var saveLocation = "___upstream_changes_self_nested.prefab";
|
|
|
|
// Create our base prefab
|
|
using var prefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( saveLocation, _basicPrefabSource );
|
|
var pfile = ResourceLibrary.Get<PrefabFile>( saveLocation );
|
|
var prefabScene = SceneUtility.GetPrefabScene( pfile );
|
|
|
|
Assert.IsTrue( prefabScene.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// Create an instance in a scene
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
var hierarchy = scene.CreateObject();
|
|
hierarchy.Deserialize( Json.ParseToJsonObject( _gameObjectWithNestedPrefabInstance ) );
|
|
|
|
// Verify the instance has the expected component from prefab
|
|
Assert.AreEqual( 1, hierarchy.Children.Count );
|
|
GameObject outerPrefabInstance = hierarchy.Children[0];
|
|
Assert.IsTrue( outerPrefabInstance.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// Make a local modification to the instance (this should be preserved)
|
|
outerPrefabInstance.Components.Get<ModelRenderer>().Tint = Color.Blue;
|
|
// The editor would run this after a modification
|
|
outerPrefabInstance.PrefabInstance.RefreshPatch();
|
|
|
|
// Now modify the original prefab by adding a new component
|
|
prefabScene.AddComponent<BoxCollider>();
|
|
Assert.IsTrue( prefabScene.Components.Get<BoxCollider>() is not null );
|
|
|
|
// Save the changes to the prefab file
|
|
prefabScene.ToPrefabFile();
|
|
|
|
// Update the instance from the modified prefab
|
|
// This should call RefreshCachedPatch with refreshFromUpstream=true internally
|
|
outerPrefabInstance.UpdateFromPrefab();
|
|
|
|
// Verify that:
|
|
// 1. The instance now has the new component from the prefab
|
|
Assert.IsTrue( outerPrefabInstance.Components.Get<BoxCollider>() is not null );
|
|
|
|
// 2. The instance's local modifications are preserved
|
|
Assert.AreEqual( Color.Blue, outerPrefabInstance.Components.Get<ModelRenderer>().Tint );
|
|
|
|
// 3. The original prefab's component values remain unchanged
|
|
prefabScene.Load( pfile ); // Refresh to be sure we have the latest data
|
|
Assert.AreEqual( new Color( 1, 0, 0, 1 ), prefabScene.Components.Get<ModelRenderer>().Tint );
|
|
}
|
|
|
|
static readonly string _gameObjectWithNestedPrefabInstance = """"
|
|
{
|
|
"__guid": "d03c7897-8558-48ee-990f-547b61de7f9a",
|
|
"Name": "Object",
|
|
"Position": "788.8395,-1793.604,-1218.092",
|
|
"Enabled": true,
|
|
"Children": [
|
|
{
|
|
"__guid": "c07c9e05-a64f-4354-a98f-11bbdbec71fa",
|
|
"__version": 1,
|
|
"__Prefab": "___upstream_changes_self_nested.prefab",
|
|
"__PrefabInstancePatch": {
|
|
"AddedObjects": [
|
|
{
|
|
"Id": {
|
|
"Type": "GameObject",
|
|
"IdValue": "e5156285-adb9-4986-bd03-e7899750ffe1"
|
|
},
|
|
"Parent": {
|
|
"Type": "GameObject",
|
|
"IdValue": "fab370f8-2e2c-48cf-a523-e4be49723490"
|
|
},
|
|
"PreviousElement": null,
|
|
"ContainerProperty": "Children",
|
|
"IsContainerArray": true,
|
|
"Data": {
|
|
"__guid": "e5156285-adb9-4986-bd03-e7899750ffe1",
|
|
"__version": 1,
|
|
"__Prefab": "___upstream_changes_self_nested.prefab",
|
|
"__PrefabInstancePatch": {
|
|
"AddedObjects": [
|
|
{
|
|
"Id": {
|
|
"Type": "GameObject",
|
|
"IdValue": "3508f8c0-9cb0-421b-a040-9d340c5611c1"
|
|
},
|
|
"Parent": {
|
|
"Type": "GameObject",
|
|
"IdValue": "fab370f8-2e2c-48cf-a523-e4be49723490"
|
|
},
|
|
"PreviousElement": null,
|
|
"ContainerProperty": "Children",
|
|
"IsContainerArray": true,
|
|
"Data": {
|
|
"__guid": "3508f8c0-9cb0-421b-a040-9d340c5611c1",
|
|
"__version": 1,
|
|
"__Prefab": "___upstream_changes_self_nested.prefab",
|
|
"__PrefabInstancePatch": {
|
|
"AddedObjects": [],
|
|
"RemovedObjects": [],
|
|
"PropertyOverrides": [],
|
|
"MovedObjects": []
|
|
},
|
|
"__PrefabIdToInstanceId": {
|
|
"fab370f8-2e2c-48cf-a523-e4be49723490": "3508f8c0-9cb0-421b-a040-9d340c5611c1",
|
|
"230b45c1-a446-42b4-af39-f7195135e31f": "d2679da4-2b84-4aa5-95ad-32e47a1bbed5"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"RemovedObjects": [],
|
|
"PropertyOverrides": [],
|
|
"MovedObjects": []
|
|
},
|
|
"__PrefabIdToInstanceId": {
|
|
"fab370f8-2e2c-48cf-a523-e4be49723490": "e5156285-adb9-4986-bd03-e7899750ffe1",
|
|
"230b45c1-a446-42b4-af39-f7195135e31f": "65886a24-2a05-41c7-a5dc-da429ea1aeff"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"RemovedObjects": [],
|
|
"PropertyOverrides": [],
|
|
"MovedObjects": []
|
|
},
|
|
"__PrefabIdToInstanceId": {
|
|
"fab370f8-2e2c-48cf-a523-e4be49723490": "c07c9e05-a64f-4354-a98f-11bbdbec71fa",
|
|
"230b45c1-a446-42b4-af39-f7195135e31f": "19e18ce9-aba4-465c-b07b-7e63f5f05cef"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
"""";
|
|
|
|
[TestMethod]
|
|
public void UpstreamChangesWithHierarchiesThatContainTheSamePrefabMoreThanOnce()
|
|
{
|
|
var saveLocation = "___upstream_changes_multiple.prefab";
|
|
|
|
// Create our base prefab
|
|
using var prefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( saveLocation, _basicPrefabSource );
|
|
var pfile = ResourceLibrary.Get<PrefabFile>( saveLocation );
|
|
var prefabScene = SceneUtility.GetPrefabScene( pfile );
|
|
|
|
Assert.IsTrue( prefabScene.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// Create an instance in a scene
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
var hierarchy = scene.CreateObject();
|
|
hierarchy.Deserialize( Json.ParseToJsonObject( _gameObjectWithPrefabInstances ) );
|
|
|
|
// Verify the instance has the expected component from prefab
|
|
Assert.AreEqual( 2, hierarchy.Children.Count );
|
|
|
|
foreach ( var prefabInstance in hierarchy.Children )
|
|
{
|
|
Assert.IsTrue( prefabInstance.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// Make a local modification to the instance (this should be preserved)
|
|
prefabInstance.Components.Get<ModelRenderer>().Tint = Color.Blue;
|
|
// The editor would run this after a modification
|
|
prefabInstance.PrefabInstance.RefreshPatch();
|
|
}
|
|
|
|
// Now modify the original prefab by adding a new go and component
|
|
var newGo = prefabScene.CreateObject();
|
|
newGo.AddComponent<BoxCollider>();
|
|
Assert.IsTrue( newGo.Components.Get<BoxCollider>() is not null );
|
|
|
|
prefabScene.ToPrefabFile();
|
|
|
|
foreach ( var prefabInstance in hierarchy.Children )
|
|
{
|
|
// Update the instance from the modified prefab
|
|
prefabInstance.UpdateFromPrefab();
|
|
|
|
Assert.AreEqual( 1, prefabInstance.Children.Count );
|
|
Assert.IsNotNull( prefabInstance.Children[0].Components.Get<BoxCollider>() );
|
|
|
|
Assert.AreEqual( Color.Blue, prefabInstance.Components.Get<ModelRenderer>().Tint );
|
|
}
|
|
|
|
prefabScene.Load( pfile ); // Refresh to be sure we have the latest data
|
|
Assert.AreEqual( new Color( 1, 0, 0, 1 ), prefabScene.Components.Get<ModelRenderer>().Tint );
|
|
}
|
|
|
|
static readonly string _gameObjectWithPrefabInstances = """"
|
|
{
|
|
"__guid": "d03c7897-8558-48ee-990f-547b61de7f9a",
|
|
"Name": "Object",
|
|
"Position": "788.8395,-1793.604,-1218.092",
|
|
"Enabled": true,
|
|
"Children": [
|
|
{
|
|
"__guid": "c07c9e05-a64f-4354-a98f-11bbdbec71fa",
|
|
"__version": 1,
|
|
"__Prefab": "___upstream_changes_multiple.prefab",
|
|
"__PrefabInstancePatch": {
|
|
"AddedObjects": [],
|
|
"RemovedObjects": [],
|
|
"PropertyOverrides": [],
|
|
"MovedObjects": []
|
|
},
|
|
"__PrefabIdToInstanceId": {
|
|
"fab370f8-2e2c-48cf-a523-e4be49723490": "c07c9e05-a64f-4354-a98f-11bbdbec71fa",
|
|
"230b45c1-a446-42b4-af39-f7195135e31f": "19e18ce9-aba4-465c-b07b-7e63f5f05cef"
|
|
}
|
|
},
|
|
{
|
|
"__guid": "3508f8c0-9cb0-421b-a040-9d340c5611c1",
|
|
"__version": 1,
|
|
"__Prefab": "___upstream_changes_multiple.prefab",
|
|
"__PrefabInstancePatch": {
|
|
"AddedObjects": [],
|
|
"RemovedObjects": [],
|
|
"PropertyOverrides": [],
|
|
"MovedObjects": []
|
|
},
|
|
"__PrefabIdToInstanceId": {
|
|
"fab370f8-2e2c-48cf-a523-e4be49723490": "3508f8c0-9cb0-421b-a040-9d340c5611c1",
|
|
"230b45c1-a446-42b4-af39-f7195135e31f": "d2679da4-2b84-4aa5-95ad-32e47a1bbed5"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
"""";
|
|
|
|
[TestMethod]
|
|
public void SelfNestedPrefabInstanceWithAddedGameObjectInNestedInstanceShouldCopyCorrectly()
|
|
{
|
|
var saveLocation = "___upstream_changes_self_nested.prefab";
|
|
|
|
// Create our base prefab
|
|
using var prefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( saveLocation, _basicPrefabSource );
|
|
var pfile = ResourceLibrary.Get<PrefabFile>( saveLocation );
|
|
var prefabScene = SceneUtility.GetPrefabScene( pfile );
|
|
|
|
Assert.IsTrue( prefabScene.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// Create an instance in a scene
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
var hierarchy = scene.CreateObject();
|
|
hierarchy.Deserialize( Json.ParseToJsonObject( _gameObjectWithNestedPrefabInstance ) );
|
|
|
|
// Add a new gameobject to the nested prefab instance
|
|
var nestedPrefabInstance = hierarchy.Children[0].Children[0];
|
|
|
|
Assert.IsNotNull( nestedPrefabInstance.Components.Get<ModelRenderer>() );
|
|
Assert.AreEqual( 1, nestedPrefabInstance.Children.Count );
|
|
|
|
var addedObject = new GameObject( nestedPrefabInstance );
|
|
|
|
// Editor would make sure this is called
|
|
nestedPrefabInstance.PrefabInstance.RefreshPatch();
|
|
|
|
// Verify that the added object is now part of the prefab instance
|
|
Assert.AreEqual( 2, nestedPrefabInstance.Children.Count );
|
|
|
|
// Emulate copy paste
|
|
var s = hierarchy.Serialize();
|
|
var newHierarchy = scene.CreateObject();
|
|
SceneUtility.MakeIdGuidsUnique( s );
|
|
newHierarchy.Deserialize( s );
|
|
|
|
// Verify that the new hierarchy has the same structure
|
|
Assert.AreEqual( 1, newHierarchy.Children.Count );
|
|
var newNestedPrefabInstance = newHierarchy.Children[0].Children[0];
|
|
Assert.AreEqual( 2, newNestedPrefabInstance.Children.Count );
|
|
Assert.IsNotNull( newNestedPrefabInstance.Components.Get<ModelRenderer>() );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SelfNestedPrefabInstanceWithReferenceToOuterInstanceInNestedInstanceShouldCopyCorrectly()
|
|
{
|
|
var saveLocation = "___upstream_changes_self_nested.prefab";
|
|
|
|
// Create our base prefab
|
|
using var prefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( saveLocation, _basicPrefabSource );
|
|
var pfile = ResourceLibrary.Get<PrefabFile>( saveLocation );
|
|
var prefabScene = SceneUtility.GetPrefabScene( pfile );
|
|
|
|
Assert.IsTrue( prefabScene.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// Create an instance in a scene
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
var hierarchy = scene.CreateObject();
|
|
hierarchy.Deserialize( Json.ParseToJsonObject( _gameObjectWithNestedPrefabInstance ) );
|
|
|
|
// Add a new gameobject to the nested prefab instance
|
|
var nestedPrefabInstance = hierarchy.Children[0].Children[0];
|
|
|
|
Assert.IsNotNull( nestedPrefabInstance.Components.Get<ModelRenderer>() );
|
|
Assert.AreEqual( 1, nestedPrefabInstance.Children.Count );
|
|
|
|
var lineRenderer = nestedPrefabInstance.AddComponent<LineRenderer>();
|
|
lineRenderer.Points = [hierarchy.Children[0]];
|
|
|
|
// Editor would make sure this is called
|
|
nestedPrefabInstance.PrefabInstance.RefreshPatch();
|
|
|
|
// Emulate copy paste
|
|
var s = hierarchy.Serialize();
|
|
var newHierarchy = scene.CreateObject();
|
|
SceneUtility.MakeIdGuidsUnique( s );
|
|
newHierarchy.Deserialize( s );
|
|
|
|
// Verify that the copied line renderer is referencing the outer instance of the new hierarchy
|
|
var newNestedPrefabInstance = newHierarchy.Children[0].Children[0];
|
|
Assert.AreEqual( 1, newNestedPrefabInstance.Children.Count );
|
|
Assert.IsNotNull( newNestedPrefabInstance.Components.Get<LineRenderer>() );
|
|
|
|
var newLineRenderer = newNestedPrefabInstance.Components.Get<LineRenderer>();
|
|
Assert.AreEqual( 1, newLineRenderer.Points.Count );
|
|
Assert.AreEqual( newLineRenderer.Points[0], newHierarchy.Children[0] );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void UpdatesToNestedPrefabFileShouldPropagateToOuterPrefabFile()
|
|
{
|
|
var nestedPrefabLocation = "__nestedPrefab.prefab";
|
|
var outerPrefabLocation = "__outerPrefab.prefab";
|
|
|
|
// Step 1: Create the nested prefab
|
|
using var nestedPrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( nestedPrefabLocation, _basicPrefabSource );
|
|
var nestedPrefabFile = ResourceLibrary.Get<PrefabFile>( nestedPrefabLocation );
|
|
var nestedPrefabScene = SceneUtility.GetPrefabScene( nestedPrefabFile );
|
|
|
|
Assert.IsTrue( nestedPrefabScene.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// Step 2: Create the outer prefab containing the nested prefab
|
|
using var outerPrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( outerPrefabLocation, _outerPrefabWithNestedPrefabSource );
|
|
var outerPrefabFile = ResourceLibrary.Get<PrefabFile>( outerPrefabLocation );
|
|
var outerPrefabScene = SceneUtility.GetPrefabScene( outerPrefabFile );
|
|
|
|
// Verify the outer prefab structure and its nested prefab instance
|
|
Assert.AreEqual( 1, outerPrefabScene.Children.Count );
|
|
var nestedPrefabInstance = outerPrefabScene.Children[0];
|
|
Assert.IsTrue( nestedPrefabInstance.PrefabInstance != null );
|
|
Assert.IsTrue( nestedPrefabInstance.Components.Get<ModelRenderer>() is not null );
|
|
|
|
// Step 3: Modify the nested prefab by adding a new component
|
|
nestedPrefabScene.AddComponent<BoxCollider>();
|
|
Assert.IsTrue( nestedPrefabScene.Components.Get<BoxCollider>() is not null );
|
|
|
|
// Step 4: Save the changes to the nested prefab file
|
|
nestedPrefabScene.ToPrefabFile();
|
|
|
|
// Step 5: Verify that the changes in the nested prefab are reflected in the outer prefab
|
|
// The nested prefab instance should now have the BoxCollider
|
|
Assert.AreEqual( 1, outerPrefabScene.Children.Count );
|
|
var updatedNestedPrefabInstance = outerPrefabScene.Children[0];
|
|
|
|
// This is the key assertion - the BoxCollider from the nested prefab should now appear
|
|
// in the instance within the outer prefab
|
|
Assert.IsTrue( updatedNestedPrefabInstance.Components.Get<BoxCollider>() is not null );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void OutermostPrefabInstanceRoot_FromNestedPrefabShouldReturnCorrectRoot()
|
|
{
|
|
// Create the nested structure:
|
|
// - Inner prefab (basic prefab)
|
|
// - Outer prefab (contains an instance of inner prefab)
|
|
|
|
var innerPrefabLocation = "__nestedPrefab.prefab";
|
|
var outerPrefabLocation = "__outer_prefab.prefab";
|
|
|
|
// Step 1: Create the inner prefab
|
|
using var innerPrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( innerPrefabLocation, _basicPrefabSource );
|
|
var innerPrefabFile = ResourceLibrary.Get<PrefabFile>( innerPrefabLocation );
|
|
var innerPrefabScene = SceneUtility.GetPrefabScene( innerPrefabFile );
|
|
|
|
// Step 2: Create the outer prefab containing the inner prefab
|
|
using var outerPrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( outerPrefabLocation, _outerPrefabWithNestedPrefabSource );
|
|
var outerPrefabFile = ResourceLibrary.Get<PrefabFile>( outerPrefabLocation );
|
|
var outerPrefabScene = SceneUtility.GetPrefabScene( outerPrefabFile );
|
|
|
|
// Step 3: Create a scene and instantiate the outer prefab
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
var outerInstance = outerPrefabScene.Clone( Vector3.Zero );
|
|
Assert.IsTrue( outerInstance.IsPrefabInstanceRoot );
|
|
|
|
// Step 4: Get the inner prefab instance within the outer prefab
|
|
var innerInstance = outerInstance.Children[0];
|
|
Assert.IsTrue( innerInstance.IsPrefabInstanceRoot );
|
|
|
|
// Get a component inside the inner instance to test from
|
|
var modelRenderer = innerInstance.Components.Get<ModelRenderer>();
|
|
Assert.IsNotNull( modelRenderer );
|
|
|
|
// Step 5: Test OutermostPrefabInstanceRoot from different levels
|
|
|
|
// Test from innerInstance - should return outerInstance
|
|
Assert.AreEqual( outerInstance, innerInstance.OutermostPrefabInstanceRoot );
|
|
|
|
// Test from outerInstance - should return itself
|
|
Assert.AreEqual( outerInstance, outerInstance.OutermostPrefabInstanceRoot );
|
|
|
|
// Test IsOutermostPrefabInstanceRoot property
|
|
Assert.IsTrue( outerInstance.IsOutermostPrefabInstanceRoot );
|
|
Assert.IsFalse( innerInstance.IsOutermostPrefabInstanceRoot );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void OutermostPrefabInstanceRoot_RuntimeAddedPrefabShouldReturnItself()
|
|
{
|
|
// Step 1: Create two prefabs
|
|
var prefabALocation = "__prefab_a.prefab";
|
|
var prefabBLocation = "__prefab_b.prefab";
|
|
|
|
using var prefabA = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( prefabALocation, _basicPrefabSource );
|
|
var prefabAFile = ResourceLibrary.Get<PrefabFile>( prefabALocation );
|
|
var prefabAScene = SceneUtility.GetPrefabScene( prefabAFile );
|
|
|
|
// Create prefab B with a different tint color to distinguish it
|
|
string prefabBSource = _basicPrefabSource.Replace( "1,0,0,1", "0,1,0,1" );
|
|
using var prefabB = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( prefabBLocation, prefabBSource );
|
|
var prefabBFile = ResourceLibrary.Get<PrefabFile>( prefabBLocation );
|
|
var prefabBScene = SceneUtility.GetPrefabScene( prefabBFile );
|
|
|
|
// Step 2: Create a scene and instantiate prefab A
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
|
|
var instanceA = prefabAScene.Clone( Vector3.Zero );
|
|
Assert.IsTrue( instanceA.IsPrefabInstanceRoot );
|
|
Assert.IsTrue( instanceA.IsOutermostPrefabInstanceRoot );
|
|
|
|
// Step 3: At runtime, instantiate prefab B and parent it to instanceA
|
|
var instanceB = prefabBScene.Clone( Vector3.Zero );
|
|
instanceB.SetParent( instanceA );
|
|
|
|
// Editor would run this
|
|
instanceA.PrefabInstance.RefreshPatch();
|
|
|
|
// Even though instanceB is now a child of instanceA,
|
|
// it should still be its own outermost prefab instance root
|
|
// because it was added at runtime and not part of the original prefab
|
|
|
|
// Test from instanceB
|
|
Assert.AreEqual( instanceB, instanceB.OutermostPrefabInstanceRoot );
|
|
Assert.IsTrue( instanceB.IsOutermostPrefabInstanceRoot );
|
|
|
|
// Step 4: Create a new GameObject inside instanceB
|
|
var childOfInstanceB = scene.CreateObject();
|
|
childOfInstanceB.SetParent( instanceB );
|
|
|
|
// This child is part of instanceB, so its outermost prefab instance root should be instanceB
|
|
Assert.AreEqual( instanceB, childOfInstanceB.OutermostPrefabInstanceRoot );
|
|
|
|
// Step 5: Create a new GameObject inside instanceA (but not part of instanceB)
|
|
var childOfInstanceA = scene.CreateObject();
|
|
childOfInstanceA.SetParent( instanceA );
|
|
|
|
// This child is part of instanceA, so its outermost prefab instance root should be instanceA
|
|
Assert.AreEqual( instanceA, childOfInstanceA.OutermostPrefabInstanceRoot );
|
|
|
|
// Verify the hierarchy
|
|
Assert.AreEqual( 2, instanceA.Children.Count ); // instanceB and childOfInstanceA
|
|
Assert.AreEqual( 1, instanceB.Children.Count ); // childOfInstanceB
|
|
}
|
|
|
|
static readonly string _outerPrefabWithNestedPrefabSource = """"
|
|
{
|
|
"__guid": "16a942f3-8b7c-4b6e-a14b-5854d568e256",
|
|
"Name": "OuterPrefab",
|
|
"Position": "0,0,0",
|
|
"Enabled": true,
|
|
"Components": [
|
|
{
|
|
"__type": "ModelRenderer",
|
|
"__guid": "b34c25d6-22cd-4e4a-9fb4-71c12dce2efd",
|
|
"BodyGroups": 18446744073709551615,
|
|
"MaterialGroup": null,
|
|
"MaterialOverride": null,
|
|
"Model": null,
|
|
"RenderType": "On",
|
|
"Tint": "0,1,0,1"
|
|
}
|
|
],
|
|
"Children": [
|
|
{
|
|
"__guid": "f1482e7a-a10c-4c5b-b0fa-3dc07ef5f7e9",
|
|
"__version": 1,
|
|
"__Prefab": "__nestedPrefab.prefab",
|
|
"__PrefabInstancePatch": {
|
|
"AddedObjects": [],
|
|
"RemovedObjects": [],
|
|
"PropertyOverrides": [],
|
|
"MovedObjects": []
|
|
},
|
|
"__PrefabIdToInstanceId": {
|
|
"fab370f8-2e2c-48cf-a523-e4be49723490": "f1482e7a-a10c-4c5b-b0fa-3dc07ef5f7e9",
|
|
"230b45c1-a446-42b4-af39-f7195135e31f": "aa721c3b-9d6c-48d5-81a9-f72ef5c5b12e"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
"""";
|
|
|
|
private void TestOverriddenPropertyOnPrefabInstance(
|
|
string saveLocation,
|
|
string prefabSource,
|
|
Action<GameObject> assertInitialState,
|
|
Action<GameObject> setOverrideAction,
|
|
Action<GameObject> assertOverrideSet,
|
|
Action<GameObject> assertOverridePreserved
|
|
)
|
|
{
|
|
// Create the prefab from JSON source
|
|
using var prefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( saveLocation, prefabSource );
|
|
var pfile = ResourceLibrary.Get<PrefabFile>( saveLocation );
|
|
var prefabScene = SceneUtility.GetPrefabScene( pfile );
|
|
|
|
// Assert initial state
|
|
assertInitialState( prefabScene );
|
|
|
|
// Create a new scene and instantiate the prefab
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
var instance = prefabScene.Clone( Vector3.Zero );
|
|
|
|
// Override the property in the instance
|
|
setOverrideAction( instance );
|
|
|
|
// Assert that the override was set
|
|
assertOverrideSet( instance );
|
|
|
|
// Simulate the editor updating the patch after modification
|
|
instance.PrefabInstance.RefreshPatch();
|
|
|
|
// Ensure that when updating from the prefab the override is preserved
|
|
instance.UpdateFromPrefab();
|
|
|
|
// Assert that the override was preserved
|
|
assertOverridePreserved( instance );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void OverriddenEnabledStateOnPrefabInstance()
|
|
{
|
|
TestOverriddenPropertyOnPrefabInstance(
|
|
"___prefab_enabled_override.prefab",
|
|
_basicPrefabSource,
|
|
prefab => Assert.IsTrue( prefab.Enabled, "Prefab should be enabled initially." ),
|
|
instance => instance.Enabled = false,
|
|
instance => Assert.IsFalse( instance.Enabled, "Prefab instance Enabled should be overridden to false." ),
|
|
instance => Assert.IsFalse( instance.Enabled, "Prefab instance override should be preserved after UpdateFromPrefab." )
|
|
);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void OverriddenLocalTransformOnPrefabInstance()
|
|
{
|
|
var newTransform = new Transform( new Vector3( 1, 2, 3 ), Rotation.FromYaw( 45 ), new Vector3( 1, 2, 3 ) );
|
|
TestOverriddenPropertyOnPrefabInstance(
|
|
"___prefab_localtransform_override.prefab",
|
|
_basicPrefabSource,
|
|
prefab => Assert.AreEqual( new Transform( new Vector3( 788.8395f, -1793.604f, -1218.092f ), Rotation.Identity, new Vector3( 10, 10, 10 ) ), prefab.LocalTransform, "Prefab should have default LocalTransform." ),
|
|
instance => instance.LocalTransform = newTransform,
|
|
instance => Assert.AreEqual( newTransform, instance.LocalTransform, "Prefab instance LocalTransform should be overridden to new value." ),
|
|
instance => Assert.AreEqual( newTransform, instance.LocalTransform, "Prefab instance override should be preserved after UpdateFromPrefab." )
|
|
);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void OverriddenTagsOnPrefabInstance()
|
|
{
|
|
TestOverriddenPropertyOnPrefabInstance(
|
|
"___prefab_tags_override.prefab",
|
|
_basicPrefabSource,
|
|
prefab => Assert.IsFalse( prefab.Tags.Has( "TestTag" ), "Prefab should not contain 'TestTag' initially." ),
|
|
instance => instance.Tags.Add( "TestTag" ),
|
|
instance => Assert.IsTrue( instance.Tags.Has( "TestTag" ), "Prefab instance should contain 'TestTag' after override." ),
|
|
instance => Assert.IsTrue( instance.Tags.Has( "TestTag" ), "Prefab instance override of Tags should be preserved after UpdateFromPrefab." )
|
|
);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RevertInstanceToPrefab_DoesNotRevertTransformNameFlagsOnRoot()
|
|
{
|
|
var saveLocation = "___revert_instance_test.prefab";
|
|
|
|
// Create our base prefab
|
|
using var prefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( saveLocation, _basicPrefabSource );
|
|
var pfile = ResourceLibrary.Get<PrefabFile>( saveLocation );
|
|
var prefabScene = SceneUtility.GetPrefabScene( pfile );
|
|
|
|
// Verify initial prefab state
|
|
Assert.AreEqual( "Object", prefabScene.Name );
|
|
Assert.AreEqual( new Transform( new Vector3( 788.8395f, -1793.604f, -1218.092f ), Rotation.Identity, new Vector3( 10, 10, 10 ) ), prefabScene.LocalTransform );
|
|
Assert.IsTrue( prefabScene.Enabled );
|
|
|
|
// Create a scene and instantiate the prefab
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
var instance = prefabScene.Clone( Vector3.Zero );
|
|
|
|
// Modify instance's transform, name, and flags
|
|
instance.LocalTransform = new Transform( new Vector3( 100, 200, 300 ), Rotation.FromYaw( 45 ), Vector3.One );
|
|
instance.Name = "ModifiedInstance";
|
|
instance.Enabled = false;
|
|
instance.Flags = GameObjectFlags.DontDestroyOnLoad;
|
|
|
|
// Modify a component property
|
|
instance.Components.Get<ModelRenderer>( true ).Tint = Color.Blue;
|
|
|
|
// The editor would run this after a modification
|
|
instance.PrefabInstance.RefreshPatch();
|
|
|
|
// Revert instance to prefab
|
|
EditorUtility.Prefabs.RevertInstanceToPrefab( instance );
|
|
|
|
// Assert that transform, name, and flags are not reverted
|
|
Assert.AreEqual( new Transform( new Vector3( 100, 200, 300 ), Rotation.FromYaw( 45 ), Vector3.One ), instance.LocalTransform );
|
|
Assert.AreEqual( "ModifiedInstance", instance.Name );
|
|
Assert.IsFalse( instance.Enabled );
|
|
Assert.IsTrue( instance.Flags.HasFlag( GameObjectFlags.DontDestroyOnLoad ) );
|
|
|
|
// Assert that component overrides are reverted
|
|
Assert.AreEqual( new Color( 1, 0, 0, 1 ), instance.Components.Get<ModelRenderer>( true ).Tint );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void NestedPrefabInstanceRefreshDeserialization()
|
|
{
|
|
// Setup prefab file paths
|
|
var nestedNestedPrefabLocation = "__nested_nested.prefab";
|
|
var nestedPrefabLocation = "__nested.prefab";
|
|
var basePrefabLocation = "__base.prefab";
|
|
|
|
// Step 1: Create the innermost prefab
|
|
using var nestedNestedPrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( nestedNestedPrefabLocation, _nestedNestedPrefabSource );
|
|
var nestedNestedPrefabFile = ResourceLibrary.Get<PrefabFile>( nestedNestedPrefabLocation );
|
|
var nestedNestedPrefabScene = SceneUtility.GetPrefabScene( nestedNestedPrefabFile );
|
|
|
|
// Step 2: Create the middle prefab that contains an instance of the innermost prefab
|
|
using var nestedPrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( nestedPrefabLocation, _nestedPrefabSource );
|
|
var nestedPrefabFile = ResourceLibrary.Get<PrefabFile>( nestedPrefabLocation );
|
|
var nestedPrefabScene = SceneUtility.GetPrefabScene( nestedPrefabFile );
|
|
|
|
// Step 3: Create the outermost prefab that contains an instance of the middle prefab
|
|
using var basePrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( basePrefabLocation, _basePrefabSource );
|
|
var basePrefabFile = ResourceLibrary.Get<PrefabFile>( basePrefabLocation );
|
|
var basePrefabScene = SceneUtility.GetPrefabScene( basePrefabFile );
|
|
|
|
// Step 4: Create a test scene and instantiate the outermost prefab
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
var baseInstance = basePrefabScene.Clone( Vector3.Zero );
|
|
|
|
// Step 5: Verify the initial structure is correct
|
|
Assert.IsTrue( baseInstance.IsPrefabInstanceRoot );
|
|
Assert.AreEqual( "BasePrefab", baseInstance.Name );
|
|
Assert.IsNotNull( baseInstance.Components.Get<ModelRenderer>() );
|
|
|
|
Assert.AreEqual( 1, baseInstance.Children.Count );
|
|
var nestedInstanceInScene = baseInstance.Children[0];
|
|
Assert.IsTrue( nestedInstanceInScene.IsPrefabInstanceRoot );
|
|
Assert.AreEqual( "NestedPrefab", nestedInstanceInScene.Name );
|
|
Assert.IsNotNull( nestedInstanceInScene.Components.Get<BoxCollider>() );
|
|
|
|
Assert.AreEqual( 1, nestedInstanceInScene.Children.Count );
|
|
var nestedNestedInstanceInScene = nestedInstanceInScene.Children[0];
|
|
Assert.IsTrue( nestedNestedInstanceInScene.IsPrefabInstanceRoot );
|
|
Assert.AreEqual( "NestedNestedPrefab", nestedNestedInstanceInScene.Name );
|
|
Assert.IsNotNull( nestedNestedInstanceInScene.Components.Get<NavMeshArea>() );
|
|
|
|
// Step 6: Serialize and then deserialize the nested_nested instance
|
|
var serializedData = baseInstance.Serialize();
|
|
|
|
// Store some IDs for comparison after refresh
|
|
var originalId = nestedNestedInstanceInScene.Id;
|
|
var originalComponentId = nestedNestedInstanceInScene.Components.Get<NavMeshArea>().Id;
|
|
var originalPrefabSource = nestedNestedInstanceInScene.PrefabInstance.PrefabSource;
|
|
|
|
// Refresh through deserialization
|
|
baseInstance.Deserialize( serializedData, new GameObject.DeserializeOptions { IsRefreshing = true } );
|
|
|
|
// Step 7: Assert that the instance maintains proper identity and prefab relationships
|
|
Assert.AreEqual( originalId, nestedNestedInstanceInScene.Id, "GameObject ID should be preserved after refresh" );
|
|
Assert.AreEqual( originalComponentId, nestedNestedInstanceInScene.Components.Get<NavMeshArea>().Id, "Component ID should be preserved after refresh" );
|
|
|
|
// Verify the prefab instance relationships are still intact
|
|
Assert.IsTrue( nestedNestedInstanceInScene.IsPrefabInstanceRoot, "Should still be a prefab instance root after refresh" );
|
|
Assert.IsTrue( nestedNestedInstanceInScene.IsNestedPrefabInstanceRoot, "Should still be a nested prefab instance root after refresh" );
|
|
Assert.IsNotNull( nestedNestedInstanceInScene.PrefabInstance, "Should still have prefab instance data after refresh" );
|
|
Assert.AreEqual( originalPrefabSource, nestedNestedInstanceInScene.PrefabInstance.PrefabSource, "Prefab source should be preserved" );
|
|
|
|
// Verify the full hierarchy is still intact and properly connected
|
|
Assert.AreEqual( nestedInstanceInScene, nestedNestedInstanceInScene.Parent, "Parent relationship should be preserved" );
|
|
Assert.IsTrue( nestedInstanceInScene.IsPrefabInstanceRoot, "Should still be a prefab instance root after refresh" );
|
|
Assert.IsTrue( nestedInstanceInScene.IsNestedPrefabInstanceRoot, "Should still be a nested prefab instance root after refresh" );
|
|
Assert.IsTrue( nestedInstanceInScene.Children.Contains( nestedNestedInstanceInScene ), "Child relationship should be preserved" );
|
|
|
|
// Verify the outermost prefab instance root reference is still correct
|
|
Assert.AreEqual( baseInstance, nestedNestedInstanceInScene.OutermostPrefabInstanceRoot, "Outermost prefab root reference should be correct" );
|
|
}
|
|
|
|
[TestMethod]
|
|
public void BreakFromPrefabConvertsNestedInstancesToFullPrefabInstances()
|
|
{
|
|
// 1. Create the nested prefab (innermost)
|
|
var innerPrefabLocation = "__nestedPrefab.prefab";
|
|
using var innerPrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( innerPrefabLocation, _basicPrefabSource );
|
|
var innerPrefabFile = ResourceLibrary.Get<PrefabFile>( innerPrefabLocation );
|
|
var innerPrefabScene = SceneUtility.GetPrefabScene( innerPrefabFile );
|
|
|
|
// 2. Create the outer prefab that contains an instance of the nested prefab
|
|
var outerPrefabLocation = "__outer_for_break.prefab";
|
|
using var outerPrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( outerPrefabLocation, _outerPrefabWithNestedPrefabSource );
|
|
var outerPrefabFile = ResourceLibrary.Get<PrefabFile>( outerPrefabLocation );
|
|
var outerPrefabScene = SceneUtility.GetPrefabScene( outerPrefabFile );
|
|
|
|
// 3. Create a scene and instantiate the outer prefab
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
var outerInstance = outerPrefabScene.Clone( Vector3.Zero );
|
|
|
|
// Verify initial state
|
|
Assert.IsTrue( outerInstance.IsPrefabInstanceRoot, "Outer instance should be a prefab instance root" );
|
|
Assert.AreEqual( 1, outerInstance.Children.Count, "Outer instance should have one child" );
|
|
|
|
var nestedInstance = outerInstance.Children[0];
|
|
Assert.IsTrue( nestedInstance.IsPrefabInstanceRoot, "Nested instance should be a prefab instance root" );
|
|
Assert.IsTrue( nestedInstance.IsNestedPrefabInstanceRoot, "Nested instance should be a nested prefab instance root" );
|
|
Assert.IsFalse( nestedInstance.IsOutermostPrefabInstanceRoot, "Nested instance should not be an outermost prefab instance root" );
|
|
Assert.AreEqual( outerInstance, nestedInstance.OutermostPrefabInstanceRoot, "Outer instance should be the outermost prefab instance root" );
|
|
|
|
// 4. Break the outer instance from its prefab
|
|
outerInstance.BreakFromPrefab();
|
|
|
|
// 5. Verify that outer instance is no longer a prefab instance
|
|
Assert.IsFalse( outerInstance.IsPrefabInstanceRoot, "Outer instance should no longer be a prefab instance root after breaking" );
|
|
Assert.IsFalse( outerInstance.IsPrefabInstance, "Outer instance should no longer be a prefab instance after breaking" );
|
|
|
|
// 6. Verify that nested instance is still a prefab instance but now a full instance
|
|
Assert.IsTrue( nestedInstance.IsPrefabInstanceRoot, "Nested instance should still be a prefab instance root after breaking parent" );
|
|
Assert.IsTrue( nestedInstance.IsPrefabInstance, "Nested instance should still be a prefab instance after breaking parent" );
|
|
Assert.IsFalse( nestedInstance.IsNestedPrefabInstanceRoot, "Nested instance should no longer be a nested prefab instance root" );
|
|
Assert.IsTrue( nestedInstance.IsOutermostPrefabInstanceRoot, "Nested instance should now be an outermost prefab instance root" );
|
|
Assert.AreEqual( nestedInstance, nestedInstance.OutermostPrefabInstanceRoot, "Nested instance should be its own outermost prefab instance root" );
|
|
|
|
// 7. Verify that the nested instance can still be updated from its prefab
|
|
// First, modify the original prefab by adding a new component
|
|
innerPrefabScene.AddComponent<BoxCollider>();
|
|
innerPrefabScene.ToPrefabFile();
|
|
|
|
// Update the nested instance and verify it received the changes
|
|
nestedInstance.UpdateFromPrefab();
|
|
Assert.IsTrue( nestedInstance.Components.Get<BoxCollider>() is not null,
|
|
"Nested instance should receive updates from its prefab after parent instance broke from prefab" );
|
|
}
|
|
|
|
// https://github.com/Facepunch/sbox-issues/issues/9194
|
|
[TestMethod]
|
|
public void WriteGameObjectToPrefab_WithNestedInstance()
|
|
{
|
|
// 1. Setup base prefab
|
|
var basePrefabLocation = "__writeback_nested_base.prefab";
|
|
using var basePrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( basePrefabLocation, _basicPrefabSource );
|
|
var basePrefabFile = ResourceLibrary.Get<PrefabFile>( basePrefabLocation );
|
|
var basePrefabScene = SceneUtility.GetPrefabScene( basePrefabFile );
|
|
|
|
// 2. Create another prefab to use as nested
|
|
var nestedPrefabLocation = "__writeback_nested_inner.prefab";
|
|
string nestedPrefabSource = _basicPrefabSource.Replace( "1,0,0,1", "0,1,0,1" ); // Change color to green
|
|
using var nestedPrefab = Sandbox.SceneTests.Helpers.RegisterPrefabFromJson( nestedPrefabLocation, nestedPrefabSource );
|
|
var nestedPrefabFile = ResourceLibrary.Get<PrefabFile>( nestedPrefabLocation );
|
|
var nestedPrefabScene = SceneUtility.GetPrefabScene( nestedPrefabFile );
|
|
|
|
// 3. Create a scene and instantiate the base prefab
|
|
var scene = new Scene();
|
|
using var sceneScope = scene.Push();
|
|
var baseInstance = basePrefabScene.Clone( Vector3.Zero );
|
|
|
|
// Verify initial state
|
|
Assert.IsTrue( baseInstance.IsPrefabInstanceRoot );
|
|
Assert.AreEqual( 0, baseInstance.Children.Count );
|
|
|
|
// 4. Add a nested prefab instance to the base instance
|
|
var nestedInstance = nestedPrefabScene.Clone( Vector3.Zero );
|
|
nestedInstance.SetParent( baseInstance );
|
|
|
|
var nestedOriginalId = nestedInstance.Id;
|
|
|
|
// The editor would call this after modification
|
|
baseInstance.PrefabInstance.RefreshPatch();
|
|
|
|
// Verify the nested instance was added
|
|
Assert.AreEqual( 1, baseInstance.Children.Count );
|
|
Assert.IsTrue( baseInstance.Children[0].IsPrefabInstanceRoot );
|
|
Assert.IsTrue( baseInstance.PrefabInstance.IsAddedGameObject( nestedInstance ) );
|
|
Assert.IsTrue( baseInstance.PrefabInstance.IsModified() );
|
|
|
|
// 5. Write the modified instance back to its prefab
|
|
EditorUtility.Prefabs.WriteInstanceToPrefab( baseInstance, true );
|
|
|
|
// Need to call this manually since we skip file write
|
|
EditorScene.UpdatePrefabInstances( basePrefabFile );
|
|
|
|
// 6. Verify that the prefab now contains the nested instance
|
|
basePrefabScene = SceneUtility.GetPrefabScene( basePrefabFile );
|
|
Assert.AreEqual( 1, basePrefabScene.Children.Count );
|
|
Assert.IsTrue( basePrefabScene.Children[0].IsPrefabInstanceRoot );
|
|
|
|
// 7. Verify that the instance patch is now empty since it matches the prefab
|
|
Assert.IsFalse( baseInstance.PrefabInstance.IsModified() );
|
|
Assert.IsFalse( baseInstance.PrefabInstance.IsAddedGameObject( nestedInstance ) );
|
|
|
|
// 8. Verify id mapping is intact
|
|
Assert.IsTrue( baseInstance.PrefabInstance.InstanceToPrefabLookup.ContainsKey( nestedOriginalId ) );
|
|
}
|
|
|
|
// Innermost prefab definition
|
|
static readonly string _nestedNestedPrefabSource = """"
|
|
{
|
|
"__guid": "1e26fa20-684e-44af-8a52-30df3a45ea28",
|
|
"Name": "NestedNestedPrefab",
|
|
"Position": "0,0,0",
|
|
"Scale": "1,1,1",
|
|
"Enabled": true,
|
|
"Components": [
|
|
{
|
|
"__type": "NavMeshArea",
|
|
"__guid": "ccefdfc7-aa08-444d-b5db-b2433aeb235e",
|
|
"Bounds": "64,64,64",
|
|
"Enabled": true
|
|
}
|
|
],
|
|
"Children": []
|
|
}
|
|
"""";
|
|
|
|
// Middle prefab definition with instance of innermost prefab
|
|
static readonly string _nestedPrefabSource = """"
|
|
{
|
|
"__guid": "def12345-6789-0abc-def1-23456789abcd",
|
|
"Name": "NestedPrefab",
|
|
"Position": "0,0,0",
|
|
"Scale": "1,1,1",
|
|
"Enabled": true,
|
|
"Components": [
|
|
{
|
|
"__type": "BoxCollider",
|
|
"__guid": "98765432-fedc-ba98-7654-321012345678",
|
|
"Size": "50,50,50",
|
|
"IsTrigger": false,
|
|
"Enabled": true
|
|
}
|
|
],
|
|
"Children": [
|
|
{
|
|
"__guid": "4515d718-070a-4de3-bbda-9835c6e118c9",
|
|
"__version": 1,
|
|
"__Prefab": "__nested_nested.prefab",
|
|
"__PrefabInstancePatch": {
|
|
"AddedObjects": [],
|
|
"RemovedObjects": [],
|
|
"PropertyOverrides": [],
|
|
"MovedObjects": []
|
|
},
|
|
"__PrefabIdToInstanceId": {
|
|
"1e26fa20-684e-44af-8a52-30df3a45ea28": "4515d718-070a-4de3-bbda-9835c6e118c9",
|
|
"ccefdfc7-aa08-444d-b5db-b2433aeb235e": "3bfd4ead-0d39-4ad1-8707-e45f47045776"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
"""";
|
|
|
|
// Outermost prefab definition with instance of middle prefab
|
|
static readonly string _basePrefabSource = """"
|
|
{
|
|
"__guid": "01234567-89ab-cdef-0123-456789abcdef",
|
|
"Name": "BasePrefab",
|
|
"Position": "0,0,0",
|
|
"Scale": "1,1,1",
|
|
"Enabled": true,
|
|
"Components": [
|
|
{
|
|
"__type": "ModelRenderer",
|
|
"__guid": "fedcba98-7654-3210-fedc-ba9876543210",
|
|
"BodyGroups": 18446744073709551615,
|
|
"MaterialGroup": null,
|
|
"MaterialOverride": null,
|
|
"Model": null,
|
|
"RenderType": "On",
|
|
"Tint": "1,0,0,1"
|
|
}
|
|
],
|
|
"Children": [
|
|
{
|
|
"__guid": "2fe84d07-0e73-48f7-a8c8-e6d90c58082d",
|
|
"__version": 1,
|
|
"__Prefab": "__nested.prefab",
|
|
"__PrefabInstancePatch": {
|
|
"AddedObjects": [],
|
|
"RemovedObjects": [],
|
|
"PropertyOverrides": [],
|
|
"MovedObjects": []
|
|
},
|
|
"__PrefabIdToInstanceId": {
|
|
"def12345-6789-0abc-def1-23456789abcd": "2fe84d07-0e73-48f7-a8c8-e6d90c58082d",
|
|
"4515d718-070a-4de3-bbda-9835c6e118c9": "a5c72647-dc84-4557-b8fd-112e8b16b3b5",
|
|
"3bfd4ead-0d39-4ad1-8707-e45f47045776": "34567890-abcd-ef12-3456-789012345678",
|
|
"98765432-fedc-ba98-7654-321012345678": "abcdef01-2345-6789-abcd-ef0123456789"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
"""";
|
|
}
|