[unity] SkeletonGraphic now supports automatic scaling based on RectTransform bounds. Closes #1640.

This commit is contained in:
Harald Csaszar 2023-03-24 14:43:23 +01:00
parent 2179cd7c86
commit 91cc6ddfaa
4 changed files with 228 additions and 25 deletions

View File

@ -97,6 +97,7 @@
* `SkeletonRenderTexture` and `SkeletonGraphicRenderTexture` components now support automatic down-scaling when required size on screen exceeds `Max Render Texture Size`. * `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`. * 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. * 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** * **Breaking changes**
* Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead. * Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.

View File

@ -57,7 +57,7 @@ namespace Spine.Unity.Editor {
SerializedProperty additiveMaterial, multiplyMaterial, screenMaterial; SerializedProperty additiveMaterial, multiplyMaterial, screenMaterial;
SerializedProperty skeletonDataAsset, initialSkinName; SerializedProperty skeletonDataAsset, initialSkinName;
SerializedProperty startingAnimation, startingLoop, timeScale, freeze, SerializedProperty startingAnimation, startingLoop, timeScale, freeze,
updateTiming, updateWhenInvisible, unscaledTime, tintBlack; updateTiming, updateWhenInvisible, unscaledTime, tintBlack, layoutScaleMode, editReferenceRect;
SerializedProperty initialFlipX, initialFlipY; SerializedProperty initialFlipX, initialFlipY;
SerializedProperty meshGeneratorSettings; SerializedProperty meshGeneratorSettings;
SerializedProperty allowMultipleCanvasRenderers, separatorSlotNames, enableSeparatorSlots, updateSeparatorPartLocation; SerializedProperty allowMultipleCanvasRenderers, separatorSlotNames, enableSeparatorSlots, updateSeparatorPartLocation;
@ -76,8 +76,8 @@ namespace Spine.Unity.Editor {
protected bool TargetIsValid { protected bool TargetIsValid {
get { get {
if (serializedObject.isEditingMultipleObjects) { if (serializedObject.isEditingMultipleObjects) {
foreach (UnityEngine.Object o in targets) { foreach (UnityEngine.Object c in targets) {
SkeletonGraphic component = (SkeletonGraphic)o; SkeletonGraphic component = (SkeletonGraphic)c;
if (!component.IsValid) if (!component.IsValid)
return false; return false;
} }
@ -129,6 +129,8 @@ namespace Spine.Unity.Editor {
freeze = so.FindProperty("freeze"); freeze = so.FindProperty("freeze");
updateTiming = so.FindProperty("updateTiming"); updateTiming = so.FindProperty("updateTiming");
updateWhenInvisible = so.FindProperty("updateWhenInvisible"); updateWhenInvisible = so.FindProperty("updateWhenInvisible");
layoutScaleMode = so.FindProperty("layoutScaleMode");
editReferenceRect = so.FindProperty("editReferenceRect");
meshGeneratorSettings = so.FindProperty("meshGenerator").FindPropertyRelative("settings"); meshGeneratorSettings = so.FindProperty("meshGenerator").FindPropertyRelative("settings");
meshGeneratorSettings.isExpanded = SkeletonRendererInspector.advancedFoldout; meshGeneratorSettings.isExpanded = SkeletonRendererInspector.advancedFoldout;
@ -141,6 +143,13 @@ namespace Spine.Unity.Editor {
separatorSlotNames.isExpanded = true; separatorSlotNames.isExpanded = true;
} }
void OnDisable () {
foreach (UnityEngine.Object c in targets) {
SkeletonGraphic component = (SkeletonGraphic)c;
component.EditReferenceRect = false;
}
}
public override void OnInspectorGUI () { public override void OnInspectorGUI () {
if (UnityEngine.Event.current.type == EventType.Layout) { if (UnityEngine.Event.current.type == EventType.Layout) {
@ -299,6 +308,20 @@ namespace Spine.Unity.Editor {
EditorGUILayout.PropertyField(raycastTarget); EditorGUILayout.PropertyField(raycastTarget);
if (maskable != null) EditorGUILayout.PropertyField(maskable); if (maskable != null) EditorGUILayout.PropertyField(maskable);
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.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
EditorGUILayout.PrefixLabel("Match RectTransform with Mesh"); EditorGUILayout.PrefixLabel("Match RectTransform with Mesh");
if (GUILayout.Button("Match", EditorStyles.miniButton, GUILayout.Width(65f))) { if (GUILayout.Button("Match", EditorStyles.miniButton, GUILayout.Width(65f))) {
@ -307,6 +330,7 @@ namespace Spine.Unity.Editor {
} }
} }
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
}
if (TargetIsValid && !isInspectingPrefab) { if (TargetIsValid && !isInspectingPrefab) {
EditorGUILayout.Space(); EditorGUILayout.Space();
@ -320,7 +344,6 @@ namespace Spine.Unity.Editor {
} }
wasChanged |= EditorGUI.EndChangeCheck(); wasChanged |= EditorGUI.EndChangeCheck();
if (wasChanged) { if (wasChanged) {
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
slotsReapplyRequired = true; slotsReapplyRequired = true;
@ -346,6 +369,18 @@ namespace Spine.Unity.Editor {
return false; 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 () { protected void AssignDefaultBlendModeMaterials () {
foreach (UnityEngine.Object target in targets) { foreach (UnityEngine.Object target in targets) {
SkeletonGraphic skeletonGraphic = (SkeletonGraphic)target; SkeletonGraphic skeletonGraphic = (SkeletonGraphic)target;

View File

@ -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) { static void DrawCrosshairs2D (Vector3 position, float scale, float skeletonRenderScale = 1f) {
scale *= SpineEditorUtilities.Preferences.handleScale * skeletonRenderScale; scale *= SpineEditorUtilities.Preferences.handleScale * skeletonRenderScale;
Handles.DrawLine(position + new Vector3(-scale, 0), position + new Vector3(scale, 0)); Handles.DrawLine(position + new Vector3(-scale, 0), position + new Vector3(scale, 0));

View File

@ -68,8 +68,29 @@ namespace Spine.Unity {
public float timeScale = 1f; public float timeScale = 1f;
public bool freeze; public bool freeze;
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary> public enum LayoutMode {
public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } } 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
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
protected UpdateMode updateMode = UpdateMode.FullUpdate; protected UpdateMode updateMode = UpdateMode.FullUpdate;
/// <summary>Update mode used when the MeshRenderer becomes invisible /// <summary>Update mode used when the MeshRenderer becomes invisible
@ -273,6 +294,10 @@ namespace Spine.Unity {
Initialize(false); Initialize(false);
Rebuild(CanvasUpdate.PreRender); Rebuild(CanvasUpdate.PreRender);
} }
#if UNITY_EDITOR
InitLayoutScaleParameters();
#endif
} }
protected override void OnDestroy () { protected override void OnDestroy () {
@ -297,6 +322,7 @@ namespace Spine.Unity {
public virtual void Update () { public virtual void Update () {
#if UNITY_EDITOR #if UNITY_EDITOR
if (!Application.isPlaying) { if (!Application.isPlaying) {
UpdateReferenceRectSizes();
Update(0f); Update(0f);
return; return;
} }
@ -316,7 +342,6 @@ namespace Spine.Unity {
wasUpdatedAfterInit = true; wasUpdatedAfterInit = true;
if (updateMode < UpdateMode.OnlyAnimationStatus) if (updateMode < UpdateMode.OnlyAnimationStatus)
return; return;
UpdateAnimationStatus(deltaTime); UpdateAnimationStatus(deltaTime);
if (updateMode == UpdateMode.OnlyAnimationStatus) { if (updateMode == UpdateMode.OnlyAnimationStatus) {
@ -547,15 +572,26 @@ namespace Spine.Unity {
0.5f - (center.y / size.y) 0.5f - (center.y / size.y)
); );
this.rectTransform.sizeDelta = size; SetRectTransformSize(this, size);
this.rectTransform.pivot = p; this.rectTransform.pivot = p;
foreach (SkeletonSubmeshGraphic submeshGraphic in submeshGraphics) { foreach (SkeletonSubmeshGraphic submeshGraphic in submeshGraphics) {
submeshGraphic.rectTransform.sizeDelta = size; SetRectTransformSize(submeshGraphic, size);
submeshGraphic.rectTransform.pivot = p; 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<RectTransform>();
if (parentTransform)
parentSize = parentTransform.rect.size;
}
Vector2 anchorAreaSize = target.rectTransform.anchorMax * parentSize - target.rectTransform.anchorMin * parentSize;
target.rectTransform.sizeDelta = size - anchorAreaSize;
}
/// <summary>OnAnimationRebuild is raised after the SkeletonAnimation component is successfully initialized.</summary> /// <summary>OnAnimationRebuild is raised after the SkeletonAnimation component is successfully initialized.</summary>
public event ISkeletonAnimationDelegate OnAnimationRebuild; public event ISkeletonAnimationDelegate OnAnimationRebuild;
public event UpdateBonesDelegate BeforeApply; public event UpdateBonesDelegate BeforeApply;
@ -731,7 +767,13 @@ namespace Spine.Unity {
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles); 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); if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
Mesh mesh = smartMesh.mesh; Mesh mesh = smartMesh.mesh;
@ -803,9 +845,12 @@ namespace Spine.Unity {
} }
protected void UpdateMeshMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) { protected void UpdateMeshMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) {
Canvas c = canvas; float scale = (canvas == null) ? 100 : canvas.referencePixelsPerUnit;
float scale = (c == null) ? 100 : c.referencePixelsPerUnit; if (layoutScaleMode != LayoutMode.None) {
scale *= referenceScale;
if (!EditReferenceRect)
scale *= GetLayoutScale(layoutScaleMode);
}
// Generate meshes. // Generate meshes.
int submeshCount = currentInstructions.submeshInstructions.Count; int submeshCount = currentInstructions.submeshInstructions.Count;
Mesh[] meshesItems = meshes.Items; Mesh[] meshesItems = meshes.Items;
@ -893,14 +938,19 @@ namespace Spine.Unity {
for (int i = 0; i < submeshCount; i++) { for (int i = 0; i < submeshCount; i++) {
CanvasRenderer canvasRenderer = canvasRenderers[i]; CanvasRenderer canvasRenderer = canvasRenderers[i];
if (i >= usedRenderersCount) { if (i >= usedRenderersCount)
canvasRenderer.gameObject.SetActive(true); canvasRenderer.gameObject.SetActive(true);
}
if (canvasRenderer.transform.parent != parent.transform) { if (canvasRenderer.transform.parent != parent.transform)
canvasRenderer.transform.SetParent(parent.transform, false); canvasRenderer.transform.SetParent(parent.transform, false);
canvasRenderer.transform.localPosition = Vector3.zero;
}
canvasRenderer.transform.SetSiblingIndex(targetSiblingIndex++); 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]; SubmeshInstruction submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
if (submeshInstructionItem.forceSeparate) { 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 #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;
}
} }
} }