• Unity
  • Unity, Spine & 2D Toolkit Sprite Collection Attachments

  • Modificato
Related Discussions
...

We are currently using 2D Toolkit to manage atlases in Unity. We have one of our Spine animated characters implemented using 2D Toolkits' SpriteCollection, which alone works fine. This character has no weapon or shield bounding box or region attached to it in the Spine project. It does, however, have one bone for the shield and another for the weapon, and each of those bones have a slot and a skin placeholder.

The weapons and shields that need to be attached exist in a separate 2D Toolkit SpriteCollection that we are using for equipment. I have been trying to follow examples, but it just does not work, no exception is thrown, it just doesn't show up. Here is what I have so far.

   
public tk2dSpriteCollectionData DataCollectionSpriteTK2D; void Start() { _animationSkeletonUnitySpine = GetComponent<SkeletonAnimation>(); _skeletonSpine = _animationSkeletonUnitySpine.Skeleton RegionAttachment attachmentRegionCollectionSpriteWeaponDagger = new SpriteCollectionAttachmentLoader(DataCollectionSpriteTK2D).NewRegionAttachment(null, "Dagger", "Dagger"); int indexSlotWeapon = skeleton.FindSlotIndex("Weapon"); _skeletonSpine.Skin.AddAttachment(indexSlotWeapon, "Weapon-Dagger", attachmentRegionCollectionSpriteWeaponDagger); }

Any idea what I'm doing wrong?

Thx in advance.

I don't think NewRegionAttachment is sufficient to define and prepare the RegionAttachment.

You basically want to mimic what the normal loaders do:
spine-runtimes/SkeletonJson.cs at 3.6
So something like

// this is your SpriteCollectionAttachmentLoader
RegionAttachment region = attachmentLoader.NewRegionAttachment(null, name, path); 
if (region == null) couldn't find the sprite;
region.Path = path;
//units scales are in Unity world space.
region.x = local x offset from bone;
region.y = local y offset from bone;
region.scaleX = local scale x;
region.scaleY = local scale y;
region.rotation = local rotation;
region.width = width;
region.height = height;

region.r = red value;
region.g = green value;
region.b = blue value;
region.a = alpha value;

region.UpdateOffset();
//return region;

Hi Pharan,

Thanks for answering my question.

In your answer, you mentioned that NewRegionAttachment was insufficient to define and prepare the RegionAttachment, and that I should mimic what normal loaders do. You also referred to the attachment loader I was using as "your SpriteCollectionAttachmentLoader".

The fact is I'm attempting to use the Spine.Unity.TK2D.SpriteCollectionAttachmentLoader that is included as a module of Spine Unity, possibly incorrectly, which leads me to ask the next question.

What is the prescribed order of operations, if there is one, to be able to load a sprite from a 2D Toolkit SpriteCollection, and attach it to a Spine Skeleton in Unity.

Thanks Pharan.

That would be what I pasted above.
The local attachmentLoader in the sample would be your SpriteCollectionAttachmentLoader instance.
Reuse it if you need to load a lot of sprites.

I think you're using it correctly. I mean I'm not seeing anything wrong.

Hi Pharan,

So I'm still a little confused.

region.Path = path; // path? Isn't the path just a reference to a sprite in the sprite collection? If not, what should I put here?
//units scales are in Unity world space.
region.x = 0; // Will this work just as a test?
region.y = 0; // Will this work just as a test?
region.scaleX = 1; // Will this work just as a test?
region.scaleY = 1; // Will this work just as a test?
region.rotation = 0; // Will this work just as a test?
region.width = width; // Shouldn't this already exist in the sprite collection? If not, what should I put here?
region.height = height; // Shouldn't this already exist in the sprite collection? If not, what should I put here?

region.r = red value; // Shouldn't this already exist in the sprite collection? If not, what should I put here?
region.g = green value; // Shouldn't this already exist in the sprite collection? If not, what should I put here?
region.b = blue value; // Shouldn't this already exist in the sprite collection? If not, what should I put here?
region.a = alpha value; // Shouldn't this already exist in the sprite collection? If not, what should I put here?

