/// <summary> /// Draws two concentric arcs, depending on the <paramref name="thickness"/> parameter. /// </summary> /// <param name="center">Position of the arc center</param> /// <param name="radius">Radius of the outer arc.</param> /// <param name="arc">Length of the arc in range [0, 360] degrees.</param> /// <param name="thickness">Determines radius of the inner arc, in range [0, 1]. 0 specifies the inner arc is /// the same radius as the outer arc, while 1 specifies the inner arc has zero radius.</param> /// <param name="color">Color to draw the arc with.</param> private static void DrawArcWithThickness(Vector3 center, float radius, Radian arc, float thickness, Color color) { Gizmos.Color = color; if (arc.Degrees > 0.0f) { Gizmos.DrawWireArc(center, -Vector3.ZAxis, radius, new Degree(0.0f), arc); if (thickness > 0.0f) { float innerRadius = radius * (1.0f - MathEx.Clamp01(thickness)); if (thickness < 1.0f) { Gizmos.Color = color * new Color(0.5f, 0.5f, 0.5f); Gizmos.DrawWireArc(center, -Vector3.ZAxis, innerRadius, new Degree(0.0f), arc); } Gizmos.Color = color * new Color(0.25f, 0.25f, 0.25f); for (Radian x = new Radian(0.0f); x < arc; x += MathEx.HalfPi) { Vector3 dir = new Vector3(MathEx.Cos(x), MathEx.Sin(x), 0.0f); Gizmos.DrawLine(center + dir * innerRadius, center + dir * radius); } if (!MathEx.ApproxEquals(arc.Degrees % 90.0f, 0.0f)) { Vector3 dir = new Vector3(MathEx.Cos(arc), MathEx.Sin(arc), 0.0f); Gizmos.DrawLine(center + dir * innerRadius, center + dir * radius); } } } }
/// <summary> /// Initializes the quaternion so that it orients an object so it faces in te provided direction. /// </summary> /// <param name="forward">Direction to orient the object towards.</param> /// <param name="up">Axis that determines the upward direction of the object.</param> public void SetLookRotation(Vector3 forward, Vector3 up) { Vector3 forwardNrm = Vector3.Normalize(forward); Vector3 upNrm = Vector3.Normalize(up); if (MathEx.ApproxEquals(Vector3.Dot(forwardNrm, upNrm), 1.0f)) { SetLookRotation(forwardNrm); return; } Vector3 x = Vector3.Cross(forwardNrm, upNrm); Vector3 y = Vector3.Cross(x, forwardNrm); x.Normalize(); y.Normalize(); this = Quaternion.FromAxes(x, y, -forwardNrm); }
/// <summary> /// Draws a border between two arcs. /// </summary> /// <param name="topRadius">Radius of the top arc.</param> /// <param name="baseRadius">Radius of the base arc.</param> /// <param name="arc">Length of the arc in range [0, 360] degrees.</param> /// <param name="length">Distance between the two arcs.</param> private static void DrawConeBorder(float topRadius, float baseRadius, Radian arc, float length) { for (Radian x = new Radian(0.0f); x < arc; x += MathEx.HalfPi) { Vector3 dir = new Vector3(MathEx.Cos(x), MathEx.Sin(x), 0.0f); Vector3 a = dir * baseRadius + new Vector3(0.0f, 0.0f, -length); Vector3 b = dir * topRadius; Gizmos.DrawLine(a, b); } if (!MathEx.ApproxEquals(arc.Degrees % 90.0f, 0.0f)) { Vector3 dir = new Vector3(MathEx.Cos(arc), MathEx.Sin(arc), 0.0f); Vector3 a = dir * baseRadius + new Vector3(0.0f, 0.0f, -length); Vector3 b = dir * topRadius; Gizmos.DrawLine(a, b); } }
/// <summary> /// Adds a new keyframe to the animation curve, unless a keyframe with the same time already exists in which case /// the existing keyframe is updated with the new value. Newly added keyframe will use the automatic tangent mode. /// </summary> /// <param name="time">Time at which to add/update the keyframe.</param> /// <param name="value">Value of the keyframe.</param> internal void AddOrUpdateKeyframe(float time, float value) { int keyframeIdx = -1; for (int i = 0; i < keyFrames.Length; i++) { if (MathEx.ApproxEquals(keyFrames[i].time, time)) { keyframeIdx = i; break; } } if (keyframeIdx != -1) { UpdateKeyframe(keyframeIdx, time, value); } else { AddKeyframe(time, value); } }
/// <summary> /// Recalculates tangents for all keyframes using the keyframe values and set tangent modes. /// </summary> private void UpdateTangents() { if (keyFrames.Length == 0) { return; } if (keyFrames.Length == 1) { keyFrames[0].inTangent = 0.0f; keyFrames[0].outTangent = 0.0f; return; } // First keyframe { KeyFrame keyThis = keyFrames[0]; KeyFrame keyNext = keyFrames[1]; keyThis.inTangent = 0.0f; TangentMode tangentMode = tangentModes[0]; if (tangentMode == TangentMode.Auto || tangentMode.HasFlag(TangentMode.OutAuto) || tangentMode.HasFlag(TangentMode.OutLinear)) { float diff = keyNext.time - keyThis.time; if (!MathEx.ApproxEquals(diff, 0.0f)) { keyThis.outTangent = (keyNext.value - keyThis.value) / diff; } else { keyThis.outTangent = float.PositiveInfinity; } } else if (tangentMode.HasFlag(TangentMode.OutStep)) { keyThis.outTangent = float.PositiveInfinity; } keyFrames[0] = keyThis; } // Inner keyframes for (int i = 1; i < keyFrames.Length - 1; i++) { KeyFrame keyPrev = keyFrames[i - 1]; KeyFrame keyThis = keyFrames[i]; KeyFrame keyNext = keyFrames[i + 1]; keyThis.inTangent = 0.0f; TangentMode tangentMode = tangentModes[i]; if (tangentMode == TangentMode.Auto) // Both automatic { float diff = keyNext.time - keyPrev.time; if (!MathEx.ApproxEquals(diff, 0.0f)) { keyThis.outTangent = (keyNext.value - keyPrev.value) / diff; } else { keyThis.outTangent = float.PositiveInfinity; } keyThis.inTangent = keyThis.outTangent; } else if (tangentMode == TangentMode.Free) // Both free { keyThis.inTangent = keyThis.outTangent; } else // Different per-tangent modes { // In tangent if (tangentMode.HasFlag(TangentMode.InAuto)) { float diff = keyNext.time - keyPrev.time; if (!MathEx.ApproxEquals(diff, 0.0f)) { keyThis.inTangent = (keyNext.value - keyPrev.value) / diff; } else { keyThis.inTangent = float.PositiveInfinity; } } else if (tangentMode.HasFlag(TangentMode.InLinear)) { float diff = keyThis.time - keyPrev.time; if (!MathEx.ApproxEquals(diff, 0.0f)) { keyThis.inTangent = (keyThis.value - keyPrev.value) / diff; } else { keyThis.inTangent = float.PositiveInfinity; } } else if (tangentMode.HasFlag(TangentMode.InStep)) { keyThis.inTangent = float.PositiveInfinity; } // Out tangent if (tangentMode.HasFlag(TangentMode.OutAuto)) { float diff = keyNext.time - keyPrev.time; if (!MathEx.ApproxEquals(diff, 0.0f)) { keyThis.outTangent = (keyNext.value - keyPrev.value) / diff; } else { keyThis.outTangent = float.PositiveInfinity; } } else if (tangentMode.HasFlag(TangentMode.OutLinear)) { float diff = keyNext.time - keyThis.time; if (!MathEx.ApproxEquals(diff, 0.0f)) { keyThis.outTangent = (keyNext.value - keyThis.value) / diff; } else { keyThis.outTangent = float.PositiveInfinity; } } else if (tangentMode.HasFlag(TangentMode.OutStep)) { keyThis.outTangent = float.PositiveInfinity; } } keyFrames[i] = keyThis; } // Last keyframe { KeyFrame keyThis = keyFrames[keyFrames.Length - 1]; KeyFrame keyPrev = keyFrames[keyFrames.Length - 2]; keyThis.outTangent = 0.0f; TangentMode tangentMode = tangentModes[tangentModes.Length - 1]; if (tangentMode == TangentMode.Auto || tangentMode.HasFlag(TangentMode.InAuto) || tangentMode.HasFlag(TangentMode.InLinear)) { float diff = keyThis.time - keyPrev.time; if (!MathEx.ApproxEquals(diff, 0.0f)) { keyThis.inTangent = (keyThis.value - keyPrev.value) / diff; } else { keyThis.inTangent = float.PositiveInfinity; } } else if (tangentMode.HasFlag(TangentMode.InStep)) { keyThis.inTangent = float.PositiveInfinity; } keyFrames[keyFrames.Length - 1] = keyThis; } }
/// <summary> /// Draws the area between two curves using the provided color. /// </summary> /// <param name="curves">Curves to draw within the currently set range.</param> /// <param name="color">Color to draw the area with.</param> private void DrawCurveRange(EdAnimationCurve[] curves, Color color) { float range = GetRange(true); if (curves.Length != 2 || curves[0] == null || curves[1] == null) { return; } KeyFrame[][] keyframes = { curves[0].KeyFrames, curves[1].KeyFrames }; if (keyframes[0].Length <= 0 || keyframes[1].Length <= 0) { return; } int numSamples = (drawableWidth + LINE_SPLIT_WIDTH - 1) / LINE_SPLIT_WIDTH; float timePerSample = range / numSamples; float time = rangeOffset; float lengthPerPixel = rangeLength / drawableWidth; time -= lengthPerPixel * PADDING; int[] keyframeIndices = { 0, 0 }; // Find first valid keyframe indices for (int curveIdx = 0; curveIdx < 2; curveIdx++) { keyframeIndices[curveIdx] = keyframes[curveIdx].Length; for (int i = 0; i < keyframes[curveIdx].Length; i++) { if (keyframes[curveIdx][i].time > time) { keyframeIndices[curveIdx] = i; } } } List <float> times = new List <float>(); List <float>[] points = { new List <float>(), new List <float>() }; // Determine start points for (int curveIdx = 0; curveIdx < 2; curveIdx++) { float value = curves[curveIdx].Evaluate(time, false); points[curveIdx].Add(value); } times.Add(time); float rangeEnd = rangeOffset + range; while (time < rangeEnd) { float nextTime = time + timePerSample; bool hasStep = false; // Determine time to sample at. Use fixed increments unless there's a step keyframe within our increment in // which case we use its time so we can evaluate it directly for (int curveIdx = 0; curveIdx < 2; curveIdx++) { int keyframeIdx = keyframeIndices[curveIdx]; if (keyframeIdx < keyframes[curveIdx].Length) { KeyFrame keyframe = keyframes[curveIdx][keyframeIdx]; bool isStep = keyframe.inTangent == float.PositiveInfinity || keyframe.outTangent == float.PositiveInfinity; if (isStep && keyframe.time <= nextTime) { nextTime = Math.Min(nextTime, keyframe.time); hasStep = true; } } } // Evaluate if (hasStep) { for (int curveIdx = 0; curveIdx < 2; curveIdx++) { int keyframeIdx = keyframeIndices[curveIdx]; if (keyframeIdx < keyframes[curveIdx].Length) { KeyFrame keyframe = keyframes[curveIdx][keyframeIdx]; if (MathEx.ApproxEquals(keyframe.time, nextTime)) { if (keyframeIdx > 0) { KeyFrame prevKeyframe = keyframes[curveIdx][keyframeIdx - 1]; points[curveIdx].Add(prevKeyframe.value); } else { points[curveIdx].Add(keyframe.value); } points[curveIdx].Add(keyframe.value); } else { // The other curve has step but this one doesn't, we just insert the same value twice float value = curves[curveIdx].Evaluate(nextTime, false); points[curveIdx].Add(value); points[curveIdx].Add(value); } times.Add(nextTime); times.Add(nextTime); } } } else { for (int curveIdx = 0; curveIdx < 2; curveIdx++) { points[curveIdx].Add(curves[curveIdx].Evaluate(nextTime, false)); } times.Add(nextTime); } // Advance keyframe indices for (int curveIdx = 0; curveIdx < 2; curveIdx++) { int keyframeIdx = keyframeIndices[curveIdx]; while (keyframeIdx < keyframes[curveIdx].Length) { KeyFrame keyframe = keyframes[curveIdx][keyframeIdx]; if (keyframe.time > nextTime) { break; } keyframeIdx = ++keyframeIndices[curveIdx]; } } time = nextTime; } // End points for (int curveIdx = 0; curveIdx < 2; curveIdx++) { float value = curves[curveIdx].Evaluate(rangeEnd, false); points[curveIdx].Add(value); } times.Add(rangeEnd); int numQuads = times.Count - 1; List <Vector2I> vertices = new List <Vector2I>(); for (int i = 0; i < numQuads; i++) { int idxLeft = points[0][i] < points[1][i] ? 0 : 1; int idxRight = points[0][i + 1] < points[1][i + 1] ? 0 : 1; Vector2[] left = { new Vector2(times[i], points[0][i]), new Vector2(times[i], points[1][i]) }; Vector2[] right = { new Vector2(times[i + 1], points[0][i + 1]), new Vector2(times[i + 1], points[1][i + 1]) }; if (idxLeft == idxRight) { int idxA = idxLeft; int idxB = (idxLeft + 1) % 2; vertices.Add(CurveToPixelSpace(left[idxB])); vertices.Add(CurveToPixelSpace(right[idxB])); vertices.Add(CurveToPixelSpace(left[idxA])); vertices.Add(CurveToPixelSpace(right[idxB])); vertices.Add(CurveToPixelSpace(right[idxA])); vertices.Add(CurveToPixelSpace(left[idxA])); } // Lines intersects, can't represent them with a single quad else if (idxLeft != idxRight) { int idxA = idxLeft; int idxB = (idxLeft + 1) % 2; Line2 lineA = new Line2(left[idxB], right[idxA] - left[idxB]); Line2 lineB = new Line2(left[idxA], right[idxB] - left[idxA]); if (lineA.Intersects(lineB, out var t)) { Vector2 intersection = left[idxB] + t * (right[idxA] - left[idxB]); vertices.Add(CurveToPixelSpace(left[idxB])); vertices.Add(CurveToPixelSpace(intersection)); vertices.Add(CurveToPixelSpace(left[idxA])); vertices.Add(CurveToPixelSpace(intersection)); vertices.Add(CurveToPixelSpace(right[idxB])); vertices.Add(CurveToPixelSpace(right[idxA])); } } } canvas.DrawTriangleList(vertices.ToArray(), color, 129); }
/// <summary> /// Tests saving, loading and updating of prefabs. /// </summary> private static void UnitTest4_Prefabs() { if (!EditorApplication.IsProjectLoaded) { Debug.LogWarning("Skipping unit test as no project is loaded."); return; } if (EditorApplication.IsSceneModified()) { Debug.LogWarning("Cannot perform unit test as the current scene is modified."); return; } Action PrintSceneState = () => { SceneObject root = Scene.Root; Stack <SceneObject> todo = new Stack <SceneObject>(); todo.Push(root); StringBuilder output = new StringBuilder(); while (todo.Count > 0) { SceneObject so = todo.Pop(); int numChildren = so.GetNumChildren(); for (int i = numChildren - 1; i >= 0; i--) { SceneObject child = so.GetChild(i); output.AppendLine(child.Name); todo.Push(child); } } Debug.Log(output); }; // Disabled because it's a slow test, enable only when relevant (or when a build machine is set up) return; string oldScene = Scene.ActiveSceneUUID; Scene.Clear(); try { // Simple scene save & load { { // unitTest4Scene_0.prefab: // so0 (Comp1) // - so0_0 // - so0_1 (Comp1) // - so0_1_0 (Comp1) // so1 (Comp2) // - so1_0 SceneObject so0 = new SceneObject("so0"); SceneObject so1 = new SceneObject("so1"); SceneObject so0_0 = new SceneObject("so0_0"); SceneObject so0_1 = new SceneObject("so0_1"); SceneObject so1_0 = new SceneObject("so1_0"); SceneObject so0_1_0 = new SceneObject("so0_1_0"); so0_0.Parent = so0; so0_1.Parent = so0; so1_0.Parent = so1; so0_1_0.Parent = so0_1; so0_1_0.LocalPosition = new Vector3(10.0f, 15.0f, 20.0f); so0_1.LocalPosition = new Vector3(1.0f, 2.0f, 3.0f); so1_0.LocalPosition = new Vector3(0, 123.0f, 0.0f); UT1_Component1 comp0 = so0.AddComponent <UT1_Component1>(); UT1_Component2 comp1 = so1.AddComponent <UT1_Component2>(); UT1_Component1 comp1_1 = so0_1.AddComponent <UT1_Component1>(); UT1_Component1 comp0_1_0 = so0_1_0.AddComponent <UT1_Component1>(); comp0.otherSO = so0_1_0; comp0.otherComponent = comp1; comp1_1.b = "originalValue2"; comp0_1_0.b = "testValue"; comp0_1_0.otherSO = so0; comp0_1_0.otherComponent2 = comp0; EditorApplication.SaveScene("unitTest4Scene_0.prefab"); } { EditorApplication.LoadScene("unitTest4Scene_0.prefab"); SceneObject sceneRoot = Scene.Root; SceneObject so0 = sceneRoot.FindChild("so0", false); SceneObject so1 = sceneRoot.FindChild("so1", false); SceneObject so0_0 = so0.FindChild("so0_0", false); SceneObject so0_1 = so0.FindChild("so0_1", false); SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false); Assert(so0_0 != null); Assert(so0_1 != null); Assert(so0_1_0 != null); UT1_Component1 comp0 = so0.GetComponent <UT1_Component1>(); UT1_Component2 comp1 = so1.GetComponent <UT1_Component2>(); UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>(); Assert(comp0 != null); Assert(comp1 != null); Assert(comp0_1_0 != null); Assert(comp0_1_0.b == "testValue"); Assert(comp0.otherSO == so0_1_0); Assert(comp0.otherComponent == comp1); Assert(comp0_1_0.otherSO == so0); Assert(comp0_1_0.otherComponent2 == comp0); } } Debug.Log("Passed stage 1"); // Load & save a scene that contains a prefab and references its objects { { // unitTest4Scene_1.prefab: // parentSO0 // - [unitTest4Scene_0.prefab] // parentSO1 // - parentSO1_0 (Comp1) Scene.Clear(); SceneObject parentSO0 = new SceneObject("parentSO0", false); SceneObject parentSO1 = new SceneObject("parentSO1", false); SceneObject parentSO1_0 = new SceneObject("parentSO1_0", false); parentSO1_0.Parent = parentSO1; parentSO0.LocalPosition = new Vector3(50.0f, 50.0f, 50.0f); UT1_Component1 parentComp1_0 = parentSO1_0.AddComponent <UT1_Component1>(); Prefab scene0Prefab = ProjectLibrary.Load <Prefab>("unitTest4Scene_0.prefab"); SceneObject prefabInstance = scene0Prefab.Instantiate(); prefabInstance.Parent = parentSO0; prefabInstance.LocalPosition = Vector3.Zero; SceneObject so0 = prefabInstance.FindChild("so0", false); SceneObject so1 = prefabInstance.FindChild("so1", false); SceneObject so0_1 = so0.FindChild("so0_1", false); SceneObject so1_0 = so1.FindChild("so1_0", false); SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false); UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>(); parentComp1_0.otherSO = so1_0; parentComp1_0.otherComponent2 = comp0_1_0; EditorApplication.SaveScene("unitTest4Scene_1.prefab"); } { EditorApplication.LoadScene("unitTest4Scene_1.prefab"); SceneObject parentSO0 = Scene.Root.FindChild("parentSO0", false); SceneObject parentSO1 = Scene.Root.FindChild("parentSO1", false); SceneObject parentSO1_0 = parentSO1.FindChild("parentSO1_0", false); UT1_Component1 parentComp1_0 = parentSO1_0.GetComponent <UT1_Component1>(); SceneObject prefabInstance = parentSO0.GetChild(0); SceneObject so0 = prefabInstance.FindChild("so0", false); SceneObject so1 = prefabInstance.FindChild("so1", false); SceneObject so0_1 = so0.FindChild("so0_1", false); SceneObject so1_0 = so1.FindChild("so1_0", false); SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false); UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>(); Assert(parentComp1_0.otherSO == so1_0); Assert(parentComp1_0.otherComponent2 == comp0_1_0); } } Debug.Log("Passed stage 2"); // Modify prefab, reload the scene and ensure it is updated with modified prefab { { // unitTest4Scene_0.prefab: // so0 // - so0_1 (Comp1) // - so0_1_0 (Comp1) // so1 (Comp1, Comp2) // - so1_0 // - so1_1 Scene.Load("unitTest4Scene_0.prefab"); SceneObject sceneRoot = Scene.Root; SceneObject so0 = sceneRoot.FindChild("so0", false); SceneObject so0_0 = so0.FindChild("so0_0", false); SceneObject so0_1 = so0.FindChild("so0_1", false); SceneObject so1 = sceneRoot.FindChild("so1", false); SceneObject so1_0 = so1.FindChild("so1_0", false); SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false); SceneObject so1_1 = new SceneObject("so1_1"); so1_1.Parent = so1; so0.RemoveComponent <UT1_Component1>(); UT1_Component1 comp1 = so1.AddComponent <UT1_Component1>(); UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>(); so0_0.Destroy(); comp1.otherSO = so1_0; comp1.otherComponent2 = comp0_1_0; comp0_1_0.otherSO = so1_1; comp0_1_0.otherComponent2 = comp1; comp0_1_0.a = 123; comp0_1_0.b = "modifiedValue"; so1.Name = "so1_modified"; so1.LocalPosition = new Vector3(0, 999.0f, 0.0f); EditorApplication.SaveScene("unitTest4Scene_0.prefab"); } { EditorApplication.LoadScene("unitTest4Scene_1.prefab"); SceneObject parentSO0 = Scene.Root.FindChild("parentSO0", false); SceneObject parentSO1 = Scene.Root.FindChild("parentSO1", false); SceneObject parentSO1_0 = parentSO1.FindChild("parentSO1_0", false); UT1_Component1 parentComp1_0 = parentSO1_0.GetComponent <UT1_Component1>(); SceneObject prefabInstance = parentSO0.GetChild(0); SceneObject so0 = prefabInstance.FindChild("so0", false); SceneObject so1 = prefabInstance.FindChild("so1_modified", false); SceneObject so0_0 = so0.FindChild("so0_0", false); SceneObject so0_1 = so0.FindChild("so0_1", false); SceneObject so1_0 = so1.FindChild("so1_0", false); SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false); SceneObject so1_1 = so1.FindChild("so1_1", false); UT1_Component1 comp0 = so0.GetComponent <UT1_Component1>(); UT1_Component1 comp1 = so1.GetComponent <UT1_Component1>(); UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>(); Assert(parentComp1_0.otherSO == so1_0); Assert(parentComp1_0.otherComponent2 == comp0_1_0); Assert(so1_1 != null); Assert(so0_0 == null); Assert(comp0 == null); Assert(comp0_1_0.otherSO == so1_1); Assert(comp0_1_0.otherComponent2 == comp1); Assert(comp0_1_0.a == 123); Assert(comp0_1_0.b == "modifiedValue"); Assert(comp1.otherSO == so1_0); Assert(comp1.otherComponent2 == comp0_1_0); Assert(MathEx.ApproxEquals(so1.LocalPosition.y, 999.0f)); } } Debug.Log("Passed stage 3"); // Make instance specific changes to the prefab, modify the prefab itself and ensure // both changes persist { // Create new scene referencing the prefab and make instance modifications { // unitTest4Scene_2.prefab: // parent2SO0 // - [unitTest4Scene_0.prefab] // parent2SO1 // - parent2SO1_0 (Comp1) // unitTest4Scene_0.prefab (unitTest4Scene_2.prefab instance): // so0 (Comp1(INSTANCE)) // - so0_0 (INSTANCE) // - so0_1 (Comp1) // - so0_1_0 (Comp1) // so1 (Comp2) // - so1_0 Scene.Clear(); SceneObject parent2SO0 = new SceneObject("parent2SO0"); SceneObject parent2SO1 = new SceneObject("parent2SO1"); SceneObject parent2SO1_0 = new SceneObject("parent2SO1_0"); parent2SO1_0.Parent = parent2SO1; UT1_Component1 parentComp1_0 = parent2SO1_0.AddComponent <UT1_Component1>(); Prefab scene0Prefab = ProjectLibrary.Load <Prefab>("unitTest4Scene_0.prefab"); SceneObject prefabInstance = scene0Prefab.Instantiate(); prefabInstance.Parent = parent2SO0; SceneObject so0 = prefabInstance.FindChild("so0", false); SceneObject so1 = prefabInstance.FindChild("so1_modified", false); SceneObject so0_1 = so0.FindChild("so0_1", false); SceneObject so1_0 = so1.FindChild("so1_0", false); SceneObject so1_1 = so1.FindChild("so1_1", false); SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false); UT1_Component2 comp1 = so1.GetComponent <UT1_Component2>(); UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>(); UT1_Component1 comp0_1 = so0_1.GetComponent <UT1_Component1>(); SceneObject so0_0 = new SceneObject("so0_0"); so0_0.Parent = so0; UT1_Component1 comp0 = so0.AddComponent <UT1_Component1>(); so1.RemoveComponent <UT1_Component1>(); so1_1.Destroy(); comp0.otherSO = so0_1_0; comp0.otherComponent = comp1; parentComp1_0.otherSO = so1_0; parentComp1_0.otherComponent2 = comp0_1_0; comp0_1_0.otherSO = parent2SO1_0; comp0_1_0.otherComponent2 = parentComp1_0; comp0_1_0.b = "instanceValue"; comp0_1.b = "instanceValue2"; EditorApplication.SaveScene("unitTest4Scene_2.prefab"); } Debug.Log("Passed stage 4.1"); // Reload the scene and ensure instance modifications remain { EditorApplication.LoadScene("unitTest4Scene_2.prefab"); SceneObject root = Scene.Root; SceneObject parent2SO0 = root.FindChild("parent2SO0", false); SceneObject parent2SO1 = root.FindChild("parent2SO1", false); SceneObject parent2SO1_0 = parent2SO1.FindChild("parent2SO1_0", false); SceneObject prefabInstance = parent2SO0.GetChild(0); SceneObject so0 = prefabInstance.FindChild("so0", false); SceneObject so1 = prefabInstance.FindChild("so1_modified", false); SceneObject so0_0 = so0.FindChild("so0_0", false); SceneObject so0_1 = so0.FindChild("so0_1", false); SceneObject so1_0 = so1.FindChild("so1_0", false); SceneObject so1_1 = so1.FindChild("so1_1", false); SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false); UT1_Component1 parentComp1_0 = parent2SO1_0.GetComponent <UT1_Component1>(); UT1_Component1 comp0 = so0.GetComponent <UT1_Component1>(); UT1_Component2 comp1 = so1.GetComponent <UT1_Component2>(); UT1_Component1 comp11 = so1.GetComponent <UT1_Component1>(); UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>(); UT1_Component1 comp0_1 = so0_1.GetComponent <UT1_Component1>(); Assert(so0_0 != null); Assert(comp0 != null); Assert(so1_1 == null); Assert(comp11 == null); Assert(comp0.otherSO == so0_1_0); Assert(comp0.otherComponent == comp1); Assert(parentComp1_0.otherSO == so1_0); Assert(parentComp1_0.otherComponent2 == comp0_1_0); Debug.Log(comp0_1_0.otherSO == null); if (comp0_1_0.otherSO != null) { Debug.Log(comp0_1_0.otherSO.InstanceId + " - " + parent2SO1_0.InstanceId); } Assert(comp0_1_0.otherSO == parent2SO1_0); Assert(comp0_1_0.otherComponent2 == parentComp1_0); Assert(comp0_1_0.b == "instanceValue"); Assert(comp0_1.b == "instanceValue2"); } Debug.Log("Passed stage 4.2"); // Load original scene and ensure instance modifications didn't influence it { EditorApplication.LoadScene("unitTest4Scene_1.prefab"); SceneObject parentSO0 = Scene.Root.FindChild("parentSO0", false); SceneObject parentSO1 = Scene.Root.FindChild("parentSO1", false); SceneObject parentSO1_0 = parentSO1.FindChild("parentSO1_0", false); UT1_Component1 parentComp1_0 = parentSO1_0.GetComponent <UT1_Component1>(); SceneObject prefabInstance = parentSO0.GetChild(0); SceneObject so0 = prefabInstance.FindChild("so0", false); SceneObject so1 = prefabInstance.FindChild("so1_modified", false); SceneObject so0_0 = so0.FindChild("so0_0", false); SceneObject so0_1 = so0.FindChild("so0_1", false); SceneObject so1_0 = so1.FindChild("so1_0", false); SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false); SceneObject so1_1 = so1.FindChild("so1_1", false); UT1_Component1 comp0 = so0.GetComponent <UT1_Component1>(); UT1_Component1 comp1 = so1.GetComponent <UT1_Component1>(); UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>(); UT1_Component1 comp0_1 = so0_1.GetComponent <UT1_Component1>(); Assert(parentComp1_0.otherSO == so1_0); Assert(parentComp1_0.otherComponent2 == comp0_1_0); Assert(so1_1 != null); Assert(so0_0 == null); Assert(comp0 == null); Assert(comp0_1_0.otherSO == so1_1); Assert(comp0_1_0.otherComponent2 == comp1); Assert(comp0_1_0.a == 123); Assert(comp0_1_0.b == "modifiedValue"); Assert(comp1.otherSO == so1_0); Assert(comp1.otherComponent2 == comp0_1_0); Assert(comp0_1.b == "originalValue2"); Assert(MathEx.ApproxEquals(so1.LocalPosition.y, 999.0f)); } Debug.Log("Passed stage 4.3"); // Modify prefab and ensure both prefab and instance modifications remain { // unitTest4Scene_0.prefab: // so0 (Comp1) // - so0_1 // - so0_1_0 (Comp1) // so1 (Comp1, Comp2) // - so1_1 // - so1_2 (Comp1) // unitTest4Scene_0.prefab (unitTest4Scene_2.prefab instance): // so0 (Comp1) // - so0_0 // - so0_1 (Comp1) // - so0_1_0 (Comp1) // so1 (Comp2) // - so1_2 (Comp1) Scene.Load("unitTest4Scene_0.prefab"); SceneObject sceneRoot = Scene.Root; SceneObject so0 = sceneRoot.FindChild("so0", false); SceneObject so0_1 = so0.FindChild("so0_1", false); SceneObject so1 = sceneRoot.FindChild("so1_modified", false); SceneObject so1_0 = so1.FindChild("so1_0", false); SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false); SceneObject so1_2 = new SceneObject("so1_2"); so1_2.Parent = so1; so0.AddComponent <UT1_Component1>(); so0_1.RemoveComponent <UT1_Component1>(); so1_0.Destroy(); UT1_Component1 comp3 = so1_2.AddComponent <UT1_Component1>(); UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>(); comp0_1_0.b = "modifiedValueAgain"; so1.Name = "so1_modifiedAgain"; comp3.otherSO = so0_1; comp3.otherComponent2 = comp0_1_0; EditorApplication.SaveScene("unitTest4Scene_0.prefab"); } Debug.Log("Passed stage 4.4"); // Reload the scene and ensure both instance and prefab modifications remain { EditorApplication.LoadScene("unitTest4Scene_2.prefab"); SceneObject root = Scene.Root; SceneObject parent2SO0 = root.FindChild("parent2SO0", false); SceneObject parent2SO1 = root.FindChild("parent2SO1", false); SceneObject parent2SO1_0 = parent2SO1.FindChild("parent2SO1_0", false); SceneObject prefabInstance = parent2SO0.GetChild(0); SceneObject so0 = prefabInstance.FindChild("so0", false); SceneObject so1 = prefabInstance.FindChild("so1_modifiedAgain", false); SceneObject so0_0 = so0.FindChild("so0_0", false); SceneObject so0_1 = so0.FindChild("so0_1", false); SceneObject so1_0 = so1.FindChild("so1_0", false); SceneObject so1_1 = so1.FindChild("so1_1", false); SceneObject so1_2 = so1.FindChild("so1_2", false); SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false); UT1_Component1 parentComp1_0 = parent2SO1_0.GetComponent <UT1_Component1>(); UT1_Component1 comp0 = so0.GetComponent <UT1_Component1>(); UT1_Component2 comp1 = so1.GetComponent <UT1_Component2>(); UT1_Component1 comp11 = so1.GetComponent <UT1_Component1>(); UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>(); UT1_Component1 comp3 = so1_2.AddComponent <UT1_Component1>(); // Check instance modifications (they should override any prefab modifications) Assert(so0_0 != null); Assert(comp0 != null); Assert(so1_1 == null); Assert(comp11 == null); Assert(comp0.otherSO == so0_1_0); Assert(comp0.otherComponent == comp1); Assert(parentComp1_0.otherSO == so1_0); Assert(parentComp1_0.otherComponent2 == comp0_1_0); Assert(comp0_1_0.otherSO == parent2SO1_0); Assert(comp0_1_0.otherComponent2 == parentComp1_0); Assert(comp0_1_0.b == "instanceValue"); // Check prefab modifications Assert(so1_0 == null); Assert(so1.Name == "so1_modifiedAgain"); Assert(comp3.otherSO == so0_1); Assert(comp3.otherComponent2 == comp0_1_0); } Debug.Log("Passed stage 4.5"); } } catch { PrintSceneState(); throw; } finally { if (!string.IsNullOrEmpty(oldScene)) { Scene.Load(ProjectLibrary.GetPath(oldScene)); } else { Scene.Clear(); } ProjectLibrary.Delete("unitTest4Scene_0.prefab"); ProjectLibrary.Delete("unitTest4Scene_1.prefab"); ProjectLibrary.Delete("unitTest4Scene_2.prefab"); } }