• Unity
  • Issues with Sprite Attachments in Unity

Related Discussions
...

Hey guys, having a very frustrating time trying to get a very simple implementation working, where I swap 1 sprite for another in Unity 2018.1.9f1. We have a generic in-hand attachment in spine, and would like to swap it with any number of sprites in Unity, all with the same origin pt, and texture size.

I've tried 3 different methods, and encountered 3 totally different bugs. To make it worse, many links on your forums and faqs just 404 to github (ie: https://github.com/pharan/spine-unity-docs/blob/master/Mix-and-Match.md)

BoneFollower
I tried using a bone follower, attached to the slot, which almost works... but when flipping the X, there's a a 1-frame flicker where the rotation is inverted: http://treefortress.com/shared/2018-08-24_10-58-42.mp4,

I tried changing from LateUpdate to Update, but there are dependancies littered through the code-base calling into this class with LateUpdate, so it turned into a can of worms. Tried changing script execution order, didn't work.

SpriteAttacher
I tried using SpriteAttacher, but it rendered a white sprite for no discernible reason.

If I select "PMA Vertex Colors" on the Skeleton, it then decides to work... But it took me a long time to notice that, and I'm still confused as to why?

AddAttachment Method
I then tried the method shown in EquipMixAndMatch example, and despite doing everything properly, the editor is throwing errors.

NullReferenceException: Object reference not set to an instance of an object
Spine.Unity.MeshGenerator.GenerateSkeletonRendererInstruction (Spine.Unity.SkeletonRendererInstruction instructionOutput, Spine.Skeleton skeleton, System.Collections.Generic.Dictionary2 customSlotMaterials, System.Collections.Generic.List1 separatorSlots, Boolean generateMeshOverride, Boolean immutableTriangles) (at Assets/Plugins/Spine/spine-unity/Mesh Generation/SpineMesh.cs:366)
Spine.Unity.SkeletonRenderer.LateUpdate () (at Assets/Plugins/Spine/spine-unity/Components/SkeletonRenderer.cs:274)

The weird thing about the error above, if I step through it in the debugger... nothing is null. So confused...

UPDATE: If I set PMA to true, in the templateAttachment.GetRemappedClone() call, Method 3 works, and the error goes away. So it seems I need to use the ToRegionAttachmentPMAClone() API regardless if I want PMA or not, just to get a valid attachment, something is broken with sprite.ToAtlasRegion()

Overall I find it very hard to diagnose what is going wrong at any step in the process, there's so many dependancies, extension scripts, editor utilities, and so much complexity, and all I want to do, is just attach 1 sprite to 1 bone, and it's been a massive headache.

It's great that the advanced use cases are served, but it would be nice to add like a single, self-contained code-based example, of attaching a unity sprite, to a bone in the most straightforward way with as little architecture in the way to obfuscate the actual API calls.


In the interest of being constructive, here's the bare minimum script I could come up with, that works currently in Unity to swap a sprite:

using UnityEngine;

using Spine.Unity.Modules.AttachmentTools;
using Spine.Unity;
using Spine;