region.UpdateOffset();

The questions next to the lines of code make me think I don't understand something.

n3xes

Sorry for the confusion.

Path is omittable.

The rest of those values would come from the Attachment as they are defined in Spine:
Their alignment to the parent bone (position, rotation, scale, dimensions of the image, and color)

Since the Attachment object wasn't created in Spine and loaded from the skeleton data, but instead created from scratch, you have to define them.

The reasonable default would centered on the bone with scale of one.
So that would be position (0, 0), scale (1, 1), rotation (0) which would work as a test.
Width and height, I think reasonable values come from your sprite, yes.

color can be white (1,1,1,1) to have no tint.

Hi Pharan,

So I tried your solution. The bad news is it still didn't work, and all of the properties you mentioned had to be set are already set when this line is called.

RegionAttachment attachmentRegionCollectionSpriteWeaponDagger =
            new SpriteCollectionAttachmentLoader(DataCollectionSpriteTK2D).NewRegionAttachment(null, "Dagger", "Dagger");

Here are the properties:

  • attachmentRegionCollectionSpriteWeapon "Dagger" Spine.RegionAttachment
  • base "Dagger" Spine.Attachment
    A 1 System.Single
    B 1 System.Single
    G 1 System.Single
    Height 0 System.Single
  • Offset System.Single[8] System.Single[]
    Path "Dagger" System.String
    R 1 System.Single
    RegionHeight 500 System.Single
    RegionOffsetX 0 System.Single
    RegionOffsetY 0 System.Single
    RegionOriginalHeight 500 System.Single
    RegionOriginalWidth 102 System.Single
    RegionWidth 102 System.Single
  • RendererObject "Weapons-SpriteAtlasMaterial (UnityEngine.Material)" System.Object
    Rotation 0 System.Single
    ScaleX 1 System.Single
    ScaleY 1 System.Single
  • UVs System.Single[8] System.Single[]
    Width 0 System.Single
    X 0 System.Single
    Y 0 System.Single
    a 1 System.Single
    b 1 System.Single
    g 1 System.Single
    height 0 System.Single
  • offset System.Single[8] System.Single[]
    r 1 System.Single
    regionHeight 500 System.Single
    regionOffsetX 0 System.Single
    regionOffsetY 0 System.Single
    regionOriginalHeight 500 System.Single
    regionOriginalWidth 102 System.Single
    regionWidth 102 System.Single
    rotation 0 System.Single
    scaleX 1 System.Single
    scaleY 1 System.Single
  • uvs System.Single[8] System.Single[]
    width 0 System.Single
    x 0 System.Single
    y 0 System.Single
  • Static members

I'm not sure where to even go from here. Please help.

n3xes.

Looks like your width and height are 0. With those dimensions, it would be expected that you would see nothing.
You need to set them.
They come from your sprite, you need to get those values and put them in yourself.

The call to "UpdateOffset" is also important. Don't forget to call it after setting the properties.

Note that what you're doing is a bit out of the ordinary use so some of these calls need to be done through new code.
You can abstract it away as your own method, but it needs to be written.

