浏览代码

[unity] SkeletonGraphic now supports multiple atlas textures via additional CanvasRenderer GameObjects. Closes #1592. SkeletonGraphic now also supports SkeletonRenderSeparator functionality. Closes #1254. See SkeletonRenderSeparator.unity scene for a usage example.

Harald Csaszar 5 年之前
父节点
当前提交
bc559625f2
共有 15 个文件被更改,包括 1680 次插入57 次删除
  1. 3 0
      CHANGELOG.md
  2. 703 7
      spine-unity/Assets/Spine Examples/Other Examples/SkeletonRenderSeparator.unity
  3. 80 0
      spine-unity/Assets/Spine Examples/Scripts/SpineboyPoleGraphic.cs
  4. 12 0
      spine-unity/Assets/Spine Examples/Scripts/SpineboyPoleGraphic.cs.meta
  5. 159 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicCustomMaterialsInspector.cs
  6. 12 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicCustomMaterialsInspector.cs.meta
  7. 134 2
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs
  8. 326 15
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs
  9. 0 6
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.txt
  10. 0 8
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.txt.meta
  11. 210 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonGraphicCustomMaterials.cs
  12. 12 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonGraphicCustomMaterials.cs.meta
  13. 0 11
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonRendererCustomMaterials.txt
  14. 0 8
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonRendererCustomMaterials.txt.meta
  15. 29 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs

+ 3 - 0
CHANGELOG.md

