diff --git a/CHANGELOG.md b/CHANGELOG.md index 638883106..38de66eac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -357,6 +357,9 @@ 4) Assign this *_Outline* material at the `RenderExistingMesh` component under *Replacement Materials*. * Added `Outline Shaders URP` example scene to URP extension module to demonstrate the above additions. * Added support for Unity's [`SpriteAtlas`](https://docs.unity3d.com/Manual/class-SpriteAtlas.html) as atlas provider (as an alternative to `.atlas.txt` and `.png` files) alongside a skeleton data file. There is now an additional `Spine SpriteAtlas Import` tool window accessible via `Window - Spine - SpriteAtlas Import`. Additional information can be found in a new section on the [spine-unity documentation page](http://esotericsoftware.com/spine-unity#Advanced---Using-Unity-SpriteAtlas-as-Atlas-Provider). + * Added support for **multiple atlas textures at `SkeletonGraphic`**. You can enable this feature by enabling the parameter `Multiple CanvasRenders` in the `Advanced` section of the `SkeletonGraphic` Inspector. This automatically creates the required number of child `CanvasRenderer` GameObjects for each required draw call (submesh). + * Added support for **Render Separator Slots** at `SkeletonGraphic`. Render separation can be enabled directly in the `Advanced` section of the `SkeletonGraphic` Inspector, it does not require any additional components (like `SkeletonRenderSeparator` or `SkeletonPartsRenderer` for `SkeletonRenderer` components). When enabled, additional separator GameObjects will be created automatically for each separation part, and `CanvasRenderer` GameObjects re-parented to them accordingly. The separator GameObjects can be moved around and re-parented in the hierarchy according to your requirements to achieve the desired draw order within your `Canvas`. A usage example can be found in the updated `Spine Examples/Other Examples/SkeletonRenderSeparator` scene. + * Added `SkeletonGraphicCustomMaterials` component, providing functionality to override materials and textures of a `SkeletonGraphic`, similar to `SkeletonRendererCustomMaterials`. Note: overriding materials or textures per slot is not provided due to structural limitations. * **Changes of default values** * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`. diff --git a/spine-as3/spine-as3/lib/spine-as3.swc b/spine-as3/spine-as3/lib/spine-as3.swc index d0ead398e..d0276b02b 100644 Binary files a/spine-as3/spine-as3/lib/spine-as3.swc and b/spine-as3/spine-as3/lib/spine-as3.swc differ diff --git a/spine-as3/spine-as3/src/spine/Bone.as b/spine-as3/spine-as3/src/spine/Bone.as index 5bb6726b3..775603f13 100644 --- a/spine-as3/spine-as3/src/spine/Bone.as +++ b/spine-as3/spine-as3/src/spine/Bone.as @@ -143,6 +143,8 @@ package spine { var prx : Number = 0; if (s > 0.0001) { s = Math.abs(pa * pd - pb * pc) / s; + pa /= this.skeleton.scaleX; + pc /= this.skeleton.scaleY; pb = pc * s; pd = pa * s; prx = Math.atan2(pc, pa) * MathUtils.radDeg; @@ -161,7 +163,7 @@ package spine { this.b = pa * lb - pb * ld; this.c = pc * la + pd * lc; this.d = pc * lb + pd * ld; - return; + break; } case TransformMode.noScale: case TransformMode.noScaleOrReflection: { diff --git a/spine-c/spine-c/src/spine/AnimationState.c b/spine-c/spine-c/src/spine/AnimationState.c index c5442d291..860b36637 100644 --- a/spine-c/spine-c/src/spine/AnimationState.c +++ b/spine-c/spine-c/src/spine/AnimationState.c @@ -385,6 +385,7 @@ int spAnimationState_apply (spAnimationState* self, spSkeleton* skeleton) { timelines = current->animation->timelines; if ((i == 0 && mix == 1) || blend == SP_MIX_BLEND_ADD) { for (ii = 0; ii < timelineCount; ii++) { + timeline = timelines[ii]; if (timeline->type == SP_TIMELINE_ATTACHMENT) { _spAnimationState_applyAttachmentTimeline(self, timeline, skeleton, animationTime, blend, -1); } else { diff --git a/spine-lua/Bone.lua b/spine-lua/Bone.lua index b1eae553f..14076df80 100644 --- a/spine-lua/Bone.lua +++ b/spine-lua/Bone.lua @@ -144,6 +144,8 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX, local prx = 0 if s > 0.0001 then s = math_abs(pa * pd - pb * pc) / s + pa = pa / self.skeleton.scaleX + pc = pc / self.skeleton.scaleY pb = pc * s pd = pa * s prx = math_deg(math_atan2(pc, pa)); diff --git a/spine-starling/spine-starling/lib/spine-starling.swc b/spine-starling/spine-starling/lib/spine-starling.swc index 4746c7bc0..84c8a1e12 100644 Binary files a/spine-starling/spine-starling/lib/spine-starling.swc and b/spine-starling/spine-starling/lib/spine-starling.swc differ diff --git a/spine-unity/Assets/Spine Examples/Other Examples/SkeletonRenderSeparator.unity b/spine-unity/Assets/Spine Examples/Other Examples/SkeletonRenderSeparator.unity index 1b35f9f1e..aed9c04a4 100644 --- a/spine-unity/Assets/Spine Examples/Other Examples/SkeletonRenderSeparator.unity +++ b/spine-unity/Assets/Spine Examples/Other Examples/SkeletonRenderSeparator.unity @@ -108,6 +108,42 @@ NavMeshSettings: tileSize: 256 accuratePlacement: 0 m_NavMeshData: {fileID: 0} +--- !u!1 &33325432 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 33325433} + m_Layer: 5 + m_Name: SkeletonGraphic with Separator + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &33325433 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 33325432} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 835737018} + - {fileID: 1738423300} + - {fileID: 1185404038} + m_Father: {fileID: 624843597} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 314.43, y: -527.6} + m_SizeDelta: {x: 176.7, y: 265.7} + m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &51877969 GameObject: m_ObjectHideFlags: 0 @@ -202,6 +238,74 @@ RectTransform: m_AnchoredPosition: {x: 6.56, y: -2.09} m_SizeDelta: {x: 452.17, y: 108.32} m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &84997716 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 84997717} + - component: {fileID: 84997719} + - component: {fileID: 84997718} + m_Layer: 5 + m_Name: GreenBar1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &84997717 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 84997716} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1738423300} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -0.0000038146973, y: -0.00012207031} + m_SizeDelta: {x: 36.8, y: 183.94} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &84997718 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 84997716} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.3529412, g: 0.56078434, b: 0.10980393, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 +--- !u!222 &84997719 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 84997716} --- !u!1 &93048074 GameObject: m_ObjectHideFlags: 0 @@ -328,6 +432,90 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 4 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &105238416 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 105238417} + - component: {fileID: 105238419} + - component: {fileID: 105238418} + m_Layer: 5 + m_Name: Text SkeletonGraphic + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &105238417 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 105238416} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 0.99999994} + m_Children: [] + m_Father: {fileID: 624843597} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: -101, y: -527.6} + m_SizeDelta: {x: 256.3, y: 63} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &105238418 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 105238416} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 18 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 77 + m_Alignment: 0 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 1 + m_LineSpacing: 1 + m_Text: 'Spineboy to the right is an example of SkeletonGraphics render separation + functionality. + + The separator slots are setup via the SkeletonGraphics Advanced Inspector section. + + + The separator can be enabled via ''SkeletonGraphic.enableSeparatorSlots''. + + Please note that ''Multiple Canvas Renderers'' must be enabled. + +' +--- !u!222 &105238419 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 105238416} --- !u!1 &122287539 GameObject: m_ObjectHideFlags: 0 @@ -409,6 +597,40 @@ Transform: m_Father: {fileID: 1918225119} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &415442239 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 415442240} + m_Layer: 0 + m_Name: Part[1] + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &415442240 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 415442239} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1211557619} + m_Father: {fileID: 1185404038} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 35.678116, y: -357.22165} + m_SizeDelta: {x: 100, y: 100} + m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &565117361 GameObject: m_ObjectHideFlags: 0 @@ -3791,7 +4013,7 @@ GameObject: - component: {fileID: 624843595} - component: {fileID: 624843594} m_Layer: 5 - m_Name: World Canvas + m_Name: World Canvas With Spineboy m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -3865,6 +4087,9 @@ RectTransform: m_LocalScale: {x: 0.02, y: 0.02, z: 0.1} m_Children: - {fileID: 1618699050} + - {fileID: 105238417} + - {fileID: 1055098057} + - {fileID: 33325433} m_Father: {fileID: 0} m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -3938,12 +4163,124 @@ Transform: m_PrefabInternal: {fileID: 0} m_GameObject: {fileID: 774732875} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 5.5483284, y: 48.482475, z: 1} + m_LocalPosition: {x: 0, y: 2.175, z: 0} + m_LocalScale: {x: 5.5483284, y: 34.887302, z: 1} m_Children: [] m_Father: {fileID: 1675659861} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &835737017 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 835737018} + m_Layer: 0 + m_Name: Part[0] + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &835737018 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 835737017} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0.25, y: 0.25, z: 0.25} + m_Children: + - {fileID: 1882323273} + m_Father: {fileID: 33325433} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -32.299957, y: -63.99994} + m_SizeDelta: {x: 100, y: 100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &1055098056 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1055098057} + - component: {fileID: 1055098059} + - component: {fileID: 1055098058} + m_Layer: 5 + m_Name: Text SkeletonGraphic Location + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1055098057 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1055098056} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 0.99999994} + m_Children: [] + m_Father: {fileID: 624843597} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 577, y: -513.5} + m_SizeDelta: {x: -54.3, y: 63} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1055098058 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1055098056} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 18 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 77 + m_Alignment: 0 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 1 + m_LineSpacing: 1 + m_Text: 'The Part[0] GameObject was re-parented above the GreenBar object to render + in the desired order + + The SkeletonGraphic''s ''Update Part Location'' parameter lets the re-parented + parts follow the main SkeletonGraphic location.' +--- !u!222 &1055098059 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1055098056} --- !u!1 &1149289853 GameObject: m_ObjectHideFlags: 0 @@ -3967,8 +4304,8 @@ Transform: m_PrefabInternal: {fileID: 0} m_GameObject: {fileID: 1149289853} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0.06, y: 0, z: 0} - m_LocalScale: {x: 4.661441, y: 48.482475, z: 1} + m_LocalPosition: {x: 0.06, y: 2.175, z: 0} + m_LocalScale: {x: 4.661441, y: 34.887302, z: 1} m_Children: [] m_Father: {fileID: 1675659861} m_RootOrder: 1 @@ -4015,6 +4352,153 @@ SpriteRenderer: m_SpriteTileMode: 0 m_WasSpriteAssigned: 1 m_MaskInteraction: 0 +--- !u!1 &1185404037 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1185404038} + - component: {fileID: 1185404040} + - component: {fileID: 1185404039} + - component: {fileID: 1185404041} + m_Layer: 0 + m_Name: SkeletonGraphic (spineboy-unity) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1185404038 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1185404037} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0.25, y: 0.25, z: 0.25} + m_Children: + - {fileID: 415442240} + m_Father: {fileID: 33325433} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -32.3, y: -64} + m_SizeDelta: {x: 371.0865, y: 731.54474} + m_Pivot: {x: 0.59614486, y: 0.011688247} +--- !u!114 &1185404039 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1185404037} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d85b887af7e6c3f45a2e2d2920d641bc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 2100000, guid: b66cf7a186d13054989b33a5c90044e4, type: 2} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + skeletonDataAsset: {fileID: 11400000, guid: a467507a4ffb1d542a558739b2fede77, type: 2} + initialSkinName: base + initialFlipX: 0 + initialFlipY: 0 + startingAnimation: + startingLoop: 1 + timeScale: 1 + freeze: 0 + unscaledTime: 0 + allowMultipleCanvasRenderers: 1 + canvasRenderers: + - {fileID: 1882323272} + - {fileID: 1211557620} + separatorSlotNames: + - --A + enableSeparatorSlots: 1 + separatorParts: + - {fileID: 835737018} + - {fileID: 415442240} + updateSeparatorPartLocation: 1 + meshGenerator: + settings: + useClipping: 1 + zSpacing: 0 + pmaVertexColors: 1 + tintBlack: 0 + calculateTangents: 0 + addNormals: 0 + immutableTriangles: 0 +--- !u!222 &1185404040 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1185404037} +--- !u!114 &1185404041 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1185404037} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d6bb29eb283767441a398ce2a7be27c3, type: 3} + m_Name: + m_EditorClassIdentifier: + skeletonGraphic: {fileID: 1185404039} + run: {fileID: 11400000, guid: 2d841d20c203ff24a859b8c73f9c3817, type: 2} + pole: {fileID: 11400000, guid: dff7c26e6e007e748b47240522cff0c8, type: 2} + startX: -600 + endX: 0 +--- !u!1 &1211557618 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1211557619} + - component: {fileID: 1211557620} + m_Layer: 0 + m_Name: Renderer1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1211557619 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1211557618} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 415442240} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 100, y: 100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1211557620 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1211557618} --- !u!1 &1406277772 GameObject: m_ObjectHideFlags: 0 @@ -4038,8 +4522,8 @@ Transform: m_PrefabInternal: {fileID: 0} m_GameObject: {fileID: 1406277772} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0.44, y: 0, z: 0} - m_LocalScale: {x: 0.44441086, y: 48.482475, z: 1} + m_LocalPosition: {x: 0.44, y: 2.175, z: 0} + m_LocalScale: {x: 0.44441086, y: 34.887302, z: 1} m_Children: [] m_Father: {fileID: 1675659861} m_RootOrder: 2 @@ -4439,6 +4923,42 @@ Transform: m_Father: {fileID: 1918225119} m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1738423299 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1738423300} + m_Layer: 5 + m_Name: GreenBar + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1738423300 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1738423299} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 84997717} + - {fileID: 1983276686} + - {fileID: 2132214121} + m_Father: {fileID: 33325433} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 6.700018, y: 24.610062} + m_SizeDelta: {x: 100, y: 100} + m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &1769987559 GameObject: m_ObjectHideFlags: 0 @@ -4520,6 +5040,46 @@ Transform: m_Father: {fileID: 93048079} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1882323271 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1882323273} + - component: {fileID: 1882323272} + m_Layer: 0 + m_Name: Renderer0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!222 &1882323272 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1882323271} +--- !u!224 &1882323273 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1882323271} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 835737018} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 100, y: 100} + m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &1918225114 GameObject: m_ObjectHideFlags: 0 @@ -4663,6 +5223,142 @@ MonoBehaviour: pole: {fileID: 11400000, guid: dff7c26e6e007e748b47240522cff0c8, type: 2} startX: -11.5 endX: 2.8 +--- !u!1 &1983276685 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1983276686} + - component: {fileID: 1983276688} + - component: {fileID: 1983276687} + m_Layer: 5 + m_Name: GreenBar2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1983276686 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1983276685} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1738423300} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -14.800003, y: -0.000061035156} + m_SizeDelta: {x: 7.2, y: 183.98} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1983276687 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1983276685} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.3647059, g: 0.4901961, b: 0.17254902, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 +--- !u!222 &1983276688 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1983276685} +--- !u!1 &2132214120 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 2132214121} + - component: {fileID: 2132214123} + - component: {fileID: 2132214122} + m_Layer: 5 + m_Name: GreenBar3 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2132214121 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 2132214120} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1738423300} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 6.7999954, y: -0.000061035156} + m_SizeDelta: {x: 7.2, y: 183.98} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2132214122 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 2132214120} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.4784314, g: 0.9686275, b: 0.007843138, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 +--- !u!222 &2132214123 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 2132214120} --- !u!1 &2142418130 GameObject: m_ObjectHideFlags: 0 diff --git a/spine-unity/Assets/Spine Examples/Scripts/SpineboyPoleGraphic.cs b/spine-unity/Assets/Spine Examples/Scripts/SpineboyPoleGraphic.cs new file mode 100644 index 000000000..6d5b643a3 --- /dev/null +++ b/spine-unity/Assets/Spine Examples/Scripts/SpineboyPoleGraphic.cs @@ -0,0 +1,80 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using UnityEngine; +using System.Collections; +using Spine.Unity; + +using Spine.Unity.Examples; + +namespace Spine.Unity.Examples { + public class SpineboyPoleGraphic : MonoBehaviour { + public SkeletonGraphic skeletonGraphic; + + [Space(18)] + public AnimationReferenceAsset run; + public AnimationReferenceAsset pole; + public float startX; + public float endX; + + const float Speed = 18f; + const float RunTimeScale = 1.5f; + + IEnumerator Start () { + var state = skeletonGraphic.AnimationState; + + while (true) { + // Run phase + SetXPosition(startX); + skeletonGraphic.enableSeparatorSlots = false; // Disable Separator during run. + state.SetAnimation(0, run, true); + state.TimeScale = RunTimeScale; + + while (transform.localPosition.x < endX) { + transform.Translate(Vector3.right * Speed * Time.deltaTime); + yield return null; + } + + // Hit phase + SetXPosition(endX); + skeletonGraphic.enableSeparatorSlots = true; // Enable Separator when hit + var poleTrack = state.SetAnimation(0, pole, false); + yield return new WaitForSpineAnimationComplete(poleTrack); + yield return new WaitForSeconds(1f); + } + } + + void SetXPosition (float x) { + var tp = transform.localPosition; + tp.x = x; + transform.localPosition = tp; + } + } + +} diff --git a/spine-unity/Assets/Spine Examples/Scripts/SpineboyPoleGraphic.cs.meta b/spine-unity/Assets/Spine Examples/Scripts/SpineboyPoleGraphic.cs.meta new file mode 100644 index 000000000..3ccdb352e --- /dev/null +++ b/spine-unity/Assets/Spine Examples/Scripts/SpineboyPoleGraphic.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d6bb29eb283767441a398ce2a7be27c3 +timeCreated: 1458684804 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicCustomMaterialsInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicCustomMaterialsInspector.cs new file mode 100644 index 000000000..f9a1c2d78 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicCustomMaterialsInspector.cs @@ -0,0 +1,159 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; +using Spine.Unity.Examples; + +namespace Spine.Unity.Editor { + + // This script is not intended for use with code. See spine-unity documentation page for additional information. + [CustomEditor(typeof(SkeletonGraphicCustomMaterials))] + public class SkeletonGraphicCustomMaterialsInspector : UnityEditor.Editor { + List componentCustomMaterialOverrides, _customMaterialOverridesPrev; + List componentCustomTextureOverrides, _customTextureOverridesPrev; + SkeletonGraphicCustomMaterials component; + + const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic; + MethodInfo RemoveCustomMaterialOverrides, RemoveCustomTextureOverrides, SetCustomMaterialOverrides, SetCustomTextureOverrides; + + #region SkeletonGraphic context menu + [MenuItem("CONTEXT/SkeletonGraphic/Add Basic Serialized Custom Materials")] + static void AddSkeletonGraphicCustomMaterials (MenuCommand menuCommand) { + var skeletonGraphic = (SkeletonGraphic)menuCommand.context; + var newComponent = skeletonGraphic.gameObject.AddComponent(); + Undo.RegisterCreatedObjectUndo(newComponent, "Add Basic Serialized Custom Materials"); + } + + [MenuItem("CONTEXT/SkeletonGraphic/Add Basic Serialized Custom Materials", true)] + static bool AddSkeletonGraphicCustomMaterials_Validate (MenuCommand menuCommand) { + var skeletonGraphic = (SkeletonGraphic)menuCommand.context; + return (skeletonGraphic.GetComponent() == null); + } + #endregion + + void OnEnable () { + Type cm = typeof(SkeletonGraphicCustomMaterials); + RemoveCustomMaterialOverrides = cm.GetMethod("RemoveCustomMaterialOverrides", PrivateInstance); + RemoveCustomTextureOverrides = cm.GetMethod("RemoveCustomTextureOverrides", PrivateInstance); + SetCustomMaterialOverrides = cm.GetMethod("SetCustomMaterialOverrides", PrivateInstance); + SetCustomTextureOverrides = cm.GetMethod("SetCustomTextureOverrides", PrivateInstance); + } + + public override void OnInspectorGUI () { + component = (SkeletonGraphicCustomMaterials)target; + var skeletonGraphic = component.skeletonGraphic; + + // Draw the default inspector + DrawDefaultInspector(); + + if (serializedObject.isEditingMultipleObjects) + return; + + if (componentCustomMaterialOverrides == null) { + Type cm = typeof(SkeletonGraphicCustomMaterials); + componentCustomMaterialOverrides = cm.GetField("customMaterialOverrides", PrivateInstance).GetValue(component) as List; + componentCustomTextureOverrides = cm.GetField("customTextureOverrides", PrivateInstance).GetValue(component) as List; + if (componentCustomMaterialOverrides == null) { + Debug.Log("Reflection failed."); + return; + } + } + + // Fill with current values at start + if (_customMaterialOverridesPrev == null || _customTextureOverridesPrev == null) { + _customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides); + _customTextureOverridesPrev = CopyList(componentCustomTextureOverrides); + } + + // Compare new values with saved. If change is detected: + // store new values, restore old values, remove overrides, restore new values, restore overrides. + + // 1. Store new values + var customMaterialOverridesNew = CopyList(componentCustomMaterialOverrides); + var customTextureOverridesNew = CopyList(componentCustomTextureOverrides); + + // Detect changes + if (!_customMaterialOverridesPrev.SequenceEqual(customMaterialOverridesNew) || + !_customTextureOverridesPrev.SequenceEqual(customTextureOverridesNew)) { + // 2. Restore old values + componentCustomMaterialOverrides.Clear(); + componentCustomTextureOverrides.Clear(); + componentCustomMaterialOverrides.AddRange(_customMaterialOverridesPrev); + componentCustomTextureOverrides.AddRange(_customTextureOverridesPrev); + + // 3. Remove overrides + RemoveCustomMaterials(); + + // 4. Restore new values + componentCustomMaterialOverrides.Clear(); + componentCustomTextureOverrides.Clear(); + componentCustomMaterialOverrides.AddRange(customMaterialOverridesNew); + componentCustomTextureOverrides.AddRange(customTextureOverridesNew); + + // 5. Restore overrides + SetCustomMaterials(); + + if (skeletonGraphic != null) + skeletonGraphic.LateUpdate(); + } + + _customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides); + _customTextureOverridesPrev = CopyList(componentCustomTextureOverrides); + + if (SpineInspectorUtility.LargeCenteredButton(SpineInspectorUtility.TempContent("Clear and Reapply Changes", tooltip: "Removes all non-serialized overrides in the SkeletonGraphic and reapplies the overrides on this component."))) { + if (skeletonGraphic != null) { + skeletonGraphic.CustomMaterialOverride.Clear(); + skeletonGraphic.CustomTextureOverride.Clear(); + RemoveCustomMaterials(); + SetCustomMaterials(); + skeletonGraphic.LateUpdate(); + } + } + } + + void RemoveCustomMaterials () { + RemoveCustomMaterialOverrides.Invoke(component, null); + RemoveCustomTextureOverrides.Invoke(component, null); + } + + void SetCustomMaterials () { + SetCustomMaterialOverrides.Invoke(component, null); + SetCustomTextureOverrides.Invoke(component, null); + } + + static List CopyList (List list) { + return list.GetRange(0, list.Count); + } + } +} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicCustomMaterialsInspector.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicCustomMaterialsInspector.cs.meta new file mode 100644 index 000000000..4a5d35197 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicCustomMaterialsInspector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 349bf125947e3aa4bb78690fec69ea17 +timeCreated: 1588789940 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs index 5c44cba74..97b8235bc 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs @@ -42,15 +42,20 @@ namespace Spine.Unity.Editor { [CustomEditor(typeof(SkeletonGraphic))] [CanEditMultipleObjects] public class SkeletonGraphicInspector : UnityEditor.Editor { + + const string SeparatorSlotNamesFieldName = "separatorSlotNames"; + SerializedProperty material, color; SerializedProperty skeletonDataAsset, initialSkinName; SerializedProperty startingAnimation, startingLoop, timeScale, freeze, unscaledTime, tintBlack; SerializedProperty initialFlipX, initialFlipY; SerializedProperty meshGeneratorSettings; + SerializedProperty allowMultipleCanvasRenderers, separatorSlotNames, enableSeparatorSlots, updateSeparatorPartLocation; SerializedProperty raycastTarget; SkeletonGraphic thisSkeletonGraphic; protected bool isInspectingPrefab; + protected bool slotsReapplyRequired = false; protected bool TargetIsValid { get { @@ -100,9 +105,17 @@ namespace Spine.Unity.Editor { meshGeneratorSettings = so.FindProperty("meshGenerator").FindPropertyRelative("settings"); meshGeneratorSettings.isExpanded = SkeletonRendererInspector.advancedFoldout; + + allowMultipleCanvasRenderers = so.FindProperty("allowMultipleCanvasRenderers"); + updateSeparatorPartLocation = so.FindProperty("updateSeparatorPartLocation"); + enableSeparatorSlots = so.FindProperty("enableSeparatorSlots"); + + separatorSlotNames = so.FindProperty("separatorSlotNames"); + separatorSlotNames.isExpanded = true; } public override void OnInspectorGUI () { + bool wasChanged = false; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(skeletonDataAsset); @@ -115,9 +128,56 @@ namespace Spine.Unity.Editor { serializedObject.Update(); return; } + + bool isSingleRendererOnly = (!allowMultipleCanvasRenderers.hasMultipleDifferentValues && allowMultipleCanvasRenderers.boolValue == false); + bool isSeparationEnabledButNotMultipleRenderers = + isSingleRendererOnly && (!enableSeparatorSlots.hasMultipleDifferentValues && enableSeparatorSlots.boolValue == true); + bool meshRendersIncorrectlyWithSingleRenderer = + isSingleRendererOnly && SkeletonHasMultipleSubmeshes(); + + if (isSeparationEnabledButNotMultipleRenderers || meshRendersIncorrectlyWithSingleRenderer) + meshGeneratorSettings.isExpanded = true; + using (new SpineInspectorUtility.BoxScope()) { EditorGUILayout.PropertyField(meshGeneratorSettings, SpineInspectorUtility.TempContent("Advanced..."), includeChildren: true); SkeletonRendererInspector.advancedFoldout = meshGeneratorSettings.isExpanded; + + if (meshGeneratorSettings.isExpanded) { + EditorGUILayout.Space(); + using (new SpineInspectorUtility.IndentScope()) { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PropertyField(allowMultipleCanvasRenderers, SpineInspectorUtility.TempContent("Multiple CanvasRenderers")); + + if (GUILayout.Button(new GUIContent("Trim Renderers", "Remove currently unused CanvasRenderer GameObjects. These will be regenerated whenever needed."), + EditorStyles.miniButton, GUILayout.Width(100f))) { + + foreach (var skeletonGraphic in targets) { + ((SkeletonGraphic)skeletonGraphic).TrimRenderers(); + } + } + EditorGUILayout.EndHorizontal(); + + // warning box + if (isSeparationEnabledButNotMultipleRenderers) { + using (new SpineInspectorUtility.BoxScope()) { + meshGeneratorSettings.isExpanded = true; + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("'Multiple Canvas Renderers' must be enabled\nwhen 'Enable Separation' is enabled.", Icons.warning), GUILayout.Height(42), GUILayout.Width(340)); + } + } + else if (meshRendersIncorrectlyWithSingleRenderer) { + using (new SpineInspectorUtility.BoxScope()) { + meshGeneratorSettings.isExpanded = true; + EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("This mesh uses multiple atlas pages. You\n" + + "need to enable 'Multiple Canvas Renderers'\n" + + "for correct rendering. Consider packing\n" + + "attachments to a single atlas page if possible.", Icons.warning), GUILayout.Height(60), GUILayout.Width(340)); + } + } + } + + EditorGUILayout.Space(); + SeparatorsField(separatorSlotNames, enableSeparatorSlots, updateSeparatorPartLocation); + } } EditorGUILayout.Space(); @@ -164,10 +224,82 @@ namespace Spine.Unity.Editor { } } - bool wasChanged = EditorGUI.EndChangeCheck(); + wasChanged |= EditorGUI.EndChangeCheck(); - if (wasChanged) + if (wasChanged) { serializedObject.ApplyModifiedProperties(); + slotsReapplyRequired = true; + } + + if (slotsReapplyRequired && UnityEngine.Event.current.type == EventType.Repaint) { + foreach (var target in targets) { + var skeletonGraphic = (SkeletonGraphic)target; + skeletonGraphic.ReapplySeparatorSlotNames(); + skeletonGraphic.LateUpdate(); + SceneView.RepaintAll(); + } + slotsReapplyRequired = false; + } + } + + protected bool SkeletonHasMultipleSubmeshes () { + foreach (var target in targets) { + var skeletonGraphic = (SkeletonGraphic)target; + if (skeletonGraphic.HasMultipleSubmeshInstructions()) + return true; + } + return false; + } + + public static void SetSeparatorSlotNames (SkeletonRenderer skeletonRenderer, string[] newSlotNames) { + var field = SpineInspectorUtility.GetNonPublicField(typeof(SkeletonRenderer), SeparatorSlotNamesFieldName); + field.SetValue(skeletonRenderer, newSlotNames); + } + + public static string[] GetSeparatorSlotNames (SkeletonRenderer skeletonRenderer) { + var field = SpineInspectorUtility.GetNonPublicField(typeof(SkeletonRenderer), SeparatorSlotNamesFieldName); + return field.GetValue(skeletonRenderer) as string[]; + } + + public static void SeparatorsField (SerializedProperty separatorSlotNames, SerializedProperty enableSeparatorSlots, + SerializedProperty updateSeparatorPartLocation) { + + bool multi = separatorSlotNames.serializedObject.isEditingMultipleObjects; + bool hasTerminalSlot = false; + if (!multi) { + var sr = separatorSlotNames.serializedObject.targetObject as ISkeletonComponent; + var skeleton = sr.Skeleton; + int lastSlot = skeleton.Slots.Count - 1; + if (skeleton != null) { + for (int i = 0, n = separatorSlotNames.arraySize; i < n; i++) { + int index = skeleton.FindSlotIndex(separatorSlotNames.GetArrayElementAtIndex(i).stringValue); + if (index == 0 || index == lastSlot) { + hasTerminalSlot = true; + break; + } + } + } + } + + string terminalSlotWarning = hasTerminalSlot ? " (!)" : ""; + + using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { + const string SeparatorsDescription = "Stored names of slots where the Skeleton's render will be split into different batches. This is used by separate components that split the render into different MeshRenderers or GameObjects."; + if (separatorSlotNames.isExpanded) { + EditorGUILayout.PropertyField(separatorSlotNames, SpineInspectorUtility.TempContent(separatorSlotNames.displayName + terminalSlotWarning, Icons.slotRoot, SeparatorsDescription), true); + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("+", GUILayout.MaxWidth(28f), GUILayout.MaxHeight(15f))) { + separatorSlotNames.arraySize++; + } + GUILayout.EndHorizontal(); + } + else + EditorGUILayout.PropertyField(separatorSlotNames, new GUIContent(separatorSlotNames.displayName + string.Format("{0} [{1}]", terminalSlotWarning, separatorSlotNames.arraySize), SeparatorsDescription), true); + + EditorGUILayout.PropertyField(enableSeparatorSlots, SpineInspectorUtility.TempContent("Enable Separation", tooltip: "Whether to enable separation at the above separator slots.")); + EditorGUILayout.PropertyField(updateSeparatorPartLocation, SpineInspectorUtility.TempContent("Update Part Location", tooltip:"Update separator part GameObject location to match the position of the SkeletonGraphic. This can be helpful when re-parenting parts to a different GameObject.")); + } } #region Menus diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs index 9de516ff0..2956a56d6 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -31,6 +31,7 @@ #define NEW_PREFAB_SYSTEM #endif +using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; @@ -58,6 +59,20 @@ namespace Spine.Unity { public float timeScale = 1f; public bool freeze; public bool unscaledTime; + public bool allowMultipleCanvasRenderers = false; + public List canvasRenderers = new List(); + + // Submesh Separation + public const string SeparatorPartGameObjectName = "Part"; + /// Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing. + [SerializeField] [SpineSlot] protected string[] separatorSlotNames = new string[0]; + + /// Slots that determine where the render is split. This is used by components such as SkeletonRenderSeparator so that the skeleton can be rendered by two separate renderers on different GameObjects. + [System.NonSerialized] public readonly List separatorSlots = new List(); + public bool enableSeparatorSlots = false; + [SerializeField] protected List separatorParts = new List(); + public List SeparatorParts { get { return separatorParts; } } + public bool updateSeparatorPartLocation = true; private bool wasUpdatedAfterInit = true; private Texture baseTexture = null; @@ -74,8 +89,8 @@ namespace Spine.Unity { } else if (skeletonDataAsset.GetSkeletonData(true) != skeleton.data) { Clear(); Initialize(true); - if (skeletonDataAsset.atlasAssets.Length > 1 || skeletonDataAsset.atlasAssets[0].MaterialCount > 1) - Debug.LogError("Unity UI does not support multiple textures per Renderer. Your skeleton will not be rendered correctly. Recommend using SkeletonAnimation instead. This requires the use of a Screen space camera canvas."); + if (!allowMultipleCanvasRenderers && (skeletonDataAsset.atlasAssets.Length > 1 || skeletonDataAsset.atlasAssets[0].MaterialCount > 1)) + Debug.LogError("Unity UI does not support multiple textures per Renderer. Please enable 'Advanced - Multiple CanvasRenderers' to generate the required CanvasRenderer GameObjects. Otherwise your skeleton will not be rendered correctly.", this); } else { if (freeze) return; @@ -143,7 +158,15 @@ namespace Spine.Unity { } #endregion - #region Internals + #region Overrides + [System.NonSerialized] readonly Dictionary customTextureOverride = new Dictionary(); + /// Use this Dictionary to override a Texture with a different Texture. + public Dictionary CustomTextureOverride { get { return customTextureOverride; } } + + [System.NonSerialized] readonly Dictionary customMaterialOverride = new Dictionary(); + /// Use this Dictionary to override the Material where the Texture was used at the original atlas. + public Dictionary CustomMaterialOverride { get { return customMaterialOverride; } } + // This is used by the UI system to determine what to put in the MaterialPropertyBlock. Texture overrideTexture; public Texture OverrideTexture { @@ -153,6 +176,9 @@ namespace Spine.Unity { canvasRenderer.SetTexture(this.mainTexture); // Refresh canvasRenderer's texture. Make sure it handles null. } } + #endregion + + #region Internals public override Texture mainTexture { get { if (overrideTexture != null) return overrideTexture; @@ -181,6 +207,7 @@ namespace Spine.Unity { base.Rebuild(update); if (canvasRenderer.cull) return; if (update == CanvasUpdate.PreRender) UpdateMesh(); + if (allowMultipleCanvasRenderers) canvasRenderer.Clear(); } public virtual void Update () { @@ -223,6 +250,29 @@ namespace Spine.Unity { //this.SetVerticesDirty(); // Which is better? UpdateMesh(); } + + public void ReapplySeparatorSlotNames () { + if (!IsValid) + return; + + separatorSlots.Clear(); + for (int i = 0, n = separatorSlotNames.Length; i < n; i++) { + string slotName = separatorSlotNames[i]; + if (slotName == "") + continue; + var slot = skeleton.FindSlot(slotName); + if (slot != null) { + separatorSlots.Add(slot); + } + #if UNITY_EDITOR + else + { + Debug.LogWarning(slotName + " is not a slot in " + skeletonDataAsset.skeletonJSON.name); + } + #endif + } + UpdateSeparatorPartParents(); + } #endregion #region API @@ -247,6 +297,7 @@ namespace Spine.Unity { public Spine.Unity.MeshGenerator MeshGenerator { get { return this.meshGenerator; } } DoubleBuffered meshBuffers; SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction(); + readonly ExposedList meshes = new ExposedList(); public Mesh GetLastMesh () { return meshBuffers.GetCurrent().mesh; @@ -255,21 +306,62 @@ namespace Spine.Unity { public bool MatchRectTransformWithBounds () { UpdateMesh(); + if (!this.allowMultipleCanvasRenderers) + return MatchRectTransformSingleRenderer(); + else + return MatchRectTransformMultipleRenderers(); + } + + protected bool MatchRectTransformSingleRenderer () { Mesh mesh = this.GetLastMesh(); if (mesh == null) { return false; } - if (mesh.vertexCount == 0) { this.rectTransform.sizeDelta = new Vector2(50f, 50f); this.rectTransform.pivot = new Vector2(0.5f, 0.5f); return false; } - mesh.RecalculateBounds(); - var bounds = mesh.bounds; - var size = bounds.size; - var center = bounds.center; + SetRectTransformBounds(mesh.bounds); + return true; + } + + protected bool MatchRectTransformMultipleRenderers () { + bool anyBoundsAdded = false; + Bounds combinedBounds = new Bounds(); + for (int i = 0; i < canvasRenderers.Count; ++i) { + var canvasRenderer = canvasRenderers[i]; + if (!canvasRenderer.gameObject.activeSelf) + continue; + + Mesh mesh = meshes.Items[i]; + if (mesh == null || mesh.vertexCount == 0) + continue; + + mesh.RecalculateBounds(); + var bounds = mesh.bounds; + if (anyBoundsAdded) + combinedBounds.Encapsulate(bounds); + else { + anyBoundsAdded = true; + combinedBounds = bounds; + } + } + + if (!anyBoundsAdded) { + this.rectTransform.sizeDelta = new Vector2(50f, 50f); + this.rectTransform.pivot = new Vector2(0.5f, 0.5f); + return false; + } + + SetRectTransformBounds(combinedBounds); + return true; + } + + private void SetRectTransformBounds (Bounds combinedBounds) { + var size = combinedBounds.size; + var center = combinedBounds.center; var p = new Vector2( 0.5f - (center.x / size.x), 0.5f - (center.y / size.y) @@ -277,7 +369,6 @@ namespace Spine.Unity { this.rectTransform.sizeDelta = size; this.rectTransform.pivot = p; - return true; } public event UpdateBonesDelegate UpdateLocal; @@ -290,6 +381,28 @@ namespace Spine.Unity { public void Clear () { skeleton = null; canvasRenderer.Clear(); + + for (int i = 0; i < canvasRenderers.Count; ++i) + canvasRenderers[i].Clear(); + foreach (var mesh in meshes) + Destroy(mesh); + meshes.Clear(); + } + + public void TrimRenderers () { + var newList = new List(); + foreach (var canvasRenderer in canvasRenderers) { + if (canvasRenderer.gameObject.activeSelf) { + newList.Add(canvasRenderer); + } + else { + if (Application.isEditor && !Application.isPlaying) + DestroyImmediate(canvasRenderer.gameObject); + else + Destroy(canvasRenderer.gameObject); + } + } + canvasRenderers = newList; } public void Initialize (bool overwrite) { @@ -321,6 +434,10 @@ namespace Spine.Unity { if (!string.IsNullOrEmpty(initialSkinName)) skeleton.SetSkin(initialSkinName); + separatorSlots.Clear(); + for (int i = 0; i < separatorSlotNames.Length; i++) + separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i])); + wasUpdatedAfterInit = false; if (!string.IsNullOrEmpty(startingAnimation)) { var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(startingAnimation); @@ -341,15 +458,39 @@ namespace Spine.Unity { if (!this.IsValid) return; skeleton.SetColor(this.color); - var smartMesh = meshBuffers.GetNext(); + var currentInstructions = this.currentInstructions; + if (!this.allowMultipleCanvasRenderers) { + UpdateMeshSingleCanvasRenderer(); + } + else { + UpdateMeshMultipleCanvasRenderers(currentInstructions); + } + + if (OnMeshAndMaterialsUpdated != null) + OnMeshAndMaterialsUpdated(this); + } + + public bool HasMultipleSubmeshInstructions () { + if (!IsValid) + return false; + return MeshGenerator.RequiresMultipleSubmeshesByDrawOrder(skeleton); + } + #endregion + + protected void UpdateMeshSingleCanvasRenderer () { + if (canvasRenderers.Count > 0) + DisableUnusedCanvasRenderers(usedCount : 0); + + var smartMesh = meshBuffers.GetNext(); MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, null); bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed); meshGenerator.Begin(); if (currentInstructions.hasActiveClipping && currentInstructions.submeshInstructions.Count > 0) { meshGenerator.AddSubmesh(currentInstructions.submeshInstructions.Items[0], updateTriangles); - } else { + } + else { meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles); } @@ -373,10 +514,180 @@ namespace Spine.Unity { } } - //this.UpdateMaterial(); // TODO: This allocates memory. - if (OnMeshAndMaterialsUpdated != null) - OnMeshAndMaterialsUpdated(this); + //this.UpdateMaterial(); // note: This would allocate memory. } - #endregion + + protected void UpdateMeshMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) { + MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, null, + enableSeparatorSlots ? separatorSlots : null, + enableSeparatorSlots ? separatorSlots.Count > 0 : false, + false); + + int submeshCount = currentInstructions.submeshInstructions.Count; + EnsureCanvasRendererCount(submeshCount); + EnsureMeshesCount(submeshCount); + EnsureSeparatorPartCount(); + + var c = canvas; + float scale = (c == null) ? 100 : c.referencePixelsPerUnit; + + // Generate meshes. + var meshesItems = meshes.Items; + bool useOriginalTextureAndMaterial = (customMaterialOverride.Count == 0 && customTextureOverride.Count == 0); + int separatorSlotGroupIndex = 0; + Transform parent = this.separatorSlots.Count == 0 ? this.transform : this.separatorParts[0]; + + if (updateSeparatorPartLocation) { + for (int p = 0; p < this.separatorParts.Count; ++p) { + separatorParts[p].position = this.transform.position; + separatorParts[p].rotation = this.transform.rotation; + } + } + + int targetSiblingIndex = 0; + for (int i = 0; i < submeshCount; i++) { + var submeshInstructionItem = currentInstructions.submeshInstructions.Items[i]; + meshGenerator.Begin(); + meshGenerator.AddSubmesh(submeshInstructionItem); + + var targetMesh = meshesItems[i]; + meshGenerator.ScaleVertexData(scale); + if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers); + meshGenerator.FillVertexData(targetMesh); + meshGenerator.FillTriangles(targetMesh); + meshGenerator.FillLateVertexData(targetMesh); + + var submeshMaterial = submeshInstructionItem.material; + var canvasRenderer = canvasRenderers[i]; + canvasRenderer.gameObject.SetActive(true); + canvasRenderer.SetMesh(targetMesh); + canvasRenderer.materialCount = 1; + + if (canvasRenderer.transform.parent != parent.transform) { + canvasRenderer.transform.SetParent(parent.transform, false); + canvasRenderer.transform.localPosition = Vector3.zero; + } + canvasRenderer.transform.SetSiblingIndex(targetSiblingIndex++); + if (submeshInstructionItem.forceSeparate) { + targetSiblingIndex = 0; + parent = separatorParts[++separatorSlotGroupIndex]; + } + + if (useOriginalTextureAndMaterial) + canvasRenderer.SetMaterial(this.materialForRendering, submeshMaterial.mainTexture); + else { + var originalTexture = submeshMaterial.mainTexture; + Material usedMaterial; + Texture usedTexture; + if (!customMaterialOverride.TryGetValue(originalTexture, out usedMaterial)) + usedMaterial = material; + if (!customTextureOverride.TryGetValue(originalTexture, out usedTexture)) + usedTexture = originalTexture; + canvasRenderer.SetMaterial(usedMaterial, usedTexture); + } + } + + DisableUnusedCanvasRenderers(usedCount : submeshCount); + } + + protected void EnsureCanvasRendererCount (int targetCount) { + #if UNITY_EDITOR + RemoveNullCanvasRenderers(); + #endif + int currentCount = canvasRenderers.Count; + for (int i = currentCount; i < targetCount; ++i) { + var go = new GameObject(string.Format("Renderer{0}", i), typeof(RectTransform)); + go.transform.SetParent(this.transform, false); + go.transform.localPosition = Vector3.zero; + var canvasRenderer = go.AddComponent(); + canvasRenderers.Add(canvasRenderer); + } + } + + protected void DisableUnusedCanvasRenderers (int usedCount) { + #if UNITY_EDITOR + RemoveNullCanvasRenderers(); + #endif + for (int i = usedCount; i < canvasRenderers.Count; i++) { + canvasRenderers[i].Clear(); + canvasRenderers[i].gameObject.SetActive(false); + } + } + + #if UNITY_EDITOR + private void RemoveNullCanvasRenderers () { + if (Application.isEditor && !Application.isPlaying) { + for (int i = canvasRenderers.Count - 1; i >= 0; --i) { + if (canvasRenderers[i] == null) { + canvasRenderers.RemoveAt(i); + } + } + } + } + #endif + + protected void EnsureMeshesCount (int targetCount) { + int oldCount = meshes.Count; + meshes.EnsureCapacity(targetCount); + var meshesItems = meshes.Items; + for (int i = oldCount; i < targetCount; i++) + if (meshesItems[i] == null) meshesItems[i] = new Mesh(); + } + + protected void EnsureSeparatorPartCount () { + #if UNITY_EDITOR + RemoveNullSeparatorParts(); + #endif + int targetCount = separatorSlots.Count + 1; + if (targetCount == 1) + return; + + #if UNITY_EDITOR + if (Application.isEditor && !Application.isPlaying) { + for (int i = separatorParts.Count-1; i >= 0; --i) { + if (separatorParts[i] == null) { + separatorParts.RemoveAt(i); + } + } + } + #endif + int currentCount = separatorParts.Count; + for (int i = currentCount; i < targetCount; ++i) { + var go = new GameObject(string.Format("{0}[{1}]", SeparatorPartGameObjectName, i), typeof(RectTransform)); + go.transform.SetParent(this.transform, false); + go.transform.localPosition = Vector3.zero; + separatorParts.Add(go.transform); + } + } + + protected void UpdateSeparatorPartParents () { + int usedCount = separatorSlots.Count + 1; + if (usedCount == 1) { + usedCount = 0; // placed directly at the SkeletonGraphic parent + for (int i = 0; i < canvasRenderers.Count; ++i) { + var canvasRenderer = canvasRenderers[i]; + if (canvasRenderer.transform.parent.name.Contains(SeparatorPartGameObjectName)) { + canvasRenderer.transform.SetParent(this.transform, false); + canvasRenderer.transform.localPosition = Vector3.zero; + } + } + } + for (int i = 0; i < separatorParts.Count; ++i) { + bool isUsed = i < usedCount; + separatorParts[i].gameObject.SetActive(isUsed); + } + } + + #if UNITY_EDITOR + private void RemoveNullSeparatorParts () { + if (Application.isEditor && !Application.isPlaying) { + for (int i = separatorParts.Count - 1; i >= 0; --i) { + if (separatorParts[i] == null) { + separatorParts.RemoveAt(i); + } + } + } + } + #endif } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs index d5aab08f0..71690ec46 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs @@ -269,14 +269,14 @@ namespace Spine.Unity { var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; var clip = GetAnimation(info.clip); if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, weight, layerBlendMode, MixDirection.In); + clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, info.clip.isLooping, stateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); } if (hasNext) { for (int c = 0; c < nextClipInfoCount; c++) { var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; var clip = GetAnimation(info.clip); if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, layerBlendMode, MixDirection.In); + clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); } } if (isInterruptionActive) { @@ -288,7 +288,7 @@ namespace Spine.Unity { var clip = GetAnimation(info.clip); if (clip != null) clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), - interruptingStateInfo.loop, null, weight, layerBlendMode, MixDirection.In); + info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); } } } else { // case MixNext || Hard @@ -298,7 +298,7 @@ namespace Spine.Unity { var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; var clip = GetAnimation(info.clip); if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, 1f, layerBlendMode, MixDirection.In); + clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, info.clip.isLooping, stateInfo.speed < 0), info.clip.isLooping, null, 1f, layerBlendMode, MixDirection.In); ++c; break; } // Mix the rest @@ -306,7 +306,7 @@ namespace Spine.Unity { var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; var clip = GetAnimation(info.clip); if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, stateInfo.loop, stateInfo.speed < 0), stateInfo.loop, null, weight, layerBlendMode, MixDirection.In); + clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, info.clip.isLooping, stateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); } c = 0; @@ -317,7 +317,7 @@ namespace Spine.Unity { var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; var clip = GetAnimation(info.clip); if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), nextStateInfo.loop, null, 1f, layerBlendMode, MixDirection.In); + clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), info.clip.isLooping, null, 1f, layerBlendMode, MixDirection.In); ++c; break; } } @@ -326,7 +326,7 @@ namespace Spine.Unity { var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; var clip = GetAnimation(info.clip); if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), nextStateInfo.loop, null, weight, layerBlendMode, MixDirection.In); + clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); } } @@ -340,7 +340,7 @@ namespace Spine.Unity { float weight = clipWeight * layerWeight; if (weight == 0) continue; var clip = GetAnimation(info.clip); if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), interruptingStateInfo.loop, null, 1f, layerBlendMode, MixDirection.In); + clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), info.clip.isLooping, null, 1f, layerBlendMode, MixDirection.In); ++c; break; } } @@ -351,7 +351,7 @@ namespace Spine.Unity { float weight = clipWeight * layerWeight; if (weight == 0) continue; var clip = GetAnimation(info.clip); if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), interruptingStateInfo.loop, null, weight, layerBlendMode, MixDirection.In); + clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); } } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.txt b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.txt deleted file mode 100644 index c42f929ac..000000000 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.txt +++ /dev/null @@ -1,6 +0,0 @@ -SkeletonRenderSeparator -======================= - -Dependencies: -- SkeletonPartsRenderer uses the `ArraysMeshGenerator` class in `Spine.Unity.MeshGeneration` -- It requires `SPINE_OPTIONAL_RENDEROVERRIDE` to be #defined in `SkeletonRenderer.cs`. diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.txt.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.txt.meta deleted file mode 100644 index 5dd3c994d..000000000 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.txt.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: f0e413eeb00eabc46bde6dbd7aaaa76c -timeCreated: 1469110129 -licenseType: Free -TextScriptImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonGraphicCustomMaterials.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonGraphicCustomMaterials.cs new file mode 100644 index 000000000..7e2b315e5 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonGraphicCustomMaterials.cs @@ -0,0 +1,210 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER +#define NEW_PREFAB_SYSTEM +#endif + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Spine.Unity { + #if NEW_PREFAB_SYSTEM + [ExecuteAlways] + #else + [ExecuteInEditMode] + #endif + public class SkeletonGraphicCustomMaterials : MonoBehaviour { + + #region Inspector + public SkeletonGraphic skeletonGraphic; + [SerializeField] protected List customMaterialOverrides = new List(); + [SerializeField] protected List customTextureOverrides = new List(); + + #if UNITY_EDITOR + void Reset () { + skeletonGraphic = GetComponent(); + + // Populate material list + if (skeletonGraphic != null && skeletonGraphic.skeletonDataAsset != null) { + var atlasAssets = skeletonGraphic.skeletonDataAsset.atlasAssets; + + var initialAtlasMaterialOverrides = new List(); + foreach (AtlasAssetBase atlasAsset in atlasAssets) { + foreach (Material atlasMaterial in atlasAsset.Materials) { + var atlasMaterialOverride = new AtlasMaterialOverride { + overrideEnabled = false, + originalTexture = atlasMaterial.mainTexture + }; + + initialAtlasMaterialOverrides.Add(atlasMaterialOverride); + } + } + customMaterialOverrides = initialAtlasMaterialOverrides; + } + + // Populate texture list + if (skeletonGraphic != null && skeletonGraphic.skeletonDataAsset != null) { + var atlasAssets = skeletonGraphic.skeletonDataAsset.atlasAssets; + + var initialAtlasTextureOverrides = new List(); + foreach (AtlasAssetBase atlasAsset in atlasAssets) { + foreach (Material atlasMaterial in atlasAsset.Materials) { + var atlasTextureOverride = new AtlasTextureOverride { + overrideEnabled = false, + originalTexture = atlasMaterial.mainTexture + }; + + initialAtlasTextureOverrides.Add(atlasTextureOverride); + } + } + customTextureOverrides = initialAtlasTextureOverrides; + } + } + #endif + #endregion + + void SetCustomMaterialOverrides () { + if (skeletonGraphic == null) { + Debug.LogError("skeletonGraphic == null"); + return; + } + + for (int i = 0; i < customMaterialOverrides.Count; i++) { + AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i]; + if (atlasMaterialOverride.overrideEnabled) + skeletonGraphic.CustomMaterialOverride[atlasMaterialOverride.originalTexture] = atlasMaterialOverride.replacementMaterial; + } + } + + void RemoveCustomMaterialOverrides () { + if (skeletonGraphic == null) { + Debug.LogError("skeletonGraphic == null"); + return; + } + + for (int i = 0; i < customMaterialOverrides.Count; i++) { + AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i]; + Material currentMaterial; + + if (!skeletonGraphic.CustomMaterialOverride.TryGetValue(atlasMaterialOverride.originalTexture, out currentMaterial)) + continue; + + // Do not revert the material if it was changed by something else + if (currentMaterial != atlasMaterialOverride.replacementMaterial) + continue; + + skeletonGraphic.CustomMaterialOverride.Remove(atlasMaterialOverride.originalTexture); + } + } + + void SetCustomTextureOverrides () { + if (skeletonGraphic == null) { + Debug.LogError("skeletonGraphic == null"); + return; + } + + for (int i = 0; i < customTextureOverrides.Count; i++) { + AtlasTextureOverride atlasTextureOverride = customTextureOverrides[i]; + if (atlasTextureOverride.overrideEnabled) + skeletonGraphic.CustomTextureOverride[atlasTextureOverride.originalTexture] = atlasTextureOverride.replacementTexture; + } + } + + void RemoveCustomTextureOverrides () { + if (skeletonGraphic == null) { + Debug.LogError("skeletonGraphic == null"); + return; + } + + for (int i = 0; i < customTextureOverrides.Count; i++) { + AtlasTextureOverride atlasTextureOverride = customTextureOverrides[i]; + Texture currentTexture; + + if (!skeletonGraphic.CustomTextureOverride.TryGetValue(atlasTextureOverride.originalTexture, out currentTexture)) + continue; + + // Do not revert the material if it was changed by something else + if (currentTexture != atlasTextureOverride.replacementTexture) + continue; + + skeletonGraphic.CustomTextureOverride.Remove(atlasTextureOverride.originalTexture); + } + } + + // OnEnable applies the overrides at runtime, and when the editor loads. + void OnEnable () { + if (skeletonGraphic == null) + skeletonGraphic = GetComponent(); + + if (skeletonGraphic == null) { + Debug.LogError("skeletonGraphic == null"); + return; + } + + skeletonGraphic.Initialize(false); + SetCustomMaterialOverrides(); + SetCustomTextureOverrides(); + } + + // OnDisable removes the overrides at runtime, and in the editor when the component is disabled or destroyed. + void OnDisable () { + if (skeletonGraphic == null) { + Debug.LogError("skeletonGraphic == null"); + return; + } + + RemoveCustomMaterialOverrides(); + RemoveCustomTextureOverrides(); + } + + [Serializable] + public struct AtlasMaterialOverride : IEquatable { + public bool overrideEnabled; + public Texture originalTexture; + public Material replacementMaterial; + + public bool Equals (AtlasMaterialOverride other) { + return overrideEnabled == other.overrideEnabled && originalTexture == other.originalTexture && replacementMaterial == other.replacementMaterial; + } + } + + [Serializable] + public struct AtlasTextureOverride : IEquatable { + public bool overrideEnabled; + public Texture originalTexture; + public Texture replacementTexture; + + public bool Equals (AtlasTextureOverride other) { + return overrideEnabled == other.overrideEnabled && originalTexture == other.originalTexture && replacementTexture == other.replacementTexture; + } + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonGraphicCustomMaterials.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonGraphicCustomMaterials.cs.meta new file mode 100644 index 000000000..3f0170a1f --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonGraphicCustomMaterials.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6c8717e10b272bf42b05d363ac2679a6 +timeCreated: 1588789074 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonRendererCustomMaterials.txt b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonRendererCustomMaterials.txt deleted file mode 100644 index 917077577..000000000 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonRendererCustomMaterials.txt +++ /dev/null @@ -1,11 +0,0 @@ -SkeletonRendererCustomMaterials by LostPolygon -=============================== -This is a basic serialization and inspector implementation for custom material overrides for SkeletonRenderer and its derived classes (SkeletonAnimation, SkeletonAnimator). - -## How to use -Right-click on your SkeletonRenderer and select "Add Basic Serialized Custom Materials". This will add and initialize the SkeletonRendererCustomMaterials to the same object. - -You can use this to store material override settings for SkeletonRenderer instances/prefabs so they will be applied automatically when your scene starts or when the prefab is instantiated. - -This script is not intended for use with code. -To dynamically set materials for your SkeletonRenderer through code, you can directly access `SkeletonRenderer.CustomMaterialOverride` for material array overrides and `SkeletonRenderer.CustomSlotMaterials` for slot material overrides. \ No newline at end of file diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonRendererCustomMaterials.txt.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonRendererCustomMaterials.txt.meta deleted file mode 100644 index a97910679..000000000 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonRendererCustomMaterials.txt.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 3d4db6c367e463c4cb5566afc490163c -timeCreated: 1460572571 -licenseType: Free -TextScriptImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs index 1aba1c193..153f94e6f 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs @@ -227,6 +227,35 @@ namespace Spine.Unity { } } + public static bool RequiresMultipleSubmeshesByDrawOrder (Skeleton skeleton) { + + #if SPINE_TK2D + return false; + #endif + ExposedList drawOrder = skeleton.drawOrder; + int drawOrderCount = drawOrder.Count; + var drawOrderItems = drawOrder.Items; + + Material lastRendererMaterial = null; + for (int i = 0; i < drawOrderCount; i++) { + Slot slot = drawOrderItems[i]; + if (!slot.bone.active) continue; + Attachment attachment = slot.attachment; + var rendererAttachment = attachment as IHasRendererObject; + if (rendererAttachment != null) { + AtlasRegion atlasRegion = (AtlasRegion)rendererAttachment.RendererObject; + Material material = (Material)atlasRegion.page.rendererObject; + if (lastRendererMaterial != material) { + if (lastRendererMaterial != null) + return true; + else + lastRendererMaterial = material; + } + } + } + return false; + } + public static void GenerateSkeletonRendererInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Dictionary customSlotMaterials, List separatorSlots, bool generateMeshOverride, bool immutableTriangles = false) { // if (skeleton == null) throw new ArgumentNullException("skeleton"); // if (instructionOutput == null) throw new ArgumentNullException("instructionOutput");