public class SpineSlotAttacher : MonoBehaviour
{
    public SkeletonAnimation skeletonAnimation;
    [SpineSkin]
    public string skinName;
    [SpineSlot]
    public string targetSlot;
    [SpineAttachment(currentSkinOnly = true)]
    public string attachmentName;
    //Sprite to swap
    public Sprite sprite;

//Internal
int slotIndex;
Material sourceMaterial;
Attachment templateAttachment;
Skin defaultSkin;
Skin equipsSkin = new Skin("Equips");
Material runtimeMaterial;
Texture2D runtimeAtlas;

void Start() {
    var dataAsset = skeletonAnimation.SkeletonDataAsset;
    var skeletonData = dataAsset.GetSkeletonData(true);
    sourceMaterial = dataAsset.atlasAssets[0].materials[0];
    defaultSkin = skeletonData.FindSkin(skinName);
    slotIndex = skeletonData.FindSlotIndex(targetSlot);
    templateAttachment = defaultSkin.GetAttachment(slotIndex, attachmentName);
    //Append the baseSkin to our new skin, so we don't lose any existing attachments.
    equipsSkin.Append(defaultSkin);
    //Attach unity sprite 
    AttachSprite(sprite);
}

public void AttachSprite(Sprite sprite, bool optimize = true) {
   //PMA must be true here, or null reference errors will be thrown
    var attachment = templateAttachment.GetRemappedClone(sprite, sourceMaterial, premultiplyAlpha: true);
    equipsSkin.AddAttachment(slotIndex, attachmentName, attachment);
    if (optimize) {
        skeletonAnimation.Skeleton.Skin = equipsSkin.GetRepackedSkin("Repacked skin", skeletonAnimation.SkeletonDataAsset.atlasAssets[0].materials[0], out runtimeMaterial, out runtimeAtlas);
    } else {
        skeletonAnimation.Skeleton.Skin = equipsSkin;
    }
    RefreshSkeletonAttachments();
}

void RefreshSkeletonAttachments() {
    skeletonAnimation.Skeleton.SetSlotsToSetupPose();
    skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); //skeletonAnimation.Update(0);
}
}

With the script above, it now becomes trivial to swap-sprites from some other control script:

void Update() {
        if (Input.GetKeyDown(KeyCode.Alpha1)) {
            slotAttacher.AttachSprite(axe);
        }
        if (Input.GetKeyDown(KeyCode.Alpha2)) {
            slotAttacher.AttachSprite(shovel);
        }
//ETC
    }

Sorry for the broken link and confusion. We're in the process of moving the docs to the website.
The link is here: Spine-Unity Mix and Match
github here: https://github.com/pharan/spine-unity-docs/blob/master/spine-unity-mix-and-match.md

For changing equips, I wouldn't recommend BoneFollower, though that's more handy for when you need Unity-side behavior on it like particle systems.

Note that SpriteAttacher is a legacy sample component. Its use isn't recommended either but it's in the unitypackage because some people were using it for production before. But I just tried it now and it works fine.

The Mix and Match samples are for the custom-skin workflow, which minimizes animation and instance problems and maximizes performance. They are as large as they are to show you what you may want to keep track of in this type of workflow. They are also there so you can pattern after it how your equip system hooks into Spine, according to your needs; You put those parts into its own class so that your actual equip management can not care how it interfaces with Spine. This abstraction step must be up to you because it's specific to how you want to handle things.

Not all of its parts are necessary and some implementations will vary based on how you put your skeleton together.
Some of the data is also best relegated into their own data types but that would have made them harder to read across different scripts.

We can recommend trying the simplest sample to get started but the Mix and Match sample eventually shows the direction you would be headed.

Here's the minimum of changing the attachment and using a. But you run into problems based on how you animated your skeleton.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using Spine;
using Spine.Unity;
using Spine.Unity.Modules.AttachmentTools;

public class SimpleAttachSpriteExample : MonoBehaviour {

   [SpineSlot]
   public string slotName;
   public Sprite sprite;
   public Material originalMaterial;

   void Start () {
      var skeletonAnimation = GetComponent<SkeletonAnimation>();
      var slot = skeletonAnimation.Skeleton.FindSlot(slotName);
      var oldAttachment = slot.Attachment; // Using a template saves you from manually aligning and rotating the sprite.
      var newAttachment = oldAttachment.GetRemappedClone(sprite, originalMaterial);
      slot.Attachment = newAttachment; // This can be overwritten if your animation keys the slot attachment.
   }
}

Ahh, that docs page was definitely the missing link I needed to explain the current system, and your script also works perfectly. Thanks!

I'm glad it helped.
We're always looking to improve clarity and lower the barriers to doing things (reasonably without sacrificing or miscommunicating how Spine works). So thanks for giving us feedback!