@@ -228,6 +228,9 @@
     4) Assign this *_Outline* material at the `RenderExistingMesh` component under *Replacement Materials*.
     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 `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 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**
 * **Changes of default values**
   * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`.
   * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`.

+ 703 - 7
spine-unity/Assets/Spine Examples/Other Examples/SkeletonRenderSeparator.unity

@@ -108,6 +108,42 @@ NavMeshSettings:
     tileSize: 256
     tileSize: 256
     accuratePlacement: 0
     accuratePlacement: 0
   m_NavMeshData: {fileID: 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
 --- !u!1 &51877969
 GameObject:
 GameObject:
   m_ObjectHideFlags: 0
   m_ObjectHideFlags: 0
@@ -202,6 +238,74 @@ RectTransform:
   m_AnchoredPosition: {x: 6.56, y: -2.09}
   m_AnchoredPosition: {x: 6.56, y: -2.09}
   m_SizeDelta: {x: 452.17, y: 108.32}
   m_SizeDelta: {x: 452.17, y: 108.32}
   m_Pivot: {x: 0.5, y: 0.5}
   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
 --- !u!1 &93048074
 GameObject:
 GameObject:
   m_ObjectHideFlags: 0
   m_ObjectHideFlags: 0
@@ -328,6 +432,90 @@ Transform:
   m_Father: {fileID: 0}
   m_Father: {fileID: 0}
   m_RootOrder: 4
   m_RootOrder: 4
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   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
 --- !u!1 &122287539
 GameObject:
 GameObject:
   m_ObjectHideFlags: 0
   m_ObjectHideFlags: 0
@@ -409,6 +597,40 @@ Transform:
   m_Father: {fileID: 1918225119}
   m_Father: {fileID: 1918225119}
   m_RootOrder: 0
   m_RootOrder: 0
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 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
 --- !u!1 &565117361
 GameObject:
 GameObject:
   m_ObjectHideFlags: 0
   m_ObjectHideFlags: 0
@@ -3791,7 +4013,7 @@ GameObject:
   - component: {fileID: 624843595}
   - component: {fileID: 624843595}
   - component: {fileID: 624843594}
   - component: {fileID: 624843594}
   m_Layer: 5
   m_Layer: 5
-  m_Name: World Canvas
+  m_Name: World Canvas With Spineboy
   m_TagString: Untagged
   m_TagString: Untagged
   m_Icon: {fileID: 0}
   m_Icon: {fileID: 0}
   m_NavMeshLayer: 0
   m_NavMeshLayer: 0
@@ -3865,6 +4087,9 @@ RectTransform:
   m_LocalScale: {x: 0.02, y: 0.02, z: 0.1}
   m_LocalScale: {x: 0.02, y: 0.02, z: 0.1}
   m_Children:
   m_Children:
   - {fileID: 1618699050}
   - {fileID: 1618699050}
+  - {fileID: 105238417}
+  - {fileID: 1055098057}
+  - {fileID: 33325433}
   m_Father: {fileID: 0}
   m_Father: {fileID: 0}
   m_RootOrder: 3
   m_RootOrder: 3
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@@ -3938,12 +4163,124 @@ Transform:
   m_PrefabInternal: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
   m_GameObject: {fileID: 774732875}
   m_GameObject: {fileID: 774732875}
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
   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_Children: []
   m_Father: {fileID: 1675659861}
   m_Father: {fileID: 1675659861}
   m_RootOrder: 0
   m_RootOrder: 0
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 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
 --- !u!1 &1149289853
 GameObject:
 GameObject:
   m_ObjectHideFlags: 0
   m_ObjectHideFlags: 0
@@ -3967,8 +4304,8 @@ Transform:
   m_PrefabInternal: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
   m_GameObject: {fileID: 1149289853}
   m_GameObject: {fileID: 1149289853}
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
   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_Children: []
   m_Father: {fileID: 1675659861}
   m_Father: {fileID: 1675659861}
   m_RootOrder: 1
   m_RootOrder: 1
@@ -4015,6 +4352,153 @@ SpriteRenderer:
   m_SpriteTileMode: 0
   m_SpriteTileMode: 0
   m_WasSpriteAssigned: 1
   m_WasSpriteAssigned: 1
   m_MaskInteraction: 0
   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
 --- !u!1 &1406277772
 GameObject:
 GameObject:
   m_ObjectHideFlags: 0
   m_ObjectHideFlags: 0
@@ -4038,8 +4522,8 @@ Transform:
   m_PrefabInternal: {fileID: 0}
   m_PrefabInternal: {fileID: 0}
   m_GameObject: {fileID: 1406277772}
   m_GameObject: {fileID: 1406277772}
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
   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_Children: []
   m_Father: {fileID: 1675659861}
   m_Father: {fileID: 1675659861}
   m_RootOrder: 2
   m_RootOrder: 2
@@ -4439,6 +4923,42 @@ Transform:
   m_Father: {fileID: 1918225119}
   m_Father: {fileID: 1918225119}
   m_RootOrder: 1
   m_RootOrder: 1
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   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
 --- !u!1 &1769987559
 GameObject:
 GameObject:
   m_ObjectHideFlags: 0
   m_ObjectHideFlags: 0
@@ -4520,6 +5040,46 @@ Transform:
   m_Father: {fileID: 93048079}
   m_Father: {fileID: 93048079}
   m_RootOrder: 0
   m_RootOrder: 0
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 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
 --- !u!1 &1918225114
 GameObject:
 GameObject:
   m_ObjectHideFlags: 0
   m_ObjectHideFlags: 0
@@ -4663,6 +5223,142 @@ MonoBehaviour:
   pole: {fileID: 11400000, guid: dff7c26e6e007e748b47240522cff0c8, type: 2}
   pole: {fileID: 11400000, guid: dff7c26e6e007e748b47240522cff0c8, type: 2}
   startX: -11.5
   startX: -11.5
   endX: 2.8
   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
 --- !u!1 &2142418130
 GameObject:
 GameObject:
   m_ObjectHideFlags: 0
   m_ObjectHideFlags: 0

+ 80 - 0
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;
+		}
+	}
+
+}

+ 12 - 0
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: 

+ 159 - 0
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<SkeletonGraphicCustomMaterials.AtlasMaterialOverride> componentCustomMaterialOverrides, _customMaterialOverridesPrev;
+		List<SkeletonGraphicCustomMaterials.AtlasTextureOverride> 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<SkeletonGraphicCustomMaterials>();
+			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<SkeletonGraphicCustomMaterials>() == 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<SkeletonGraphicCustomMaterials.AtlasMaterialOverride>;
+				componentCustomTextureOverrides = cm.GetField("customTextureOverrides", PrivateInstance).GetValue(component) as List<SkeletonGraphicCustomMaterials.AtlasTextureOverride>;
+				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<T> CopyList<T> (List<T> list) {
+			return list.GetRange(0, list.Count);
+		}
+	}
+}

+ 12 - 0
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: 

+ 134 - 2
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs

@@ -42,15 +42,20 @@ namespace Spine.Unity.Editor {
 	[CustomEditor(typeof(SkeletonGraphic))]
 	[CustomEditor(typeof(SkeletonGraphic))]
 	[CanEditMultipleObjects]
 	[CanEditMultipleObjects]
 	public class SkeletonGraphicInspector : UnityEditor.Editor {
 	public class SkeletonGraphicInspector : UnityEditor.Editor {
+
+		const string SeparatorSlotNamesFieldName = "separatorSlotNames";
+
 		SerializedProperty material, color;
 		SerializedProperty material, color;
 		SerializedProperty skeletonDataAsset, initialSkinName;
 		SerializedProperty skeletonDataAsset, initialSkinName;
 		SerializedProperty startingAnimation, startingLoop, timeScale, freeze, unscaledTime, tintBlack;
 		SerializedProperty startingAnimation, startingLoop, timeScale, freeze, unscaledTime, tintBlack;
 		SerializedProperty initialFlipX, initialFlipY;
 		SerializedProperty initialFlipX, initialFlipY;
 		SerializedProperty meshGeneratorSettings;
 		SerializedProperty meshGeneratorSettings;
+		SerializedProperty allowMultipleCanvasRenderers, separatorSlotNames, enableSeparatorSlots, updateSeparatorPartLocation;
 		SerializedProperty raycastTarget;
 		SerializedProperty raycastTarget;
 
 
 		SkeletonGraphic thisSkeletonGraphic;
 		SkeletonGraphic thisSkeletonGraphic;
 		protected bool isInspectingPrefab;
 		protected bool isInspectingPrefab;
+		protected bool slotsReapplyRequired = false;
 
 
 		protected bool TargetIsValid {
 		protected bool TargetIsValid {
 			get {
 			get {
@@ -100,9 +105,17 @@ namespace Spine.Unity.Editor {
 
 
 			meshGeneratorSettings = so.FindProperty("meshGenerator").FindPropertyRelative("settings");
 			meshGeneratorSettings = so.FindProperty("meshGenerator").FindPropertyRelative("settings");
 			meshGeneratorSettings.isExpanded = SkeletonRendererInspector.advancedFoldout;
 			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 () {
 		public override void OnInspectorGUI () {
+			bool wasChanged = false;
 			EditorGUI.BeginChangeCheck();
 			EditorGUI.BeginChangeCheck();
 
 
 			EditorGUILayout.PropertyField(skeletonDataAsset);
 			EditorGUILayout.PropertyField(skeletonDataAsset);
@@ -115,9 +128,56 @@ namespace Spine.Unity.Editor {
 				serializedObject.Update();
 				serializedObject.Update();
 				return;
 				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()) {
 			using (new SpineInspectorUtility.BoxScope()) {
 				EditorGUILayout.PropertyField(meshGeneratorSettings, SpineInspectorUtility.TempContent("Advanced..."), includeChildren: true);
 				EditorGUILayout.PropertyField(meshGeneratorSettings, SpineInspectorUtility.TempContent("Advanced..."), includeChildren: true);
 				SkeletonRendererInspector.advancedFoldout = meshGeneratorSettings.isExpanded;
 				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();
 			EditorGUILayout.Space();
@@ -164,10 +224,82 @@ namespace Spine.Unity.Editor {
 				}
 				}
 			}
 			}
 
 
-			bool wasChanged = EditorGUI.EndChangeCheck();
+			wasChanged |= EditorGUI.EndChangeCheck();
 
 
-			if (wasChanged)
+			if (wasChanged) {
 				serializedObject.ApplyModifiedProperties();
 				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
 		#region Menus

+ 326 - 15
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs

@@ -31,6 +31,7 @@
 #define NEW_PREFAB_SYSTEM
 #define NEW_PREFAB_SYSTEM
 #endif
 #endif
 
 
+using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine;
 using UnityEngine.UI;
 using UnityEngine.UI;
 
 
@@ -58,6 +59,20 @@ namespace Spine.Unity {
 		public float timeScale = 1f;
 		public float timeScale = 1f;
 		public bool freeze;
 		public bool freeze;
 		public bool unscaledTime;
 		public bool unscaledTime;
+		public bool allowMultipleCanvasRenderers = false;
+		public List<CanvasRenderer> canvasRenderers = new List<CanvasRenderer>();
+
+		// Submesh Separation
+		public const string SeparatorPartGameObjectName = "Part";
+		/// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
+		[SerializeField] [SpineSlot] protected string[] separatorSlotNames = new string[0];
+
+		/// <summary>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.</summary>
+		[System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
+		public bool enableSeparatorSlots = false;
+		[SerializeField] protected List<Transform> separatorParts = new List<Transform>();
+		public List<Transform> SeparatorParts { get { return separatorParts; } }
+		public bool updateSeparatorPartLocation = true;
 
 
 		private bool wasUpdatedAfterInit = true;
 		private bool wasUpdatedAfterInit = true;
 		private Texture baseTexture = null;
 		private Texture baseTexture = null;
@@ -74,8 +89,8 @@ namespace Spine.Unity {
 				} else if (skeletonDataAsset.GetSkeletonData(true) != skeleton.data) {
 				} else if (skeletonDataAsset.GetSkeletonData(true) != skeleton.data) {
 					Clear();
 					Clear();
 					Initialize(true);
 					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 {
 				} else {
 					if (freeze) return;
 					if (freeze) return;
 
 
@@ -143,7 +158,15 @@ namespace Spine.Unity {
 		}
 		}
 		#endregion
 		#endregion
 
 
-		#region Internals
+		#region Overrides
+		[System.NonSerialized] readonly Dictionary<Texture, Texture> customTextureOverride = new Dictionary<Texture, Texture>();
+		/// <summary>Use this Dictionary to override a Texture with a different Texture.</summary>
+		public Dictionary<Texture, Texture> CustomTextureOverride { get { return customTextureOverride; } }
+
+		[System.NonSerialized] readonly Dictionary<Texture, Material> customMaterialOverride = new Dictionary<Texture, Material>();
+		/// <summary>Use this Dictionary to override the Material where the Texture was used at the original atlas.</summary>
+		public Dictionary<Texture, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
+
 		// This is used by the UI system to determine what to put in the MaterialPropertyBlock.
 		// This is used by the UI system to determine what to put in the MaterialPropertyBlock.
 		Texture overrideTexture;
 		Texture overrideTexture;
 		public 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.
 				canvasRenderer.SetTexture(this.mainTexture); // Refresh canvasRenderer's texture. Make sure it handles null.
 			}
 			}
 		}
 		}
+		#endregion
+
+		#region Internals
 		public override Texture mainTexture {
 		public override Texture mainTexture {
 			get {
 			get {
 				if (overrideTexture != null) return overrideTexture;
 				if (overrideTexture != null) return overrideTexture;
@@ -181,6 +207,7 @@ namespace Spine.Unity {
 			base.Rebuild(update);
 			base.Rebuild(update);
 			if (canvasRenderer.cull) return;
 			if (canvasRenderer.cull) return;
 			if (update == CanvasUpdate.PreRender) UpdateMesh();
 			if (update == CanvasUpdate.PreRender) UpdateMesh();
+			if (allowMultipleCanvasRenderers) canvasRenderer.Clear();
 		}
 		}
 
 
 		public virtual void Update () {
 		public virtual void Update () {
@@ -223,6 +250,29 @@ namespace Spine.Unity {
 			//this.SetVerticesDirty(); // Which is better?
 			//this.SetVerticesDirty(); // Which is better?
 			UpdateMesh();
 			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
 		#endregion
 
 
 		#region API
 		#region API
@@ -247,6 +297,7 @@ namespace Spine.Unity {
 		public Spine.Unity.MeshGenerator MeshGenerator { get { return this.meshGenerator; } }
 		public Spine.Unity.MeshGenerator MeshGenerator { get { return this.meshGenerator; } }
 		DoubleBuffered<Spine.Unity.MeshRendererBuffers.SmartMesh> meshBuffers;
 		DoubleBuffered<Spine.Unity.MeshRendererBuffers.SmartMesh> meshBuffers;
 		SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
 		SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
+		readonly ExposedList<Mesh> meshes = new ExposedList<Mesh>();
 
 
 		public Mesh GetLastMesh () {
 		public Mesh GetLastMesh () {
 			return meshBuffers.GetCurrent().mesh;
 			return meshBuffers.GetCurrent().mesh;
@@ -255,21 +306,62 @@ namespace Spine.Unity {
 		public bool MatchRectTransformWithBounds () {
 		public bool MatchRectTransformWithBounds () {
 			UpdateMesh();
 			UpdateMesh();
 
 
+			if (!this.allowMultipleCanvasRenderers)
+				return MatchRectTransformSingleRenderer();
+			else
+				return MatchRectTransformMultipleRenderers();
+		}
+
+		protected bool MatchRectTransformSingleRenderer () {
 			Mesh mesh = this.GetLastMesh();
 			Mesh mesh = this.GetLastMesh();
 			if (mesh == null) {
 			if (mesh == null) {
 				return false;
 				return false;
 			}
 			}
-
 			if (mesh.vertexCount == 0) {
 			if (mesh.vertexCount == 0) {
 				this.rectTransform.sizeDelta = new Vector2(50f, 50f);
 				this.rectTransform.sizeDelta = new Vector2(50f, 50f);
 				this.rectTransform.pivot = new Vector2(0.5f, 0.5f);
 				this.rectTransform.pivot = new Vector2(0.5f, 0.5f);
 				return false;
 				return false;
 			}
 			}
-
 			mesh.RecalculateBounds();
 			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(
 			var p = new Vector2(
 				0.5f - (center.x / size.x),
 				0.5f - (center.x / size.x),
 				0.5f - (center.y / size.y)
 				0.5f - (center.y / size.y)
@@ -277,7 +369,6 @@ namespace Spine.Unity {
 
 
 			this.rectTransform.sizeDelta = size;
 			this.rectTransform.sizeDelta = size;
 			this.rectTransform.pivot = p;
 			this.rectTransform.pivot = p;
-			return true;
 		}
 		}
 
 
 		public event UpdateBonesDelegate UpdateLocal;
 		public event UpdateBonesDelegate UpdateLocal;
@@ -290,6 +381,28 @@ namespace Spine.Unity {
 		public void Clear () {
 		public void Clear () {
 			skeleton = null;
 			skeleton = null;
 			canvasRenderer.Clear();
 			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<CanvasRenderer>();
+			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) {
 		public void Initialize (bool overwrite) {
@@ -321,6 +434,10 @@ namespace Spine.Unity {
 			if (!string.IsNullOrEmpty(initialSkinName))
 			if (!string.IsNullOrEmpty(initialSkinName))
 				skeleton.SetSkin(initialSkinName);
 				skeleton.SetSkin(initialSkinName);
 
 
+			separatorSlots.Clear();
+			for (int i = 0; i < separatorSlotNames.Length; i++)
+				separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
+
 			wasUpdatedAfterInit = false;
 			wasUpdatedAfterInit = false;
 			if (!string.IsNullOrEmpty(startingAnimation)) {
 			if (!string.IsNullOrEmpty(startingAnimation)) {
 				var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(startingAnimation);
 				var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(startingAnimation);
@@ -341,15 +458,39 @@ namespace Spine.Unity {
 			if (!this.IsValid) return;
 			if (!this.IsValid) return;
 
 
 			skeleton.SetColor(this.color);
 			skeleton.SetColor(this.color);
-			var smartMesh = meshBuffers.GetNext();
+
 			var currentInstructions = this.currentInstructions;
 			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);
 			MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, null);
 			bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed);
 			bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed);
 
 
 			meshGenerator.Begin();
 			meshGenerator.Begin();
 			if (currentInstructions.hasActiveClipping && currentInstructions.submeshInstructions.Count > 0) {
 			if (currentInstructions.hasActiveClipping && currentInstructions.submeshInstructions.Count > 0) {
 				meshGenerator.AddSubmesh(currentInstructions.submeshInstructions.Items[0], updateTriangles);
 				meshGenerator.AddSubmesh(currentInstructions.submeshInstructions.Items[0], updateTriangles);
-			} else {
+			}
+			else {
 				meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
 				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<CanvasRenderer>();
+				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
 	}
 	}
 }
 }

+ 0 - 6
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.txt

@@ -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`.