public RegionAttachment NewRegionAttachmentFromSpriteCollection (string tk2DspriteName,  tk2dSpriteCollectionData collectionData, float scale = 1f) {
   var attachmentLoader = new SpriteCollectionAttachmentLoader(collectionData); // Better if you cache this.
   
RegionAttachment region = attachmentLoader.NewRegionAttachment(null, tk2DspriteName, tk2DspriteName); if (region == null) return null;
region.x = 0; // *scale region.y = 0; // *scale region.scaleX = 1f; region.scaleY = 1f; region.rotation = 0f;
region.width = region.RegionWidth; //* scale. Not sure if this is needed by TK2D region.height = region.RegionHeight; //* scale region.r = 1f; region.g = 1f; region.b = 1f; region.a = 1f; region.UpdateOffset(); return region; } public void Start () { _animationSkeletonUnitySpine = GetComponent<SkeletonAnimation>(); _skeletonSpine = _animationSkeletonUnitySpine.Skeleton var daggerAttachment = this.NewRegionAttachmentFromSpriteCollection("Dagger", dataCollectionSpriteTK2D); int indexSlotWeapon = skeleton.FindSlotIndex("Weapon"); _skeletonSpine.Skin.AddAttachment(indexSlotWeapon, "Weapon-Dagger", daggerAttachment); }

Hi Pharan,

Thanks again for your help. I didn't notice that the height and width were 0, I only looked at RegionHeight and RegionWidth, and you are right, it should be expected that they would be invisible under those circumstances. It's actually kind of weird though that they have to be set when RegionHeight and RegionWidth are set to the size of the sprite. Why is that?

As for UpdateOffset, I was calling it. I omitted it in the post for brevity's sake. This time I'm going to copy the exact code.

public tk2dSpriteCollectionData DataCollectionSpriteTK2D;
public String NameSlotSkeletonSpineEquipmentHandLeft;

private Spine.Unity.SkeletonAnimation _animationSkeletonUnitySpine;
private Int32 _indexSlotSkeletonSpineEquipmentHandLeft;
private Spine.Unity.TK2D.SpriteCollectionAttachmentLoader _loaderAttachmentCollectionSpriteTK2DUnitySpine;
private Spine.Skeleton _skeletonSpine;
private Spine.AnimationState _stateAnimationSpine;

private void Awake()
{
   _animationSkeletonUnitySpine = GetComponent<Spine.Unity.SkeletonAnimation>();

   _loaderAttachmentCollectionSpriteTK2DUnitySpine = new SpriteCollectionAttachmentLoader(DataCollectionSpriteTK2D);

   _skeletonSpine = _animationSkeletonUnitySpine.Skeleton;
   _stateAnimationSpine = _animationSkeletonUnitySpine.AnimationState;
}

private RegionAttachment NewRegionAttachmentFromSpriteCollection(String nameSpriteTK2D)
{
   RegionAttachment attachmentRegionCollectionSprite =
      _loaderAttachmentCollectionSpriteTK2DUnitySpine.NewRegionAttachment(null, 
                                                         nameSpriteTK2D, 
                                                         nameSpriteTK2D);

   attachmentRegionCollectionSprite.X = 0;
   attachmentRegionCollectionSprite.Y = 0;

   attachmentRegionCollectionSprite.ScaleX = 1;
   attachmentRegionCollectionSprite.ScaleY = 1;

   attachmentRegionCollectionSprite.Rotation = 0;

   attachmentRegionCollectionSprite.Height = attachmentRegionCollectionSprite.RegionHeight;
   attachmentRegionCollectionSprite.Width = attachmentRegionCollectionSprite.RegionWidth;

   attachmentRegionCollectionSprite.R = 1;
   attachmentRegionCollectionSprite.G = 1;
   attachmentRegionCollectionSprite.B = 1;
   attachmentRegionCollectionSprite.A = 1;

   attachmentRegionCollectionSprite.UpdateOffset();

   return attachmentRegionCollectionSprite;
}

private void Start()
{
   RegionAttachment attachmentRegionDagger = NewRegionAttachmentFromSpriteCollection("Wpn-Dagger");       
_indexSlotSkeletonSpineEquipmentHandLeft = SkeletonSpine.FindSlotIndex(NameSlotSkeletonSpineEquipmentHandLeft); _skeletonSpine.Skin.AddAttachment(_indexSlotSkeletonSpineEquipmentHandLeft, "Eqpmnt-Hnd-Lft", attachmentRegionDagger); }

The sprite still does not show up. Here are the updated properties with Height and Width set.

  • attachmentRegionDagger "Wpn-Dagger" Spine.RegionAttachment
  • base "Wpn-Dagger" Spine.Attachment
    A 1 System.Single
    B 1 System.Single
    G 1 System.Single
    Height 500 System.Single
  • Offset System.Single[8] System.Single[]
    [0] -51 System.Single
    [1] -250 System.Single
    [2] -51 System.Single
    [3] 250 System.Single
    [4] 51 System.Single
    [5] 250 System.Single
    [6] 51 System.Single
    [7] -250 System.Single
    Path "Wpn-Dagger" System.String
    R 1 System.Single
    RegionHeight 500 System.Single
    RegionOffsetX 0 System.Single
    RegionOffsetY 0 System.Single
    RegionOriginalHeight 500 System.Single
    RegionOriginalWidth 102 System.Single
    RegionWidth 102 System.Single
  • RendererObject "Weapons-SpriteAtlasMaterial (UnityEngine.Material)" System.Object
    Rotation 0 System.Single
    ScaleX 1 System.Single
    ScaleY 1 System.Single
  • UVs System.Single[8] System.Single[]
    Width 102 System.Single
    X 0 System.Single
    Y 0 System.Single
    a 1 System.Single
    b 1 System.Single
    g 1 System.Single
    height 500 System.Single
  • offset System.Single[8] System.Single[]
    r 1 System.Single
    regionHeight 500 System.Single
    regionOffsetX 0 System.Single
    regionOffsetY 0 System.Single
    regionOriginalHeight 500 System.Single
    regionOriginalWidth 102 System.Single
    regionWidth 102 System.Single
    rotation 0 System.Single
    scaleX 1 System.Single
    scaleY 1 System.Single
  • uvs System.Single[8] System.Single[]
    width 102 System.Single
    x 0 System.Single
    y 0 System.Single
  • Static members

At this point, I'm looking at everything. I can spawn a Dagger sprite (one that I can see) from the SpriteCollection into the scene. So I think the SpriteCollection is functioning correctly. I'm wondering if the problem could be with the Spine file.

How should we proceed at this point?

Thanks,
-n3xes

You added it to the skin.
You can call skeleton.SetSlotsToSetupPose() to ensure the skeleton uses its current skin to set up the default attachments.

Hi Pharan,

I tried this.

private void Start()
{
   RegionAttachment attachmentRegionDagger = NewRegionAttachmentFromSpriteCollection("Wpn-Dagger");       
_indexSlotSkeletonSpineEquipmentHandLeft = SkeletonSpine.FindSlotIndex(NameSlotSkeletonSpineEquipmentHandLeft); _skeletonSpine.Skin.AddAttachment(_indexSlotSkeletonSpineEquipmentHandLeft, "Eqpmnt-Hnd-Lft", attachmentRegionDagger); _skeletonSpine.SetSlotsToSetupPose(); }

Is this what you meant? Because it didn't work either. :bang: 😐

-n3xes

Does your slot or animation actually use the skin placeholder named "Eqpmnt-Hnd-Lft"?
If not, you'd have to set it, I guess.

But if you weren't using and animating with the right skin placeholder, you wouldn't have needed to add it to the skin in the first place. Slot.Attachment = thatNewAttachment would've been enough.

THAT WAS IT!!!

Hi Pharan,

I couldn't tell that the name parameter meant skin placeholder name.

So for the sake of others reference. The "name" parameter of the Spine.Skin.AddAttachment(int slotIndex, string name, SpineAttachment attachment) method requires the skin placeholder name to be specified as the argument.

So after some scaling issues are fixed by scaling the attachment's Height and Width using this code.

float scaleSprite = 1.0f / (DataCollectionSpriteTK2D.invOrthoSize * DataCollectionSpriteTK2D.halfTargetHeight);

attachmentRegionCollectionSprite.Height = attachmentRegionCollectionSprite.RegionHeight * scaleSprite;
attachmentRegionCollectionSprite.Width = attachmentRegionCollectionSprite.RegionWidth * scaleSprite;

The attachment is finally visible and the correct size. Only one problem left. It is not following the orientation of the bone. How can that be fixed?

Thanks.
-n3xes

It would only follow the bone if the slot you put it in is actually the child or an eventual descendant of that bone.

If it's following but just doesn't have the correct alignment, that's where you would set the X, Y, ScaleX, ScaleY, and Rotation properties.