1 个月 后

Hey Pharan! I have some questions about some code I wrote for attaching sprites to slots, very similar to this thread (in fact this thread was super helpful in getting me where I needed to be). Would you rather me post here or start a new thread?


You know... in retrospect, I should probably look through the Mix and Match code a bit more before coming straight to you. I'll do that first, sorry for unneeded ping!

p.s. funnily enough it looks like I had similar questions about attaching to slots about a year ago, Swapping out wearable character equipment, e.g. hats. But this case is pretty different, as we'll be attaching and unattaching quite frequently as characters are picking stuff up and dropping stuff. But yea I'll start a new thread if I still haven't had my questions answered after looking through Mix and Match!

4 天 后

Also, hopefully this isn't a silly question but, can this same principle be applied to SkeletonGraphics as well? I am pretty sure that it should, as all the resources that I need are there, butI have been trying to attach a sprite to a slot and has been all sorts of janky so far. Either the sprite doesn't show up or the sprite that shows up uses the material of the SkeletonGraphic instead of the sprite I am trying to attach.

9 个月 后

This line of code seems incredible! But sadly is not working for me:
var oldAttachment = slot.Attachment; // Using a template saves you from manually aligning and rotating the sprite.

The new sprite Im using never gets in the same position as the previous sprite.

The current workflow Im using to make a character change the armor parts, is:

  • All the armor parts is in the same spritesheet (using TexturePacker);
  • In Unity, Im using the "Apply Regions as Texture Sprite Slices" to separate all the armor parts;
  • Then, Im using one of the armor parts (like Helmet_Heavy) as the sprite to attach to the char body;
  • The slot is right and the sprite is ok. But the final position is wrong.

What Im doing wrong? Can you help me? (or I would have to align all the armor parts manually... :lol: )

Thanks a lot! Great Software!

Glad you like Spine!

ihosse :

- The slot is right and the sprite is ok. But the final position is wrong.

It seems as if the pivot of the Sprite is not set to the correct position.

You can change the Sprite pivot by selecting the file in the Project panel in Unity and hit the Sprite Editor button. There you can drag the pivot circle to the desired location.

Hello Herald!

Harald :

You can change the Sprite pivot by selecting the file in the Project panel in Unity and hit the Sprite Editor button. There you can drag the pivot circle to the desired location

But then, it means that I will need to change all the sprites pivot manually? Because, in my experience, each sprite have a different pivot position.

I thought that the line below would correct the pivot position like magic! 🙂

Pharan :

var oldAttachment = slot.Attachment; // Using a template saves you from manually aligning and rotating the sprite.

Is there another workflow that I can follow or I would really need to align all images one by one?

Thank you for your assistance!

Actually to yield the same results as the template attachment via your code above, the Sprite pivot must be at the center.
You can pass the parameter useOriginalRegionSize: true as follows:

var newAttachment = oldAttachment.GetRemappedClone(sprite, originalMaterial, useOriginalRegionSize: true);

Please note that the pivot of the template oldAttachment is used before whitespace stripping is applied. If e.g. a lot of whitespace is stripped from the left side, it will shift the perceived pivot to the left of the attached quad. If you take the Equipment.atlas.txt used in the Spine Examples/Other Examples/AtlasRegionAttacher scene as an example, the perceived pivot of the quad is not located at the center due to whitespace stripping:

Equipment/sword1
  rotate: false
  xy: 2, 2
  size: 161, 31
  orig: 512, 256
  offset: 217, 112    <

---

 this strips a lot of whitespace at the left
  index: -1

(the image before whitespace stripping is attached below for reference, this way you can see the image borders)
So in the above example, the center before whitespace stripping was at the handle. A new remapped clone will also have the pivot at the center then.

What do your assets look like? Is it still mis-aligned when passing useOriginalRegionSize: true?