diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f2c7fb51..1b8710be0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -159,6 +159,7 @@
* `SkeletonRenderTexture` and `SkeletonGraphicRenderTexture` components now support automatic down-scaling when required size on screen exceeds `Max Render Texture Size`.
* Added `Spine/SkeletonGraphic Fill` shader to provide functionality of `Spine/Skeleton Fill` shader for `SkeletonGraphic`.
* Lit Spine URP shaders (`Universal Render Pipeline/Spine/Sprite` and `Universal Render Pipeline/Spine/Skeleton Lit`) now support `Forward+` rendering path as introduced by Unity 2022.2 and URP version 14.
+ * `SkeletonGraphic` now supports automatic scaling based on its `RectTransform` bounds. Automatic scaling can be enabled by setting the added `Layout Scale Mode` Inspector property to either `Width Controls Height`, `Height Controls Width`, `FitInParent` or `EnvelopeParent`. It is set to `None` by default to keep previous behaviour and avoid breaking existing projects. To modify the reference layout bounds, hit the additional `Edit Layout Bounds` toggle button to switch into edit mode, adjust the bounds or hit `Match RectTransform with Mesh`, and hit the button again when done adjusting. The skeleton will now be scaled accordingly to fit the reference layout bounds to the object's `RectTransform`.
* **Breaking changes**
* Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.
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 7e7153387..aaf574906 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
@@ -57,7 +57,7 @@ namespace Spine.Unity.Editor {
SerializedProperty additiveMaterial, multiplyMaterial, screenMaterial;
SerializedProperty skeletonDataAsset, initialSkinName;
SerializedProperty startingAnimation, startingLoop, timeScale, freeze,
- updateTiming, updateWhenInvisible, unscaledTime, tintBlack;
+ updateTiming, updateWhenInvisible, unscaledTime, tintBlack, layoutScaleMode, editReferenceRect;
SerializedProperty initialFlipX, initialFlipY;
SerializedProperty meshGeneratorSettings;
SerializedProperty allowMultipleCanvasRenderers, separatorSlotNames, enableSeparatorSlots, updateSeparatorPartLocation;
@@ -76,8 +76,8 @@ namespace Spine.Unity.Editor {
protected bool TargetIsValid {
get {
if (serializedObject.isEditingMultipleObjects) {
- foreach (UnityEngine.Object o in targets) {
- SkeletonGraphic component = (SkeletonGraphic)o;
+ foreach (UnityEngine.Object c in targets) {
+ SkeletonGraphic component = (SkeletonGraphic)c;
if (!component.IsValid)
return false;
}
@@ -129,6 +129,8 @@ namespace Spine.Unity.Editor {
freeze = so.FindProperty("freeze");
updateTiming = so.FindProperty("updateTiming");
updateWhenInvisible = so.FindProperty("updateWhenInvisible");
+ layoutScaleMode = so.FindProperty("layoutScaleMode");
+ editReferenceRect = so.FindProperty("editReferenceRect");
meshGeneratorSettings = so.FindProperty("meshGenerator").FindPropertyRelative("settings");
meshGeneratorSettings.isExpanded = SkeletonRendererInspector.advancedFoldout;
@@ -141,6 +143,13 @@ namespace Spine.Unity.Editor {
separatorSlotNames.isExpanded = true;
}
+ void OnDisable () {
+ foreach (UnityEngine.Object c in targets) {
+ SkeletonGraphic component = (SkeletonGraphic)c;
+ component.EditReferenceRect = false;
+ }
+ }
+
public override void OnInspectorGUI () {
if (UnityEngine.Event.current.type == EventType.Layout) {
@@ -299,14 +308,29 @@ namespace Spine.Unity.Editor {
EditorGUILayout.PropertyField(raycastTarget);
if (maskable != null) EditorGUILayout.PropertyField(maskable);
- EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
- EditorGUILayout.PrefixLabel("Match RectTransform with Mesh");
- if (GUILayout.Button("Match", EditorStyles.miniButton, GUILayout.Width(65f))) {
- foreach (UnityEngine.Object skeletonGraphic in targets) {
- MatchRectTransformWithBounds((SkeletonGraphic)skeletonGraphic);
- }
+ EditorGUILayout.PropertyField(layoutScaleMode);
+
+ using (new EditorGUI.DisabledGroupScope(layoutScaleMode.intValue == 0)) {
+ EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
+ EditorGUILayout.PrefixLabel("Edit Layout Bounds");
+ editReferenceRect.boolValue = GUILayout.Toggle(editReferenceRect.boolValue,
+ EditorGUIUtility.IconContent("EditCollider"), EditorStyles.miniButton, GUILayout.Width(40f));
+ EditorGUILayout.EndHorizontal();
+ }
+ if (layoutScaleMode.intValue == 0) {
+ editReferenceRect.boolValue = false;
+ }
+
+ using (new EditorGUI.DisabledGroupScope(editReferenceRect.boolValue == false && layoutScaleMode.intValue != 0)) {
+ EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
+ EditorGUILayout.PrefixLabel("Match RectTransform with Mesh");
+ if (GUILayout.Button("Match", EditorStyles.miniButton, GUILayout.Width(65f))) {
+ foreach (UnityEngine.Object skeletonGraphic in targets) {
+ MatchRectTransformWithBounds((SkeletonGraphic)skeletonGraphic);
+ }
+ }
+ EditorGUILayout.EndHorizontal();
}
- EditorGUILayout.EndHorizontal();
if (TargetIsValid && !isInspectingPrefab) {
EditorGUILayout.Space();
@@ -320,7 +344,6 @@ namespace Spine.Unity.Editor {
}
wasChanged |= EditorGUI.EndChangeCheck();
-
if (wasChanged) {
serializedObject.ApplyModifiedProperties();
slotsReapplyRequired = true;
@@ -346,6 +369,18 @@ namespace Spine.Unity.Editor {
return false;
}
+ protected void OnSceneGUI () {
+ SkeletonGraphic skeletonGraphic = (SkeletonGraphic)target;
+ if (skeletonGraphic.EditReferenceRect) {
+ SpineHandles.DrawRectTransformRect(skeletonGraphic, Color.gray);
+ SpineHandles.DrawReferenceRect(skeletonGraphic, Color.green);
+ } else {
+ SpineHandles.DrawReferenceRect(skeletonGraphic, Color.blue);
+ }
+
+
+ }
+
protected void AssignDefaultBlendModeMaterials () {
foreach (UnityEngine.Object target in targets) {
SkeletonGraphic skeletonGraphic = (SkeletonGraphic)target;
diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs
index d936d4bfb..ac2810063 100644
--- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs
+++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs
@@ -424,6 +424,42 @@ namespace Spine.Unity.Editor {
}
}
+ public static void DrawReferenceRect (SkeletonGraphic skeletonGraphic, Color color) {
+ RectTransform rectTransform = skeletonGraphic.rectTransform;
+ Vector2 referenceRectSize = skeletonGraphic.GetReferenceRectSize();
+ Vector3 position = rectTransform.position;
+ Vector3 right = rectTransform.TransformVector(Vector3.right * referenceRectSize.x);
+ Vector3 up = rectTransform.TransformVector(Vector3.up * referenceRectSize.y);
+
+ Vector3 cornerVertexBL = position - rectTransform.pivot.x * right - rectTransform.pivot.y * up;
+ DrawRect(cornerVertexBL, right, up, color);
+ }
+
+ public static void DrawRectTransformRect (SkeletonGraphic skeletonGraphic, Color color) {
+ RectTransform rectTransform = skeletonGraphic.rectTransform;
+ Vector2 rectTransformSize = skeletonGraphic.RectTransformSize;
+ Vector3 position = rectTransform.position;
+ Vector3 right = rectTransform.TransformVector(Vector3.right * rectTransformSize.x);
+ Vector3 up = rectTransform.TransformVector(Vector3.up * rectTransformSize.y);
+
+ Vector3 cornerVertexBL = position - rectTransform.pivot.x * right - rectTransform.pivot.y * up;
+ DrawRect(cornerVertexBL, right, up, color);
+ }
+
+ public static void DrawRect (Vector3 cornerVertexBL, Vector3 right, Vector3 up, Color color) {
+ Vector3 v0 = cornerVertexBL;
+ Vector3 v1 = v0 + right;
+ Vector3 v2 = v0 + right + up;
+ Vector3 v3 = v0 + up;
+ Color previousColor = UnityEditor.Handles.color;
+ UnityEditor.Handles.color = color;
+ UnityEditor.Handles.DrawLine(v0, v1);
+ UnityEditor.Handles.DrawLine(v1, v2);
+ UnityEditor.Handles.DrawLine(v2, v3);
+ UnityEditor.Handles.DrawLine(v3, v0);
+ UnityEditor.Handles.color = previousColor;
+ }
+
static void DrawCrosshairs2D (Vector3 position, float scale, float skeletonRenderScale = 1f) {
scale *= SpineEditorUtilities.Preferences.handleScale * skeletonRenderScale;
Handles.DrawLine(position + new Vector3(-scale, 0), position + new Vector3(scale, 0));
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 db3f12930..e4df73edc 100644
--- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs
+++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs
@@ -68,6 +68,27 @@ namespace Spine.Unity {
public float timeScale = 1f;
public bool freeze;
+ public enum LayoutMode {
+ None = 0,
+ WidthControlsHeight,
+ HeightControlsWidth,
+ FitInParent,
+ EnvelopeParent
+ }
+ public LayoutMode layoutScaleMode = LayoutMode.None;
+ [SerializeField] protected Vector2 referenceSize = Vector2.one;
+ [SerializeField] protected float referenceScale = 1f;
+#if UNITY_EDITOR
+ protected LayoutMode previousLayoutScaleMode = LayoutMode.None;
+ [SerializeField] protected Vector2 rectTransformSize = Vector2.zero;
+ [SerializeField] protected bool editReferenceRect = false;
+ protected bool previousEditReferenceRect = false;
+
+ public bool EditReferenceRect { get { return editReferenceRect; } set { editReferenceRect = value; } }
+ public Vector2 RectTransformSize { get { return rectTransformSize; } }
+#else
+ protected const bool EditReferenceRect = false;
+#endif
/// Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.
public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
protected UpdateMode updateMode = UpdateMode.FullUpdate;
@@ -273,6 +294,10 @@ namespace Spine.Unity {
Initialize(false);
Rebuild(CanvasUpdate.PreRender);
}
+
+#if UNITY_EDITOR
+ InitLayoutScaleParameters();
+#endif
}
protected override void OnDestroy () {
@@ -297,6 +322,7 @@ namespace Spine.Unity {
public virtual void Update () {
#if UNITY_EDITOR
if (!Application.isPlaying) {
+ UpdateReferenceRectSizes();
Update(0f);
return;
}
@@ -316,7 +342,6 @@ namespace Spine.Unity {
wasUpdatedAfterInit = true;
if (updateMode < UpdateMode.OnlyAnimationStatus)
return;
-
UpdateAnimationStatus(deltaTime);
if (updateMode == UpdateMode.OnlyAnimationStatus) {
@@ -547,15 +572,26 @@ namespace Spine.Unity {
0.5f - (center.y / size.y)
);
- this.rectTransform.sizeDelta = size;
+ SetRectTransformSize(this, size);
this.rectTransform.pivot = p;
foreach (SkeletonSubmeshGraphic submeshGraphic in submeshGraphics) {
- submeshGraphic.rectTransform.sizeDelta = size;
+ SetRectTransformSize(submeshGraphic, size);
submeshGraphic.rectTransform.pivot = p;
}
}
+ public static void SetRectTransformSize (Graphic target, Vector2 size) {
+ Vector2 parentSize = Vector2.zero;
+ if (target.rectTransform.parent != null) {
+ RectTransform parentTransform = target.rectTransform.parent.GetComponent();
+ if (parentTransform)
+ parentSize = parentTransform.rect.size;
+ }
+ Vector2 anchorAreaSize = Vector2.Scale(target.rectTransform.anchorMax - target.rectTransform.anchorMin, parentSize);
+ target.rectTransform.sizeDelta = size - anchorAreaSize;
+ }
+
/// OnAnimationRebuild is raised after the SkeletonAnimation component is successfully initialized.
public event ISkeletonAnimationDelegate OnAnimationRebuild;
public event UpdateBonesDelegate BeforeApply;
@@ -731,7 +767,13 @@ namespace Spine.Unity {
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
}
- if (canvas != null) meshGenerator.ScaleVertexData(canvas.referencePixelsPerUnit);
+ float scale = (canvas == null) ? 100 : canvas.referencePixelsPerUnit;
+ if (layoutScaleMode != LayoutMode.None) {
+ scale *= referenceScale;
+ if (!EditReferenceRect)
+ scale *= GetLayoutScale(layoutScaleMode);
+ }
+ meshGenerator.ScaleVertexData(scale);
if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
Mesh mesh = smartMesh.mesh;
@@ -803,9 +845,12 @@ namespace Spine.Unity {
}
protected void UpdateMeshMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) {
- Canvas c = canvas;
- float scale = (c == null) ? 100 : c.referencePixelsPerUnit;
-
+ float scale = (canvas == null) ? 100 : canvas.referencePixelsPerUnit;
+ if (layoutScaleMode != LayoutMode.None) {
+ scale *= referenceScale;
+ if (!EditReferenceRect)
+ scale *= GetLayoutScale(layoutScaleMode);
+ }
// Generate meshes.
int submeshCount = currentInstructions.submeshInstructions.Count;
Mesh[] meshesItems = meshes.Items;
@@ -893,14 +938,19 @@ namespace Spine.Unity {
for (int i = 0; i < submeshCount; i++) {
CanvasRenderer canvasRenderer = canvasRenderers[i];
- if (i >= usedRenderersCount) {
+ if (i >= usedRenderersCount)
canvasRenderer.gameObject.SetActive(true);
- }
- if (canvasRenderer.transform.parent != parent.transform) {
+
+ if (canvasRenderer.transform.parent != parent.transform)
canvasRenderer.transform.SetParent(parent.transform, false);
- canvasRenderer.transform.localPosition = Vector3.zero;
- }
+
canvasRenderer.transform.SetSiblingIndex(targetSiblingIndex++);
+ RectTransform dstTransform = submeshGraphics[i].rectTransform;
+ dstTransform.localPosition = Vector3.zero;
+ dstTransform.pivot = rectTransform.pivot;
+ dstTransform.anchorMin = Vector2.zero;
+ dstTransform.anchorMax = Vector2.one;
+ dstTransform.sizeDelta = Vector2.zero;
SubmeshInstruction submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
if (submeshInstructionItem.forceSeparate) {
@@ -1027,6 +1077,87 @@ namespace Spine.Unity {
}
}
}
+
+ protected void InitLayoutScaleParameters () {
+ previousLayoutScaleMode = layoutScaleMode;
+ }
+
+ protected void UpdateReferenceRectSizes () {
+ if (rectTransformSize == Vector2.zero)
+ rectTransformSize = GetCurrentRectSize();
+
+ HandleChangedEditReferenceRect();
+
+ if (layoutScaleMode != previousLayoutScaleMode) {
+ if (layoutScaleMode != LayoutMode.None) {
+ SetRectTransformSize(this, rectTransformSize);
+ } else {
+ rectTransformSize = referenceSize / referenceScale;
+ referenceScale = 1f;
+ SetRectTransformSize(this, rectTransformSize);
+ }
+ }
+ if (editReferenceRect || layoutScaleMode == LayoutMode.None) {
+ referenceSize = GetCurrentRectSize();
+ }
+ previousLayoutScaleMode = layoutScaleMode;
+ }
+
+ protected void HandleChangedEditReferenceRect () {
+ if (editReferenceRect == previousEditReferenceRect) return;
+ previousEditReferenceRect = editReferenceRect;
+
+ if (editReferenceRect) {
+ rectTransformSize = GetCurrentRectSize();
+ ResetRectToReferenceRectSize();
+ } else {
+ SetRectTransformSize(this, rectTransformSize);
+ }
+ }
+
+ public void ResetRectToReferenceRectSize () {
+ referenceScale = referenceScale * GetLayoutScale(previousLayoutScaleMode);
+ float referenceAspect = referenceSize.x / referenceSize.y;
+ Vector2 newSize = GetCurrentRectSize();
+
+ LayoutMode mode = previousLayoutScaleMode;
+ float frameAspect = newSize.x / newSize.y;
+ if (mode == LayoutMode.FitInParent)
+ mode = frameAspect > referenceAspect ? LayoutMode.HeightControlsWidth : LayoutMode.WidthControlsHeight;
+ else if (mode == LayoutMode.EnvelopeParent)
+ mode = frameAspect > referenceAspect ? LayoutMode.WidthControlsHeight : LayoutMode.HeightControlsWidth;
+
+ if (mode == LayoutMode.WidthControlsHeight)
+ newSize.y = newSize.x / referenceAspect;
+ else if (mode == LayoutMode.HeightControlsWidth)
+ newSize.x = newSize.y * referenceAspect;
+ SetRectTransformSize(this, newSize);
+ }
+
+ public Vector2 GetReferenceRectSize () {
+ return referenceSize * GetLayoutScale(layoutScaleMode);
+ }
#endif
+
+ protected float GetLayoutScale (LayoutMode mode) {
+ Vector2 currentSize = GetCurrentRectSize();
+ float referenceAspect = referenceSize.x / referenceSize.y;
+ float frameAspect = currentSize.x / currentSize.y;
+ if (mode == LayoutMode.FitInParent)
+ mode = frameAspect > referenceAspect ? LayoutMode.HeightControlsWidth : LayoutMode.WidthControlsHeight;
+ else if (mode == LayoutMode.EnvelopeParent)
+ mode = frameAspect > referenceAspect ? LayoutMode.WidthControlsHeight : LayoutMode.HeightControlsWidth;
+
+ if (mode == LayoutMode.WidthControlsHeight) {
+ return currentSize.x / referenceSize.x;
+ } else if (mode == LayoutMode.HeightControlsWidth) {
+ return currentSize.y / referenceSize.y;
+ }
+ return 1f;
+ }
+
+ private Vector2 GetCurrentRectSize () {
+ return this.rectTransform.rect.size;
+ }
}
}