+ 0 - 8
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.txt.meta

@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: f0e413eeb00eabc46bde6dbd7aaaa76c
-timeCreated: 1469110129
-licenseType: Free
-TextScriptImporter:
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 210 - 0
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<AtlasMaterialOverride> customMaterialOverrides = new List<AtlasMaterialOverride>();
+		[SerializeField] protected List<AtlasTextureOverride> customTextureOverrides = new List<AtlasTextureOverride>();
+
+		#if UNITY_EDITOR
+		void Reset () {
+			skeletonGraphic = GetComponent<SkeletonGraphic>();
+
+			// Populate material list
+			if (skeletonGraphic != null && skeletonGraphic.skeletonDataAsset != null) {
+				var atlasAssets = skeletonGraphic.skeletonDataAsset.atlasAssets;
+
+				var initialAtlasMaterialOverrides = new List<AtlasMaterialOverride>();
+				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<AtlasTextureOverride>();
+				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<SkeletonGraphic>();
+
+			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<AtlasMaterialOverride> {
+			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<AtlasTextureOverride> {
+			public bool overrideEnabled;
+			public Texture originalTexture;
+			public Texture replacementTexture;
+
+			public bool Equals (AtlasTextureOverride other) {
+				return overrideEnabled == other.overrideEnabled && originalTexture == other.originalTexture && replacementTexture == other.replacementTexture;
+			}
+		}
+	}
+}

+ 12 - 0
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: 

+ 0 - 11
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonRendererCustomMaterials.txt

@@ -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.

+ 0 - 8
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRendererCustomMaterials/SkeletonRendererCustomMaterials.txt.meta

@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 3d4db6c367e463c4cb5566afc490163c
-timeCreated: 1460572571
-licenseType: Free
-TextScriptImporter:
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 29 - 0
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<Slot> 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<Slot, Material> customSlotMaterials, List<Slot> separatorSlots, bool generateMeshOverride, bool immutableTriangles = false) {
 		public static void GenerateSkeletonRendererInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Dictionary<Slot, Material> customSlotMaterials, List<Slot> separatorSlots, bool generateMeshOverride, bool immutableTriangles = false) {
 			//			if (skeleton == null) throw new ArgumentNullException("skeleton");
 			//			if (skeleton == null) throw new ArgumentNullException("skeleton");
 			//			if (instructionOutput == null) throw new ArgumentNullException("instructionOutput");
 			//			if (instructionOutput == null) throw new ArgumentNullException("instructionOutput");