Hi, we've hit an issue with the Unity runtime that might be a bug, or certainly seems like strange behaviour, so would appreciate some information on how to work around it. Apologies for the long post, but I'll do my best to explain everything as clearly as possible.
In our game, characters can equip many different weapons, so our character skeletons have various region attachments that serve as weapon templates. At runtime, we enable the right template for a given weapon (small / large etc), and then take the Weapon's Unity Sprite and get a remapped clone of the template attachment to use as the "actual" attachment. We use the GetRemappedClone() method for this. With region attachments, we are able to adjust the pivot point of each unity Sprite, which is then used as a positional offset on the cloned attachment - this is all working great / produces the desired behaviour.
We've now come to need one such template to be a MeshAttachment. I was expecting this to work fine, as GetRemappedClone() has dedicated logic for MeshAttachments, and can even create a linked mesh so animation carries over. However, we then found the cloned attachments had incorrect UVs. After writing a tool to diagnose this, we discovered that a Sprite's pivot point will offset the cloned MeshAttachment's UVs, rather than the vertex position. I believe this is caused by SetRegion() setting the regionOffsetX/Y, when presumably for meshes it shouldn't.
In case it's of any consequence, after all of the character's skins are enabled, and the weapon attachments have been cloned, we bake the active skin down to a single atlas / material, using GetRepackedSkin(). However, skipping this step still results in incorrect UVs on the cloned attachments.
In this image, you can see the difference between the UVs.
So my questions are:
Is a Unity Sprite's pivot point corresponding to regionOffsetX/Y on MeshAttachments expected behaviour? If so, why does the behaviour not match the positional behaviour of RegionAttachments?
How would you recommend I offset the UVs for this attachment back to their correct position? I found that setting regionOffsetX/Y to zero in SetRegion() would result in correct UVs for the cloned attachment, but would be incorrect after repacking the skin.
How would you recommend I offset the position of the Mesh Attachment using the Sprite's pivot, so that I get the same behaviour as with RegionAttachments? My guess is I need to update the vertex positions, but any info on how this offset is usually "scaled" for the skeleton, or whether this could interfere with animation, would be great.
This is a side question, but the resulting cloned attachment (with cloneMeshAsLinked set to true) is not inheriting animation like it says it should. This one could be an issue with how we've set some stuff up in the skeleton, but I thought it might be worth asking if there were any known issues with animation on cloned MeshAttachments.
Extra Info:
We're using spine-unity-3.8-2019-09-27. If this has been fixed in a later release, I'd really appreciate if you could link the relevant commits so we can make the changes ourselves, as we're fairly tied down with this project / probably can't update and replace the package.
Thanks for any help you can provide!
[Edit as of 17:28]
Got a workaround going. I created a modified version GetRemappedClone() and replaced its call to SetRegion() with a modified version of that method as well. In the new SetRegion, I set the attachment's regionOffsetX/Y to zero, and the region's offsetX/Y both to zero before calling UpdateUVs(), which continues to work after repacking.
/// <summary>Sets the region (image) of a MeshAttachment</summary>
public static void SetWeaponRegion(this MeshAttachment attachment, AtlasRegion region, bool updateUVs = true)
{
if (region == null) throw new System.ArgumentNullException("region");
// (AtlasAttachmentLoader.cs)
attachment.RendererObject = region;
attachment.RegionU = region.u;
attachment.RegionV = region.v;
attachment.RegionU2 = region.u2;
attachment.RegionV2 = region.v2;
attachment.RegionRotate = region.rotate;
attachment.regionWidth = region.width;
attachment.regionHeight = region.height;
attachment.regionOriginalWidth = region.originalWidth;
attachment.regionOriginalHeight = region.originalHeight;
attachment.regionOffsetX = 0;
attachment.regionOffsetY = 0;
UpdateMeshOffset(attachment, region);
region.offsetX = 0;
region.offsetY = 0;
if(updateUVs) attachment.UpdateUVs();
}
Before setting the region's offsetX/Y to zero, I use it to offset the attachment's vertex positions, which I managed to get working by following similar logic to RegionAttachment.UpdateOffset(). It doesn't account for all the rotation calculations that regions do, but I don't need that currently.
//Modified from RegionAttachment.UpdateOffset()
private static void UpdateMeshOffset (MeshAttachment mesh, AtlasRegion region)
{
float width = mesh.Width;
float height = mesh.Height;
float offsetX = 0f;
float offsetY = 0f;
if (region.originalWidth != 0) // if (region != null)
{
offsetX += region.offsetX / region.originalWidth * width;
offsetY += region.offsetY / region.originalHeight * height;
}
for (int i = 0; i < mesh.vertices.Length; i++)
{
mesh.vertices[i] += i % 2 == 0 ? offsetX : offsetY;
}
}
Regarding the animation on the cloned mesh not working, I discovered that animations will not work on linked meshes moved to a different slot:
The reason for this limitation [all linked meshes in same slot] is DeformTimeline, which changes the vertices for a mesh. To find which mesh it should change, it knows what slot to look in. If a linked mesh could be in any slot, it would have to look in ALL slots for potential meshes it should change.
Frankly this is really disappointing, not only because it renders all this work entirely redundant (we're only using MeshAttachments as templates in order to get deformation), but it seems like such an obvious workflow. I couldn't see why a list of slots that required deformation couldn't be updated when the linked mesh was attached to a new slot. I began looking into doing this myself until I realised that all the animation / deform timelines are stored in the SkeletonData, so in order to track the additional slots, I'd have to modify the SkeletonData, which I can't because it's an asset in the Unity project which means modifying it would cause all sorts of issues.
This seems like a real oversight, perhaps there needs to be some way of supplying additional timelines per-skeleton. Either way, we'll probably have to scrap our plans for deforming templates, as the LinkedMesh limitation renders our template system incompatible with mesh deformation.